sand and water simulation!
authorBrendan Hansen <brendan.f.hansen@gmail.com>
Wed, 28 Apr 2021 16:16:05 +0000 (11:16 -0500)
committerBrendan Hansen <brendan.f.hansen@gmail.com>
Wed, 28 Apr 2021 16:16:05 +0000 (11:16 -0500)
site/sand_toy.wasm
src/sand_toy.onyx
src/simulation.onyx

index 1a9832b22846f2c382d38df1cf028e066532250e..ceff70b1508a06ed28cf83586b0063bc680739bd 100644 (file)
Binary files a/site/sand_toy.wasm and b/site/sand_toy.wasm differ
index 6d78cfbbc0cd5b7e6ebdf57a68a37702de5288a9..10deb919b87bd26f59f47b3036dabee7f115bb53 100644 (file)
@@ -3,11 +3,12 @@ use package core
 #private_file events :: package js_events
 #private_file gl     :: package gl
 
+particle_board: ParticleBoard;
 
 world_texture : gl.GLTexture
 world_texture_data : [] u8
-world_width := 256
-world_height := 256
+world_width := 512
+world_height := 512
 
 init :: () {
     events.init();
@@ -26,6 +27,8 @@ init :: () {
     gl.bindTexture(gl.TEXTURE_2D, -1);
 
     imgui.immediate_renderer_init();
+
+    particle_board = create_board(world_width, world_height);
 }
 
 window_width  := 0
@@ -45,28 +48,67 @@ poll_events :: () {
 
         case .MouseDown {
             if event.mouse.button == .Left {
-                println("Left button click!");
+                mx := (cast(f32) event.mouse.pos_x / cast(f32) window_width) * ~~world_width;
+                my := (cast(f32) event.mouse.pos_y / cast(f32) window_height) * ~~world_height;
+
+                radius := 10.0f;
+                for i: 100 {
+                    rx := random.float(-1, 1);
+                    ry := random.float(-1, 1);
+
+                    particle_set(^particle_board,
+                        get_index(^particle_board, ~~(mx + rx * radius), ~~(my + ry * radius)),
+                        .{ .Water });
+                }
             }
         }
     }
 }
 
 update :: () {
-    world_texture_data[(100 * world_width + 50) * 3 + 0] = 255;
-    world_texture_data[(100 * world_width + 50) * 3 + 2] = 255;
+    if random.between(0, 10) > 1 {
+        radius := 10.0f;
+        for i: 100 {
+            rx := random.float(-1, 1);
+            ry := random.float(-1, 1);
+
+            particle_set(^particle_board,
+                get_index(^particle_board, ~~(200 + rx * radius), ~~(100 + ry * radius)),
+                .{ .Sand });
+        }
+    }
+
+    update_particles(^particle_board);
+}
+
+prepare_world_texture :: () {
+    for y: particle_board.height {
+        for x: particle_board.width {
+            index := get_index(^particle_board, x, y);
+            color := particle_type_to_color(particle_board.particles[index].type);
+            world_texture_data[index * 3 + 0] = color.r;
+            world_texture_data[index * 3 + 1] = color.g;
+            world_texture_data[index * 3 + 2] = color.b;
+        }
+    }
+
+    gl.bindTexture(gl.TEXTURE_2D, world_texture);
+    gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, world_width, world_height, gl.RGB, gl.UNSIGNED_BYTE, world_texture_data);
+    gl.bindTexture(gl.TEXTURE_2D, -1);
 }
 
 draw :: () {
+    prepare_world_texture();
+
     gl.clearColor(0, 0, 0, 1);
     gl.clear(gl.COLOR_BUFFER_BIT);
 
     use imgui { immediate_set_texture, immediate_textured_quad, immediate_flush };
 
     gl.bindTexture(gl.TEXTURE_2D, world_texture);
-    gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, world_width, world_height, gl.RGB, gl.UNSIGNED_BYTE, world_texture_data);
     
     immediate_set_texture(0);
-    immediate_textured_quad(.{ -1, -1 }, .{ 2, 2 }, .{ 0, 0 }, .{ 1, 1 }, color=.{ 1, 1, 1 });
+    immediate_textured_quad(.{ -1, 1 }, .{ 2, -2 }, .{ 0, 0 }, .{ 1, 1 }, color=.{ 1, 1, 1 });
 
     immediate_flush();
 
index 89ddba6d4ef03e7d2ff893152a51da73fe3cc0af..d5c68fe681bb367765a6a1d94ad80fd6c6be3816 100644 (file)
+use package core
+
+Color :: struct {
+    Empty :: Color.{  22,  22,  22 };
+    Sand  :: Color.{ 226, 214, 173 };
+    Water :: Color.{ 100, 100, 255 };
+
+    r, g, b: u8;
+}
 
 Vec2 :: struct {
-    zero :: Vec2.{ 0, 0 };
+    zero  :: Vec2.{ 0, 0 };
+    up    :: Vec2.{  0, -1 };
+    down  :: Vec2.{  0,  1 };
+    left  :: Vec2.{ -1,  0 };
+    right :: Vec2.{  1,  0 };
 
     x, y: f32;
 }
 
 ParticleBoard :: struct {
     particles: [] Particle;
+    particle_allocator: Allocator;
+
     width, height: u32;
+    
+    time: u32 = 0;
 }
 
 Particle :: struct {
     Type :: enum {
         Empty;
         Sand;
+        Water;
     }
 
     type: Type = .Empty;
+    life: f32 = 0;
     velocity: Vec2 = .zero;
 }
 
-init_particles :: (particles: [] Particle) {
+particle_type_to_color :: (t: Particle.Type) -> Color {
+    switch t {
+        case .Sand    do return .Sand;
+        case .Water   do return .Water;
+        case #default do return .Empty;
+    }
+}
+
+create_board :: (width: u32, height: u32, allocator := context.allocator) -> ParticleBoard {
+    board := ParticleBoard.{
+        particles          = memory.make_slice(Particle, width * height, allocator=allocator),
+        particle_allocator = allocator,
+        width              = width,
+        height             = height
+    };
+
+    init_particles(^board);
+
+    return board;
+}
+
+init_particles :: (use board: ^ParticleBoard) {
     for ^particle: particles {
-        *particle = .{ .Sand };
+        *particle = .{ .Empty };
+    }
+}
+
+update_particles :: (use board: ^ParticleBoard) {
+    time += 1;
+
+    // CLEANUP: Iterating backwards in a for loop should be easier
+    // This is iterating backwards because in general, particles that are lower on the screen
+    // should update first to beable to "fall", and then particles above them can "fall" where
+    // the first particle was. Obviously this is wrong for particles that "float", but it is
+    // less noticeable for those particles. Ideally, this would all work on a secondary buffer,
+    // then a buffer swap would take place between each update.
+    while y := cast(i32) (height - 1); y >= 0 {
+        defer y -= 1;
+
+        // This for loop should alternate from L2R to R2L
+        x := 0;
+        x_step := 1;
+        x_ne := width;
+        if time % 2 == 1 {
+            x = width - 1;
+            x_step = cast(i32) -1;
+            x_ne = cast(i32) -1;
+        }
+
+        while x != x_ne {
+            defer x += x_step;
+
+            particle_type := particles[get_index(board, x, y)].type;
+
+            switch particle_type {
+                case .Sand  do update_sand(board, x, y);
+                case .Water do update_water(board, x, y);
+                case .Empty ---
+            }
+        }
+    }
+}
+
+particle_set :: (use board: ^ParticleBoard, index: i32, particle: Particle) {
+    if index < 0 do return;
+
+    particles[index] = particle;
+}
+
+// This should get inlined by Onyx, when inlining is a thing.
+get_index :: (use board: ^ParticleBoard, x: i32, y: i32) -> i32 {
+    if x < 0 || y < 0 || x >= width || y >= height do return -1;
+    return y * width + x;
+}
+
+
+#private_file
+update_sand :: (use board: ^ParticleBoard, x: i32, y: i32) {
+    index := get_index(board, x, y);
+
+    b_index  := get_index(board, x, y + 1);
+    bl_index := get_index(board, x - 1, y + 1);
+    br_index := get_index(board, x + 1, y + 1);
+
+    // NOTE: These do NOT short circuit so we are accessing memory that is not
+    // technically in our control on the second part of the condition!! This
+    // does not affect too many things as the accessing does not modify or damage
+    // the data.
+    if b_index > 0 && particles[b_index].type == .Empty {
+        particle_set(board, b_index, particles[index]);
+        particle_set(board, index, .{ .Empty });
+    }
+    else {
+        if time % 2 == 0 {
+            if bl_index > 0 && particles[bl_index].type == .Empty {
+                particle_set(board, bl_index, particles[index]);
+                particle_set(board, index, .{ .Empty });
+            }
+            elseif br_index > 0 && particles[br_index].type == .Empty {
+                particle_set(board, br_index, particles[index]);
+                particle_set(board, index, .{ .Empty });
+            }
+        } else {
+            if br_index > 0 && particles[br_index].type == .Empty {
+                particle_set(board, br_index, particles[index]);
+                particle_set(board, index, .{ .Empty });
+            }
+            elseif bl_index > 0 && particles[bl_index].type == .Empty {
+                particle_set(board, bl_index, particles[index]);
+                particle_set(board, index, .{ .Empty });
+            }
+        }
+    }
+}
+
+#private_file
+update_water :: (use board: ^ParticleBoard, x: i32, y: i32) {
+    index := get_index(board, x, y);
+
+    b_index  := get_index(board, x, y + 1);
+    bl_index := get_index(board, x - 1, y + 1);
+    br_index := get_index(board, x + 1, y + 1);
+    l_index  := get_index(board, x - 1, y);
+    r_index  := get_index(board, x + 1, y);
+
+    // NOTE: These do NOT short circuit so we are accessing memory that is not
+    // technically in our control on the second part of the condition!! This
+    // does not affect too many things as the accessing does not modify or damage
+    // the data.
+    if b_index > 0 && particles[b_index].type == .Empty {
+        particle_set(board, b_index, particles[index]);
+        particle_set(board, index, .{ .Empty });
+    }
+    else {
+        if time % 2 == 1 {
+            if bl_index > 0 && particles[bl_index].type == .Empty {
+                particle_set(board, bl_index, particles[index]);
+                particle_set(board, index, .{ .Empty });
+            }
+            elseif br_index > 0 && particles[br_index].type == .Empty {
+                particle_set(board, br_index, particles[index]);
+                particle_set(board, index, .{ .Empty });
+            }
+            elseif l_index > 0 && particles[l_index].type == .Empty {
+                particle_set(board, l_index, particles[index]);
+                particle_set(board, index, .{ .Empty });
+            }
+            elseif r_index > 0 && particles[r_index].type == .Empty {
+                particle_set(board, r_index, particles[index]);
+                particle_set(board, index, .{ .Empty });
+            }
+        } else {
+            if br_index > 0 && particles[br_index].type == .Empty {
+                particle_set(board, br_index, particles[index]);
+                particle_set(board, index, .{ .Empty });
+            }
+            elseif bl_index > 0 && particles[bl_index].type == .Empty {
+                particle_set(board, bl_index, particles[index]);
+                particle_set(board, index, .{ .Empty });
+            }
+            elseif r_index > 0 && particles[r_index].type == .Empty {
+                particle_set(board, r_index, particles[index]);
+                particle_set(board, index, .{ .Empty });
+            }
+            elseif l_index > 0 && particles[l_index].type == .Empty {
+                particle_set(board, l_index, particles[index]);
+                particle_set(board, index, .{ .Empty });
+            }
+        }
     }
 }