Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 0e08550

Browse files
Bruno-Vdrlarpon
authored andcommittedJan 14, 2025·
examples: add an Lattice Boltzmann Method simulation (vlang#888)
1 parent 3139be0 commit 0e08550

File tree

11 files changed

+869
-0
lines changed

11 files changed

+869
-0
lines changed
 

‎examples/lbm/cell.v

+176
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
module main
2+
3+
import math
4+
import rand
5+
6+
// This file contains Code and data structures for a Lattice-Boltzmann-Method (LBM)
7+
// fluid flow simulation. The simulation is 2 Dimension, with 9 possible directions (D2Q9)
8+
//
9+
// 8 1 2
10+
//
11+
// 7 0 3
12+
//
13+
// 6 5 4
14+
15+
// Vi is an enum for direction vector of D2Q9 Lattice.
16+
pub enum Vi {
17+
center = 0
18+
north = 1
19+
north_east = 2
20+
//
21+
east = 3
22+
south_east = 4
23+
south = 5
24+
//
25+
south_west = 6
26+
west = 7
27+
north_west = 8
28+
}
29+
30+
// vfmt off
31+
const opp = [
32+
Vi.center, Vi.south, Vi.south_west,
33+
Vi.west, Vi.north_west, Vi.north,
34+
Vi.north_east, Vi.east, Vi.south_east,
35+
]!
36+
37+
// Array defined here in order to loop over a Vi enum.
38+
// Warning: This array must be coherent with Vi enum order !
39+
const vi = [
40+
Vi.center, Vi.north, Vi.north_east,
41+
Vi.east, Vi.south_east, Vi.south,
42+
Vi.south_west, Vi.west, Vi.north_west,
43+
]!
44+
45+
// wi is the weight of mini-cells.
46+
// Warning: This array must be coherent with Vi enum order !
47+
const wi = [
48+
f64(16.0 / 36.0), 4.0 / 36.0, 1.0 / 36.0,
49+
4.0 / 36.0, 1.0 / 36.0, 4.0 / 36.0,
50+
1.0 / 36.0, 4.0 / 36.0, 1.0 / 36.0,
51+
]!
52+
// vfmt on
53+
54+
// opposite returns vector of opposite direction. Yes Enum can have methods in V.
55+
// Warning: This array must be coherent with Vi enum order !
56+
fn (v Vi) opposite() Vi {
57+
return opp[int(v)]
58+
}
59+
60+
// Discrete velocity vectors, by component, with respect to SDL display orientation (North is negative on y axis)
61+
// f64 and int are provided to avoid unnecessary casts.
62+
// Warning: These vectors must be coherent with Vi enum order !
63+
const dvx_f = [0.0, 0.0, 1.0, 1.0, 1.0, 0.0, -1.0, -1.0, -1.0]!
64+
const dvy_f = [0.0, -1.0, -1.0, 0.0, 1.0, 1.0, 1.0, 0.0, -1.0]!
65+
const dvx_i = [0, 0, 1, 1, 1, 0, -1, -1, -1]!
66+
const dvy_i = [0, -1, -1, 0, 1, 1, 1, 0, -1]!
67+
68+
struct Cell {
69+
mut:
70+
obstacle bool
71+
sp [9]f64
72+
}
73+
74+
// Cell.new built, and initializes a default Cell.
75+
// Warning: sp values must be coherent with Vi enum order !
76+
fn Cell.new(o bool) Cell {
77+
return Cell{
78+
obstacle: o
79+
sp: [1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0]!
80+
} // ! means fixed size array ! Will change a day !
81+
}
82+
83+
// Return a Cell with all mini-cell set to Zero
84+
fn Cell.zero(o bool) Cell {
85+
return Cell{
86+
obstacle: o
87+
sp: [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]!
88+
}
89+
}
90+
91+
// normalize perform a normalisation on the cell so that average desnsity is rho0
92+
fn (mut c Cell) normalize() {
93+
rho := c.rho()
94+
for mut v in c.sp {
95+
v *= (rho0 / rho)
96+
}
97+
}
98+
99+
// randomize add some randomness on the cell.
100+
fn (mut c Cell) randomize(i f64) {
101+
for v in vi {
102+
r := rand.f64() * i
103+
c.sum(v, r)
104+
}
105+
}
106+
107+
// get returns mini-cell depending on the given speed vector.
108+
fn (c &Cell) get(v Vi) f64 {
109+
return c.sp[int(v)]
110+
}
111+
112+
// set forces a mini-cell value given its speed vector.
113+
fn (mut c Cell) set(v Vi, value f64) {
114+
c.sp[int(v)] = value
115+
}
116+
117+
// increase (add value) to a mini-cell value given its speed vector.
118+
fn (mut c Cell) sum(v Vi, value f64) {
119+
c.sp[int(v)] += value
120+
}
121+
122+
// rho computes whole cell's density
123+
fn (c &Cell) rho() f64 {
124+
mut sum := 0.0
125+
for v in c.sp {
126+
sum += v
127+
}
128+
assert math.is_nan(sum) == false
129+
return sum
130+
}
131+
132+
// ux computes x (horizontal) component of cell speed vector.
133+
fn (c &Cell) ux() f64 {
134+
rho := c.rho()
135+
r := 1.0 / rho * (c.sp[Vi.north_east] + c.sp[Vi.east] + c.sp[Vi.south_east] - c.sp[Vi.south_west] - c.sp[Vi.west] - c.sp[Vi.north_west])
136+
assert math.is_nan(r) == false
137+
return r
138+
}
139+
140+
// uy computes y (vertical) component of cell speed vector.
141+
fn (c &Cell) uy() f64 {
142+
rho := c.rho()
143+
r := 1.0 / rho * (-c.sp[Vi.north] - c.sp[Vi.north_east] - c.sp[Vi.north_west] +
144+
c.sp[Vi.south_east] + c.sp[Vi.south] + c.sp[Vi.south_west])
145+
assert math.is_nan(r) == false
146+
return r
147+
}
148+
149+
// ux_no_rho computes x (horizontal) component of cell speed vector, when rho is already known and passed as param.
150+
fn (c &Cell) ux_no_rho(rho f64) f64 {
151+
r := 1.0 / rho * (c.sp[Vi.north_east] + c.sp[Vi.east] + c.sp[Vi.south_east] - c.sp[Vi.south_west] - c.sp[Vi.west] - c.sp[Vi.north_west])
152+
return r
153+
}
154+
155+
// uy_no_rho computes y (vertical) component of cell speed vector, when rho is already known and passed as param.
156+
fn (c &Cell) uy_no_rho(rho f64) f64 {
157+
r := 1.0 / rho * (-c.sp[Vi.north] - c.sp[Vi.north_east] - c.sp[Vi.north_west] +
158+
c.sp[Vi.south_east] + c.sp[Vi.south] + c.sp[Vi.south_west])
159+
return r
160+
}
161+
162+
// equ computes result of equilibrium function.
163+
fn (c &Cell) equ(i Vi) f64 {
164+
rho := c.rho()
165+
ux := c.ux_no_rho(rho)
166+
uy := c.uy_no_rho(rho)
167+
168+
t1 := 3.0 * (ux * dvx_f[i] + uy * dvy_f[i])
169+
mut t2 := (ux * dvx_f[i] + uy * dvy_f[i])
170+
171+
t2 *= t2 // t2^2
172+
t2 *= (9.0 / 2.0)
173+
t3 := (3.0 / 2.0) * (ux * ux + uy * uy)
174+
r := wi[i] * rho * (1.0 + t1 + t2 - t3)
175+
return r
176+
}

‎examples/lbm/colormap.v

+46
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
module main
2+
3+
import arrays
4+
5+
struct Colormap {}
6+
7+
// build a linear color map ARGB8888 from color c1 to c2, of steps colors
8+
pub fn Colormap.build(c1 u32, c2 u32, steps int) []u32 {
9+
assert steps > 1, 'Error, generating colormap needs at least two steps.'
10+
mut cm := []u32{cap: steps}
11+
12+
// split c1 & c2 to RGB channels.
13+
mut c1_r := f64((c1 & 0xFF0000) >> 16)
14+
mut c1_g := f64((c1 & 0xFF00) >> 8)
15+
mut c1_b := f64((c1 & 0xFF))
16+
17+
c2_r := f64((c2 & 0xFF0000) >> 16)
18+
c2_g := f64((c2 & 0xFF00) >> 8)
19+
c2_b := f64((c2 & 0xFF))
20+
21+
delta_r := (c2_r - c1_r) / f64(steps - 1)
22+
delta_g := (c2_g - c1_g) / f64(steps - 1)
23+
delta_b := (c2_b - c1_b) / f64(steps - 1)
24+
25+
cm << (0xFF000000 | c1) // Emit exact start color.
26+
for _ in 1 .. steps - 1 {
27+
c1_r += delta_r
28+
c1_g += delta_g
29+
c1_b += delta_b
30+
31+
// Recompose 3 channels to ARGB8888 color.
32+
mut c := u32(0xFF_00_00_00)
33+
c |= u32(c1_r) << 16
34+
c |= u32(c1_g) << 8
35+
c |= u32(c1_b)
36+
cm << c
37+
}
38+
cm << (0xFF000000 | c2) // Emit exact end color.
39+
return cm
40+
}
41+
42+
// dual creates a color map with start color, intermediate color and final color, equaly sized.
43+
fn Colormap.dual(c1 u32, c2 u32, c3 u32, steps int) []u32 {
44+
assert steps > 2, 'Error, generating dual-colormap needs at least three steps.'
45+
return arrays.append(Colormap.build(c1, c2, steps / 2), Colormap.build(c2, c3, steps - (steps / 2)))
46+
}

‎examples/lbm/lattice.v

+278
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,278 @@
1+
module main
2+
3+
import math
4+
5+
pub struct Lattice {
6+
w int
7+
h int
8+
mut:
9+
m []Cell
10+
}
11+
12+
// Allocate x*y Cells Lattice
13+
pub fn Lattice.new(x int, y int, profile &u8) Lattice {
14+
mut ret := Lattice{x, y, []Cell{cap: x * y}}
15+
16+
for i in 0 .. (x * y) {
17+
e := unsafe { profile[i] != 0 }
18+
mut c := Cell.new(e)
19+
ret.m << c
20+
}
21+
return ret
22+
}
23+
24+
pub fn (l Lattice) str() string {
25+
return 'Lattice[${l.w}x${l.h}]'
26+
}
27+
28+
// total_rho compute the total density on the Lattice. This value is conserved.
29+
pub fn (l Lattice) total_rho() f64 {
30+
mut t := 0.0
31+
for c in l.m {
32+
if c.obstacle {
33+
continue
34+
}
35+
t += c.rho()
36+
}
37+
return t
38+
}
39+
40+
// clear the Lattice : File fields with zeros.
41+
pub fn (mut l Lattice) clear() {
42+
unsafe { vmemset(l.m.data, 0, u32(l.m.len) * sizeof(Cell)) }
43+
}
44+
45+
// add_flow create an artificial flow of i intensity in v direction
46+
// It impacts all lattice cells. It's usually a good thing to call normalize()
47+
// method after that.
48+
pub fn (mut l Lattice) add_flow(i f64, v Vi) {
49+
for mut c in l.m {
50+
if c.obstacle {
51+
continue
52+
}
53+
c.sum(v, i)
54+
}
55+
}
56+
57+
// normalize normalizes all lattice cells against rho0 so that average density
58+
// is equal to rho0.
59+
pub fn (mut l Lattice) normalize() {
60+
for mut c in l.m {
61+
if c.obstacle {
62+
continue
63+
}
64+
c.normalize()
65+
}
66+
}
67+
68+
// max_ux returns maximal horizontal speed in the whole lattice
69+
pub fn (l Lattice) max_ux() f64 {
70+
mut ux := 0.0
71+
72+
for c in l.m {
73+
if c.obstacle {
74+
continue
75+
}
76+
u := c.ux()
77+
78+
if u > ux {
79+
ux = u
80+
}
81+
}
82+
return ux
83+
}
84+
85+
// min_ux returns minimal horizontal speed in the whole lattice
86+
pub fn (l Lattice) min_ux() f64 {
87+
mut ux := 0.0
88+
89+
for c in l.m {
90+
if c.obstacle {
91+
continue
92+
}
93+
u := c.ux()
94+
95+
if u < ux {
96+
ux = u
97+
}
98+
}
99+
return ux
100+
}
101+
102+
// max_uy returns maximal horizontal speed in the whole lattice
103+
pub fn (l Lattice) max_uy() f64 {
104+
mut uy := 0.0
105+
106+
for c in l.m {
107+
if c.obstacle {
108+
continue
109+
}
110+
u := c.uy()
111+
112+
if u > uy {
113+
uy = u
114+
}
115+
}
116+
return uy
117+
}
118+
119+
// min_uy returns minimal horizontal speed in the whole lattice
120+
pub fn (l Lattice) min_uy() f64 {
121+
mut uy := 0.0
122+
123+
for c in l.m {
124+
if c.obstacle {
125+
continue
126+
}
127+
u := c.uy()
128+
129+
if u < uy {
130+
uy = u
131+
}
132+
}
133+
return uy
134+
}
135+
136+
// max_rho returns maximal cell density in the whole lattice
137+
pub fn (l Lattice) max_rho() f64 {
138+
mut r := 0.0
139+
140+
for c in l.m {
141+
if c.obstacle {
142+
continue
143+
}
144+
rho := c.rho()
145+
146+
if rho > r {
147+
r = rho
148+
}
149+
}
150+
return r
151+
}
152+
153+
// min_rho returns maximal cell density in the whole lattice
154+
pub fn (l Lattice) min_rho() f64 {
155+
mut r := 1_000_000.0
156+
157+
for c in l.m {
158+
if c.obstacle {
159+
continue
160+
}
161+
rho := c.rho()
162+
163+
if rho < r {
164+
r = rho
165+
}
166+
}
167+
return r
168+
}
169+
170+
// mean_rho returns the mean rho value over all lattice
171+
fn (l Lattice) mean_rho() f64 {
172+
mut i := 0.0
173+
for c in l.m {
174+
if c.obstacle {
175+
continue
176+
}
177+
i += c.rho()
178+
}
179+
180+
return i / l.m.len
181+
}
182+
183+
// vorticity computes vorticity at given position.
184+
fn (l Lattice) vorticity(x int, y int) f64 {
185+
if x > 0 && x < l.w - 1 {
186+
if y > 0 && y < l.h - 1 {
187+
ind := y * l.w + x
188+
omega := (l.m[ind + 1].uy() - l.m[ind - 1].uy()) - (l.m[ind + l.w].ux() - l.m[ind - l.w].ux())
189+
return omega
190+
}
191+
}
192+
return 0
193+
}
194+
195+
// randomize add random noise everywhere given i intensity.
196+
// It's usually a good thing to call normalize() method after that.
197+
pub fn (mut l Lattice) randomize(i f64) {
198+
for mut c in l.m {
199+
if c.obstacle {
200+
continue
201+
}
202+
c.randomize(i)
203+
}
204+
}
205+
206+
// move applies simulation movements handling borders and profile collisions.
207+
// Note that the rightmost column is handled differently, and outgoing mini-particle
208+
// are re-injected on the left.
209+
pub fn (l Lattice) move(mut output Lattice) {
210+
mut index := 0
211+
output.clear()
212+
213+
for y in 0 .. l.h {
214+
for x in 0 .. l.w {
215+
output.m[index].obstacle = l.m[index].obstacle // Copy src reachable state to output
216+
for m in vi { // For this cell, for all direction vectors or mini particles
217+
mini_part := l.m[index].get(m)
218+
if dst_ind := l.reachable(x, y, m) {
219+
output.m[dst_ind].sum(m, mini_part) // move mini-particle
220+
} else {
221+
output.m[index].sum(m.opposite(), mini_part) // rebound mini-particle, don't move but invert direction vector.
222+
}
223+
}
224+
index++
225+
}
226+
}
227+
}
228+
229+
// collide performs the most sensible step: Collision between mini-particles.
230+
pub fn (mut l Lattice) collide() {
231+
for mut c in l.m {
232+
if c.obstacle == false {
233+
mut new_cell := Cell.zero(false)
234+
235+
for m in vi {
236+
f := c.get(m)
237+
feq := c.equ(m)
238+
assert math.is_nan(feq) == false
239+
assert math.is_nan(f) == false
240+
new_cell.set(m, f - ((1.0 / tau) * (f - feq)))
241+
}
242+
c = new_cell
243+
}
244+
}
245+
}
246+
247+
// reachable test if destination Cell (base on x,y and Direction vector)
248+
// is cross-able or reachable. Lattice edge is limiting, as the profile as
249+
// well, none is returned on these case. For reachable, index of the
250+
// reachable Cell (in Lattice) is returned, for an easy update.
251+
fn (l Lattice) reachable(x int, y int, v Vi) ?int {
252+
assert x >= 0
253+
assert y >= 0
254+
255+
// Add direction vector to current position to get destination position.
256+
mut nx := x + dvx_i[int(v)]
257+
ny := y + dvy_i[int(v)]
258+
259+
if ny < 0 || ny >= l.h {
260+
return none
261+
}
262+
263+
if nx < 0 {
264+
nx = nx + l.w
265+
}
266+
267+
if nx >= l.w {
268+
nx = nx - l.w
269+
}
270+
271+
ind := nx + (l.w * ny) // Get 1D index in lattice.
272+
273+
return if l.m[ind].obstacle {
274+
none
275+
} else {
276+
ind // Destination cell is obstacle free. Return it's index.
277+
}
278+
}

‎examples/lbm/main.v

+330
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,330 @@
1+
/**
2+
This program is about D2Q9 Lattice Boltzmann Method:
3+
See : https://en.wikipedia.org/wiki/Lattice_Boltzmann_methods
4+
5+
It's a pet project in order to use V language: https://vlang.io/
6+
7+
The simulation is single threaded, probably buggy and should not be used
8+
for serious things. It's very sensible to tau parameter that should be
9+
carefully set. This parameter is related to fluid viscosity, and is set so
10+
that the fluid speed doesn't exceed a speed limit that breaks simulation.
11+
Too narrow passage (speed increased) may reach this limit.
12+
13+
profiles files MUST be of the same size of defined width and weight and
14+
should be 8bits per pixels. Every non zero value is considered as an
15+
obstacle.
16+
17+
to compile the program from within source directory:
18+
19+
v -prod .
20+
21+
or if you want gcc as compiler:
22+
23+
v -prod -cc gcc .
24+
25+
SDL module must be installed: https://vpm.vlang.io/packages/sdl
26+
and post install script executed, see link.
27+
28+
The simulation is quite slow, but would you like to slow it down, just
29+
uncomment the sdl.delay(...) in file.
30+
31+
32+
33+
This program is released under MIT license.
34+
*/
35+
module main
36+
37+
import sdl
38+
import sdl.image as img
39+
import os
40+
import time
41+
42+
const tau = 0.6 // Relaxation time, related to fluid viscosity.
43+
const rho0 = 32.0 // Average normalised density.
44+
const width = 512
45+
const height = 128
46+
const len_in_bytes = width * height * sizeof(u32)
47+
const obstacle_color = u32(0xFF004000)
48+
const low_color = u32(0xFFFFFF00)
49+
const middle_color = u32(0xFF000000)
50+
const high_color = u32(0xFF00FFFF)
51+
52+
// Type for rendering methods, used as parameter.
53+
type Renderer = fn (l Lattice, cm []u32, mut output []u32)
54+
55+
const renderers = [vorticity, v_speed, h_speed, densities]
56+
const renderers_names = ['vorticity', 'vertical speed', 'horizontal speed', 'density']
57+
58+
fn main() {
59+
argv := os.args.len
60+
61+
if argv != 2 {
62+
println('Usage: lbm profile_file.png')
63+
println(' e.g: ./lbm profiles/circle.png')
64+
println('During simulation press "v" to show different parameters.')
65+
return
66+
}
67+
68+
if sdl.init(sdl.init_video) < 0 {
69+
eprintln('sdl.init() error: ${unsafe { cstring_to_vstring(sdl.get_error()) }}')
70+
return
71+
}
72+
73+
flags := u32(sdl.WindowFlags.opengl) | u32(sdl.WindowFlags.resizable)
74+
window := sdl.create_window(c'Lattice Boltzmann Method [D2Q9]', sdl.windowpos_centered,
75+
sdl.windowpos_centered, width * 2, height * 2, flags)
76+
77+
if window == sdl.null {
78+
eprintln('sdl.create_window() error: ${unsafe { cstring_to_vstring(sdl.get_error()) }}')
79+
return
80+
}
81+
82+
r_flags := u32(sdl.RendererFlags.accelerated)
83+
renderer := sdl.create_renderer(window, -1, r_flags)
84+
85+
if renderer == sdl.null {
86+
eprintln('sdl.create_renderer() error: ${unsafe { cstring_to_vstring(sdl.get_error()) }}')
87+
return
88+
}
89+
90+
mut tex := sdl.create_texture(renderer, sdl.Format.argb8888, sdl.TextureAccess.streaming,
91+
width, height)
92+
93+
if tex == sdl.null {
94+
eprintln('sdl.create_texture() error: ${unsafe { cstring_to_vstring(sdl.get_error()) }}')
95+
return
96+
}
97+
98+
defer {
99+
sdl.destroy_texture(tex)
100+
sdl.destroy_renderer(renderer)
101+
sdl.destroy_window(window)
102+
}
103+
104+
profile := img.load(os.args[1].str)
105+
if profile == sdl.null {
106+
eprintln('Error trying to load profile .png file: ${unsafe { cstring_to_vstring(sdl.get_error()) }}')
107+
return
108+
}
109+
110+
// Check size compatibility.
111+
if profile.w != width || profile.h != height {
112+
eprintln('Error, "${os.args[1]}" profile image must match lbm lattice size : ${profile.w}x${profile.h}')
113+
return
114+
}
115+
116+
// Check profile is 1 byte / pixel.
117+
if (profile.pitch / width) != 1 {
118+
eprintln('Error profile file must be 1 byte per pixel')
119+
return
120+
}
121+
122+
// Build a colormap to be used
123+
cm := Colormap.dual(low_color, middle_color, high_color, 384)
124+
125+
// Now create Lattices, with respect to loaded profile.
126+
mut src := Lattice.new(width, height, profile.pixels)
127+
128+
src.add_flow(1.0, Vi.east)
129+
src.randomize(0.2)
130+
src.normalize()
131+
132+
mut dst := Lattice.new(width, height, profile.pixels)
133+
134+
// Allocate pixel buffer to draw in.
135+
mut pixel_buffer := []u32{len: width * height} // Dyn array heap allocated.
136+
137+
mut should_close := false
138+
mut frame := 0
139+
mut render_method := vorticity
140+
141+
println('Showing vorticiy. Press "v" to show different parameters.')
142+
143+
for {
144+
evt := sdl.Event{}
145+
for 0 < sdl.poll_event(&evt) {
146+
match evt.@type {
147+
.quit {
148+
should_close = true
149+
}
150+
.keydown {
151+
key := unsafe { sdl.KeyCode(evt.key.keysym.sym) }
152+
if key == sdl.KeyCode.escape {
153+
should_close = true
154+
break
155+
}
156+
// Show next view
157+
if key == sdl.KeyCode.v {
158+
mut i := renderers.index(render_method)
159+
i++
160+
if i >= renderers.len {
161+
i = 0
162+
}
163+
render_method = renderers[i]
164+
println('Rendering : ${renderers_names[i]}')
165+
}
166+
}
167+
else {}
168+
}
169+
}
170+
if should_close {
171+
break
172+
}
173+
174+
mut stop_watch := time.new_stopwatch()
175+
src.move(mut dst)
176+
dst.collide()
177+
render_method(dst, cm, mut pixel_buffer) // render_method can point different method !
178+
draw_colormap(cm, mut pixel_buffer)
179+
180+
// swap src and dst buffers.
181+
tmp := src
182+
src = dst
183+
dst = tmp
184+
185+
blit_pixels(tex, pixel_buffer)
186+
frame++
187+
stop_watch.stop()
188+
// println('Frame ${frame}, loop : ${stop_watch.elapsed().milliseconds()} milliseconds. ')
189+
190+
sdl.render_clear(renderer)
191+
sdl.render_copy(renderer, tex, sdl.null, sdl.null)
192+
sdl.render_present(renderer)
193+
// sdl.delay(10)
194+
}
195+
}
196+
197+
// No bound checking here
198+
// blit_pixels from buffer to texture.
199+
@[direct_array_access]
200+
fn blit_pixels(t &sdl.Texture, data []u32) {
201+
mut pixels := unsafe { voidptr(nil) }
202+
mut pitch := int(0)
203+
204+
success := sdl.lock_texture(t, sdl.null, &pixels, &pitch)
205+
if success < 0 {
206+
panic('sdl.lock_texture error: ${sdl.get_error()}')
207+
}
208+
unsafe { vmemcpy(pixels, data.data, len_in_bytes) }
209+
sdl.unlock_texture(t)
210+
}
211+
212+
fn draw_colormap(cm []u32, mut data []u32) {
213+
data[0] = 0xFF000000
214+
data[width] = 0xFF000000
215+
data[2 * width] = 0xFF000000
216+
for i in 0 .. cm.len {
217+
data[i + 1] = 0xFF000000
218+
data[i + width + 1] = cm[i]
219+
data[i + 1 + 2 * width] = 0xFF000000
220+
}
221+
data[cm.len] = 0xFF000000
222+
data[width + cm.len] = 0xFF000000
223+
data[2 * width + cm.len] = 0xFF000000
224+
}
225+
226+
// densities is a Renderer type function
227+
fn densities(l Lattice, cm []u32, mut output []u32) {
228+
mut ind := 0
229+
230+
min_rho := l.min_rho()
231+
max_rho := l.max_rho()
232+
linear := (max_rho - min_rho) / (cm.len - 1)
233+
234+
for c in l.m {
235+
if c.obstacle == true {
236+
output[ind] = obstacle_color
237+
ind++
238+
continue
239+
}
240+
241+
rho := int((c.rho() - min_rho) / linear)
242+
output[ind] = cm[rho]
243+
ind++
244+
}
245+
}
246+
247+
// h_speed is a Renderer type function
248+
fn h_speed(l Lattice, cm []u32, mut output []u32) {
249+
mut ind := 0
250+
251+
min_ux := l.min_ux()
252+
max_ux := l.max_ux()
253+
linear := (max_ux - min_ux) / (cm.len - 1)
254+
255+
for c in l.m {
256+
if c.obstacle == true {
257+
output[ind] = obstacle_color
258+
ind++
259+
continue
260+
}
261+
262+
rho := int((c.ux() - min_ux) / linear)
263+
output[ind] = cm[rho]
264+
ind++
265+
}
266+
}
267+
268+
// h_speed is a Renderer type function
269+
fn v_speed(l Lattice, cm []u32, mut output []u32) {
270+
mut ind := 0
271+
272+
min_uy := l.min_uy()
273+
max_uy := l.max_uy()
274+
linear := (max_uy - min_uy) / (cm.len - 1)
275+
276+
for c in l.m {
277+
if c.obstacle == true {
278+
output[ind] = obstacle_color
279+
ind++
280+
continue
281+
}
282+
283+
rho := int((c.uy() - min_uy) / linear)
284+
output[ind] = cm[rho]
285+
ind++
286+
}
287+
}
288+
289+
// vorticity is a Renderer type function
290+
fn vorticity(l Lattice, cm []u32, mut output []u32) {
291+
mut min := 0.0
292+
mut max := 0.0
293+
cm2 := u32(cm.len / 2)
294+
mut vorticity_table := []f64{len: l.w * l.h}
295+
296+
for y in 0 .. l.h {
297+
for x in 0 .. l.w {
298+
out := (y * l.w) + x
299+
if l.m[out].obstacle {
300+
vorticity_table[out] = -1000000.0
301+
} else {
302+
v := l.vorticity(x, y)
303+
vorticity_table[out] = v
304+
if min > v {
305+
min = v
306+
}
307+
if max < v {
308+
max = v
309+
}
310+
}
311+
}
312+
}
313+
314+
linear := (max - min) / f64(cm.len - 1)
315+
316+
for ind, v in vorticity_table {
317+
if v < -100.0 {
318+
output[ind] = obstacle_color
319+
} else {
320+
mut id := cm2 + u32(v / linear)
321+
322+
if id < 0 {
323+
id = 0
324+
} else if id >= cm.len {
325+
id = u32(cm.len - 1)
326+
}
327+
output[ind] = cm[id]
328+
}
329+
}
330+
}

‎examples/lbm/profiles/asymetric.png

195 Bytes
Loading

‎examples/lbm/profiles/barrier.png

123 Bytes
Loading

‎examples/lbm/profiles/circle.png

249 Bytes
Loading

‎examples/lbm/profiles/septum.png

373 Bytes
Loading

‎examples/lbm/profiles/test.png

385 Bytes
Loading

‎examples/lbm/readme.md

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
This program is about D2Q9 Lattice Boltzmann Method:
2+
See : https://en.wikipedia.org/wiki/Lattice_Boltzmann_methods
3+
4+
It's a pet project in order to use V language: https://vlang.io/
5+
6+
The simulation is single threaded, probably buggy and should not be used
7+
for serious things. It's very sensible to tau parameter that should be
8+
carefully set. This parameter is related to fluid viscosity, and is set so
9+
that the fluid speed doesn't exceed a speed limit that breaks simulation.
10+
Too narrow passage (speed increased) may reach this limit.
11+
12+
profiles files MUST be of the same size of defined width and weight and
13+
should be 8bits per pixels. Every non zero value is considered as an
14+
obstacle.
15+
16+
to compile the program from within source directory:
17+
18+
v -prod .
19+
20+
or if you want gcc as compiler:
21+
22+
v -prod -cc gcc .
23+
24+
SDL module must be installed: https://vpm.vlang.io/packages/sdl
25+
and post install script executed, see link.
26+
27+
The simulation is quite slow, but would you like to slow it down, just
28+
uncomment the sdl.delay(...) in file.
29+
30+
31+
32+
This program is released under MIT license.

‎examples/lbm/v.mod

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
Module {
2+
name: 'lbm'
3+
description: 'Lattice Boltzmann simulation, used as toy projet to learn about V language'
4+
version: '0.0.1'
5+
license: 'MIT'
6+
dependencies: []
7+
}

0 commit comments

Comments
 (0)
Please sign in to comment.