1/**
2 * Copyright 2012 the V8 project authors. All rights reserved.
3 * Copyright 2009 Oliver Hunt <http://nerget.com>
4 *
5 * Permission is hereby granted, free of charge, to any person
6 * obtaining a copy of this software and associated documentation
7 * files (the "Software"), to deal in the Software without
8 * restriction, including without limitation the rights to use,
9 * copy, modify, merge, publish, distribute, sublicense, and/or sell
10 * copies of the Software, and to permit persons to whom the
11 * Software is furnished to do so, subject to the following
12 * conditions:
13 *
14 * The above copyright notice and this permission notice shall be
15 * included in all copies or substantial portions of the Software.
16 *
17 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
19 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
20 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
21 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
22 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
23 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
24 * OTHER DEALINGS IN THE SOFTWARE.
25 */
26
27var NavierStokes = new BenchmarkSuite('NavierStokes', 1484000,
28                                      [new Benchmark('NavierStokes',
29                                                     runNavierStokes,
30                                                     setupNavierStokes,
31                                                     tearDownNavierStokes,
32                                                     16)]);
33
34var solver = null;
35var nsFrameCounter = 0;
36
37function runNavierStokes()
38{
39    solver.update();
40    nsFrameCounter++;
41
42    if(nsFrameCounter==15)
43        checkResult(solver.getDens());
44}
45
46function checkResult(dens) {
47
48    this.result = 0;
49    for (var i=7000;i<7100;i++) {
50        this.result+=~~((dens[i]*10));
51    }
52
53    if (this.result!=74) {
54        alert("checksum failed: " + this.result);
55    }
56}
57
58function setupNavierStokes()
59{
60    solver = new FluidField(null);
61    solver.setResolution(128, 128);
62    solver.setIterations(20);
63    solver.setDisplayFunction(function(){});
64    solver.setUICallback(prepareFrame);
65    solver.reset();
66}
67
68function tearDownNavierStokes()
69{
70    solver = null;
71}
72
73function addPoints(field) {
74    var n = 64;
75    for (var i = 1; i <= n; i++) {
76        field.setVelocity(i, i, n, n);
77        field.setDensity(i, i, 5);
78        field.setVelocity(i, n - i, -n, -n);
79        field.setDensity(i, n - i, 20);
80        field.setVelocity(128 - i, n + i, -n, -n);
81        field.setDensity(128 - i, n + i, 30);
82    }
83}
84
85var framesTillAddingPoints = 0;
86var framesBetweenAddingPoints = 5;
87
88function prepareFrame(field)
89{
90    if (framesTillAddingPoints == 0) {
91        addPoints(field);
92        framesTillAddingPoints = framesBetweenAddingPoints;
93        framesBetweenAddingPoints++;
94    } else {
95        framesTillAddingPoints--;
96    }
97}
98
99// Code from Oliver Hunt (http://nerget.com/fluidSim/pressure.js) starts here.
100function FluidField(canvas) {
101    function addFields(x, s, dt)
102    {
103        for (var i=0; i<size ; i++ ) x[i] += dt*s[i];
104    }
105
106    function set_bnd(b, x)
107    {
108        if (b===1) {
109            for (var i = 1; i <= width; i++) {
110                x[i] =  x[i + rowSize];
111                x[i + (height+1) *rowSize] = x[i + height * rowSize];
112            }
113
114            for (var j = 1; i <= height; i++) {
115                x[j * rowSize] = -x[1 + j * rowSize];
116                x[(width + 1) + j * rowSize] = -x[width + j * rowSize];
117            }
118        } else if (b === 2) {
119            for (var i = 1; i <= width; i++) {
120                x[i] = -x[i + rowSize];
121                x[i + (height + 1) * rowSize] = -x[i + height * rowSize];
122            }
123
124            for (var j = 1; j <= height; j++) {
125                x[j * rowSize] =  x[1 + j * rowSize];
126                x[(width + 1) + j * rowSize] =  x[width + j * rowSize];
127            }
128        } else {
129            for (var i = 1; i <= width; i++) {
130                x[i] =  x[i + rowSize];
131                x[i + (height + 1) * rowSize] = x[i + height * rowSize];
132            }
133
134            for (var j = 1; j <= height; j++) {
135                x[j * rowSize] =  x[1 + j * rowSize];
136                x[(width + 1) + j * rowSize] =  x[width + j * rowSize];
137            }
138        }
139        var maxEdge = (height + 1) * rowSize;
140        x[0]                 = 0.5 * (x[1] + x[rowSize]);
141        x[maxEdge]           = 0.5 * (x[1 + maxEdge] + x[height * rowSize]);
142        x[(width+1)]         = 0.5 * (x[width] + x[(width + 1) + rowSize]);
143        x[(width+1)+maxEdge] = 0.5 * (x[width + maxEdge] + x[(width + 1) + height * rowSize]);
144    }
145
146    function lin_solve(b, x, x0, a, c)
147    {
148        if (a === 0 && c === 1) {
149            for (var j=1 ; j<=height; j++) {
150                var currentRow = j * rowSize;
151                ++currentRow;
152                for (var i = 0; i < width; i++) {
153                    x[currentRow] = x0[currentRow];
154                    ++currentRow;
155                }
156            }
157            set_bnd(b, x);
158        } else {
159            var invC = 1 / c;
160            for (var k=0 ; k<iterations; k++) {
161                for (var j=1 ; j<=height; j++) {
162                    var lastRow = (j - 1) * rowSize;
163                    var currentRow = j * rowSize;
164                    var nextRow = (j + 1) * rowSize;
165                    var lastX = x[currentRow];
166                    ++currentRow;
167                    for (var i=1; i<=width; i++)
168                        lastX = x[currentRow] = (x0[currentRow] + a*(lastX+x[++currentRow]+x[++lastRow]+x[++nextRow])) * invC;
169                }
170                set_bnd(b, x);
171            }
172        }
173    }
174
175    function diffuse(b, x, x0, dt)
176    {
177        var a = 0;
178        lin_solve(b, x, x0, a, 1 + 4*a);
179    }
180
181    function lin_solve2(x, x0, y, y0, a, c)
182    {
183        if (a === 0 && c === 1) {
184            for (var j=1 ; j <= height; j++) {
185                var currentRow = j * rowSize;
186                ++currentRow;
187                for (var i = 0; i < width; i++) {
188                    x[currentRow] = x0[currentRow];
189                    y[currentRow] = y0[currentRow];
190                    ++currentRow;
191                }
192            }
193            set_bnd(1, x);
194            set_bnd(2, y);
195        } else {
196            var invC = 1/c;
197            for (var k=0 ; k<iterations; k++) {
198                for (var j=1 ; j <= height; j++) {
199                    var lastRow = (j - 1) * rowSize;
200                    var currentRow = j * rowSize;
201                    var nextRow = (j + 1) * rowSize;
202                    var lastX = x[currentRow];
203                    var lastY = y[currentRow];
204                    ++currentRow;
205                    for (var i = 1; i <= width; i++) {
206                        lastX = x[currentRow] = (x0[currentRow] + a * (lastX + x[currentRow] + x[lastRow] + x[nextRow])) * invC;
207                        lastY = y[currentRow] = (y0[currentRow] + a * (lastY + y[++currentRow] + y[++lastRow] + y[++nextRow])) * invC;
208                    }
209                }
210                set_bnd(1, x);
211                set_bnd(2, y);
212            }
213        }
214    }
215
216    function diffuse2(x, x0, y, y0, dt)
217    {
218        var a = 0;
219        lin_solve2(x, x0, y, y0, a, 1 + 4 * a);
220    }
221
222    function advect(b, d, d0, u, v, dt)
223    {
224        var Wdt0 = dt * width;
225        var Hdt0 = dt * height;
226        var Wp5 = width + 0.5;
227        var Hp5 = height + 0.5;
228        for (var j = 1; j<= height; j++) {
229            var pos = j * rowSize;
230            for (var i = 1; i <= width; i++) {
231                var x = i - Wdt0 * u[++pos];
232                var y = j - Hdt0 * v[pos];
233                if (x < 0.5)
234                    x = 0.5;
235                else if (x > Wp5)
236                    x = Wp5;
237                var i0 = x | 0;
238                var i1 = i0 + 1;
239                if (y < 0.5)
240                    y = 0.5;
241                else if (y > Hp5)
242                    y = Hp5;
243                var j0 = y | 0;
244                var j1 = j0 + 1;
245                var s1 = x - i0;
246                var s0 = 1 - s1;
247                var t1 = y - j0;
248                var t0 = 1 - t1;
249                var row1 = j0 * rowSize;
250                var row2 = j1 * rowSize;
251                d[pos] = s0 * (t0 * d0[i0 + row1] + t1 * d0[i0 + row2]) + s1 * (t0 * d0[i1 + row1] + t1 * d0[i1 + row2]);
252            }
253        }
254        set_bnd(b, d);
255    }
256
257    function project(u, v, p, div)
258    {
259        var h = -0.5 / Math.sqrt(width * height);
260        for (var j = 1 ; j <= height; j++ ) {
261            var row = j * rowSize;
262            var previousRow = (j - 1) * rowSize;
263            var prevValue = row - 1;
264            var currentRow = row;
265            var nextValue = row + 1;
266            var nextRow = (j + 1) * rowSize;
267            for (var i = 1; i <= width; i++ ) {
268                div[++currentRow] = h * (u[++nextValue] - u[++prevValue] + v[++nextRow] - v[++previousRow]);
269                p[currentRow] = 0;
270            }
271        }
272        set_bnd(0, div);
273        set_bnd(0, p);
274
275        lin_solve(0, p, div, 1, 4 );
276        var wScale = 0.5 * width;
277        var hScale = 0.5 * height;
278        for (var j = 1; j<= height; j++ ) {
279            var prevPos = j * rowSize - 1;
280            var currentPos = j * rowSize;
281            var nextPos = j * rowSize + 1;
282            var prevRow = (j - 1) * rowSize;
283            var currentRow = j * rowSize;
284            var nextRow = (j + 1) * rowSize;
285
286            for (var i = 1; i<= width; i++) {
287                u[++currentPos] -= wScale * (p[++nextPos] - p[++prevPos]);
288                v[currentPos]   -= hScale * (p[++nextRow] - p[++prevRow]);
289            }
290        }
291        set_bnd(1, u);
292        set_bnd(2, v);
293    }
294
295    function dens_step(x, x0, u, v, dt)
296    {
297        addFields(x, x0, dt);
298        diffuse(0, x0, x, dt );
299        advect(0, x, x0, u, v, dt );
300    }
301
302    function vel_step(u, v, u0, v0, dt)
303    {
304        addFields(u, u0, dt );
305        addFields(v, v0, dt );
306        var temp = u0; u0 = u; u = temp;
307        var temp = v0; v0 = v; v = temp;
308        diffuse2(u,u0,v,v0, dt);
309        project(u, v, u0, v0);
310        var temp = u0; u0 = u; u = temp;
311        var temp = v0; v0 = v; v = temp;
312        advect(1, u, u0, u0, v0, dt);
313        advect(2, v, v0, u0, v0, dt);
314        project(u, v, u0, v0 );
315    }
316    var uiCallback = function(d,u,v) {};
317
318    function Field(dens, u, v) {
319        // Just exposing the fields here rather than using accessors is a measurable win during display (maybe 5%)
320        // but makes the code ugly.
321        this.setDensity = function(x, y, d) {
322             dens[(x + 1) + (y + 1) * rowSize] = d;
323        }
324        this.getDensity = function(x, y) {
325             return dens[(x + 1) + (y + 1) * rowSize];
326        }
327        this.setVelocity = function(x, y, xv, yv) {
328             u[(x + 1) + (y + 1) * rowSize] = xv;
329             v[(x + 1) + (y + 1) * rowSize] = yv;
330        }
331        this.getXVelocity = function(x, y) {
332             return u[(x + 1) + (y + 1) * rowSize];
333        }
334        this.getYVelocity = function(x, y) {
335             return v[(x + 1) + (y + 1) * rowSize];
336        }
337        this.width = function() { return width; }
338        this.height = function() { return height; }
339    }
340    function queryUI(d, u, v)
341    {
342        for (var i = 0; i < size; i++)
343            u[i] = v[i] = d[i] = 0.0;
344        uiCallback(new Field(d, u, v));
345    }
346
347    this.update = function () {
348        queryUI(dens_prev, u_prev, v_prev);
349        vel_step(u, v, u_prev, v_prev, dt);
350        dens_step(dens, dens_prev, u, v, dt);
351        displayFunc(new Field(dens, u, v));
352    }
353    this.setDisplayFunction = function(func) {
354        displayFunc = func;
355    }
356
357    this.iterations = function() { return iterations; }
358    this.setIterations = function(iters) {
359        if (iters > 0 && iters <= 100)
360           iterations = iters;
361    }
362    this.setUICallback = function(callback) {
363        uiCallback = callback;
364    }
365    var iterations = 10;
366    var visc = 0.5;
367    var dt = 0.1;
368    var dens;
369    var dens_prev;
370    var u;
371    var u_prev;
372    var v;
373    var v_prev;
374    var width;
375    var height;
376    var rowSize;
377    var size;
378    var displayFunc;
379    function reset()
380    {
381        rowSize = width + 2;
382        size = (width+2)*(height+2);
383        dens = new Array(size);
384        dens_prev = new Array(size);
385        u = new Array(size);
386        u_prev = new Array(size);
387        v = new Array(size);
388        v_prev = new Array(size);
389        for (var i = 0; i < size; i++)
390            dens_prev[i] = u_prev[i] = v_prev[i] = dens[i] = u[i] = v[i] = 0;
391    }
392    this.reset = reset;
393    this.getDens = function()
394    {
395        return dens;
396    }
397    this.setResolution = function (hRes, wRes)
398    {
399        var res = wRes * hRes;
400        if (res > 0 && res < 1000000 && (wRes != width || hRes != height)) {
401            width = wRes;
402            height = hRes;
403            reset();
404            return true;
405        }
406        return false;
407    }
408    this.setResolution(64, 64);
409}
410