From: Brendan Hansen Date: Wed, 28 Apr 2021 16:16:05 +0000 (-0500) Subject: sand and water simulation! X-Git-Url: https://git.brendanfh.com/?a=commitdiff_plain;h=2761ef75c4c2bd1105270d9fcaf2c4ebffe586f7;p=sand-toy.git sand and water simulation! --- diff --git a/site/sand_toy.wasm b/site/sand_toy.wasm index 1a9832b..ceff70b 100644 Binary files a/site/sand_toy.wasm and b/site/sand_toy.wasm differ diff --git a/src/sand_toy.onyx b/src/sand_toy.onyx index 6d78cfb..10deb91 100644 --- a/src/sand_toy.onyx +++ b/src/sand_toy.onyx @@ -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(); diff --git a/src/simulation.onyx b/src/simulation.onyx index 89ddba6..d5c68fe 100644 --- a/src/simulation.onyx +++ b/src/simulation.onyx @@ -1,27 +1,212 @@ +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 }); + } + } } }