Skip to content

Commit

Permalink
add particle sim (#13)
Browse files Browse the repository at this point in the history
* Add the ability to add a compute pass

* Add particles gameobject (does nothing yet)

* feat: Add particle system with WebGPU rendering and basic movement

* refactor: Update particle rendering and shader structure

* Rendering one particle!!

* feat: Add DrawInstanced method to RenderPass for instanced rendering support

* refactor: Implement Draw using DrawInstanced with single instance

* feat: Add parent pointer to GameObject and set parent references in get_gameobjects

* refactor: Implement recursive game object traversal with proper parent handling

* Reorder

* refactor: Split CalculateMVP into separate model, view, and projection functions

* feat: Add MVP() method to GameObject for easy matrix calculation

* feat: Add parent transform inheritance to GameObject MVP calculation

* use MVP

* feat: Add VertexBufferLayouts template for composing multiple vertex buffer layouts

* refactor: Update RenderPipeline to support multiple vertex buffer layouts

* tweak

* refactor: Fix VertexBufferLayouts to handle move-only VertexBufferInfo

* hmm

* Instancing works!

* confetti!!

* Initial compute shader

* refactor: Update BindGroupLayout to include buffer count in getId and setEntry methods

* fix: Resolve type conversion and narrowing issues in BindGroupLayout

* feat: Add WorldInfo struct to pass deltaTime and mouse position to compute shader

* feat: Add worldInfo uniform buffer to Particles constructor

* feat: Add gravitational mouse attraction to particle simulation

* play with gravity

* increase workgroup size

* feat: Add SceneGeometry class for managing 3D scene geometry

* refactor: Extract scene geometry computation to SceneGeometry class

* refactor: Split computeSceneGeometry into separate wall and invisibility path functions

* refactor: Inline getFlattenedBounds and computeInvisibilityPaths methods

* simplify

* refactor: Simplify visibility computation method signature

* feat: Add BVH acceleration structure for line intersection tests in wall paths

* fix: Remove duplicate AABB constructor and improve comments

* feat: Add line intersection method to BVHNode for efficient line segment intersection checks

* refactor: Move BVH implementation to separate source file

* feat: Add ray intersection method to BVH for efficient ray-segment intersection

* refactor: Remove unused `cull_frontfaces` flag in visibility polygon computation

* refactor: Modify visibility polygon computation to use BVH acceleration structure

* Use BVH acceleration structure when computing FoW

* feat: Add BVH debug visualization with recursive AABB drawing

* Flattenable-BVH

* feat: Add shader include processing functionality

* Rearrange

* More compact BVHNode

* weird

* Can't believe that was it

* bring force back

* refactor: Add normal to SegmentIntersection struct for improved ray tracing

* feat: Implement realistic particle wall bouncing with reflection physics

* update buffer

* only spawn particles when p is pressed

* buffer upload resize should be based on capacity

* push rather than emplace

* hmm
  • Loading branch information
anchpop authored Nov 29, 2024
1 parent 45484b7 commit f357a0c
Show file tree
Hide file tree
Showing 37 changed files with 1,634 additions and 284 deletions.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -405,3 +405,7 @@ res_path.hpp

build_emscripten
build_analysis
.aider*
.env

build_xcode
5 changes: 4 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,10 @@
"print": "cpp",
"filesystem": "cpp",
"forward_list": "cpp",
"ranges": "cpp"
"ranges": "cpp",
"cfenv": "cpp",
"regex": "cpp",
"valarray": "cpp"
},
"files.exclude": {
"build": true,
Expand Down
46 changes: 46 additions & 0 deletions OpenGL/res/shaders/particles.wgsl
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
struct Particle {
@location(1) position: vec2<f32>, // World space position
@location(2) velocity: vec2<f32>,
@location(3) color: vec4<f32>,
};

struct VertexInput {
@location(0) position: vec2<f32>, // Local vertex position (relative to particle center)
};

// Separate matrices for clearer transform chain
struct VertexUniforms {
world_to_clip: mat4x4<f32>, // View-Projection matrix only
};

@group(0) @binding(0)
var<uniform> vertexUniforms: VertexUniforms;

struct VertexOutput {
@builtin(position) Position: vec4<f32>,
@location(0) color: vec4<f32>,
};

struct FragmentOutput {
@location(0) color: vec4<f32>,
};

@vertex
fn vertex_main(vertex: VertexInput, particle: Particle) -> VertexOutput {
var output: VertexOutput;

// Transform the local vertex position to world space relative to particle position
let world_pos = vertex.position + particle.position;

// Transform from world space to clip space using world_to_clip matrix
output.Position = vertexUniforms.world_to_clip * vec4<f32>(world_pos, 0.0, 1.0);
output.color = particle.color;
return output;
}

@fragment
fn fragment_main(input: VertexOutput) -> FragmentOutput {
var output: FragmentOutput;
output.color = input.color;
return output;
}
209 changes: 209 additions & 0 deletions OpenGL/res/shaders/particlesCompute.wgsl
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
struct WorldInfo {
deltaTime : f32,
mousePos : vec2<f32>,
};

struct Particle {
position : vec2<f32>, // World space position
velocity : vec2<f32>,
color : vec4<f32>,
};

struct Segment {
start : vec2<f32>,
end : vec2<f32>,
};

struct BvhNode {
leftTypeCount : u32, // leftType :1, leftCount :31
leftOffset : u32,

rightTypeCount : u32, // rightType :1, rightCount :31
rightOffset : u32,

leftBBoxMin : vec2<f32>,
leftBBoxMax : vec2<f32>,

rightBBoxMin : vec2<f32>,
rightBBoxMax : vec2<f32>,
};

struct AABB {
min : vec2<f32>,
max : vec2<f32>,
};

// Helper functions to unpack BVH Node data
fn getLeftType(node : BvhNode) -> u32 {
return node.leftTypeCount >> 31u;
}

fn getLeftCount(node : BvhNode) -> u32 {
return node.leftTypeCount & 0x7FFFFFFFu;
}

fn getRightType(node : BvhNode) -> u32 {
return node.rightTypeCount >> 31u;
}

fn getRightCount(node : BvhNode) -> u32 {
return node.rightTypeCount & 0x7FFFFFFFu;
}

fn isLeafNode(node : BvhNode, isLeft : bool) -> bool {
if isLeft {
return getLeftType(node) == 1u;
} else {
return getRightType(node) == 1u;
}
}

fn getChildOffset(node : BvhNode, isLeft : bool) -> u32 {
if isLeft {
return node.leftOffset;
} else {
return node.rightOffset;
}
}

fn getChildCount(node : BvhNode, isLeft : bool) -> u32 {
if isLeft {
return getLeftCount(node);
} else {
return getRightCount(node);
}
}

fn getChildBBox(node : BvhNode, isLeft : bool) -> AABB {
if isLeft {
return AABB(node.leftBBoxMin, node.leftBBoxMax);
} else {
return AABB(node.rightBBoxMin, node.rightBBoxMax);
}
}

// Utility functions
fn cross2D(a : vec2<f32>, b : vec2<f32>) -> f32 {
return a.x * b.y - a.y * b.x;
}

fn dot2D(a : vec2<f32>, b : vec2<f32>) -> f32 {
return a.x * b.x + a.y * b.y;
}

struct Ray {
origin : vec2<f32>,
direction : vec2<f32>,
};

struct SegmentIntersection {
hit : bool,
position : vec2<f32>,
normal : vec2<f32>,
t : f32,
};

// Buffer bindings
@group(0) @binding(0) var<storage, read_write> particleBuffer : array<Particle>;
@group(0) @binding(1) var<uniform> world : WorldInfo;
@group(0) @binding(2) var<storage, read> segments : array<Segment>;
@group(0) @binding(3) var<storage, read> bvhNodes : array<BvhNode>;

// Constants
const G : f32 = 30.0;
const MIN_DISTANCE_SQUARED : f32 = 1.0; // Prevent division by zero

// Compute shader entry point
@compute @workgroup_size(256)
fn compute_main(@builtin(global_invocation_id) id : vec3<u32>) {
let index = id.x;

let particle = &particleBuffer[index];

// Calculate direction to mouse
let toMouse = world.mousePos - particle.position;
let distanceSquared = max(dot2D(toMouse, toMouse), MIN_DISTANCE_SQUARED);

// Calculate gravitational force (F = G * m1 * m2 / r^2)
// Since mass is uniform we can simplify
let force = normalize(toMouse) * G / distanceSquared;

// Update velocity (a = F/m, simplified since mass = 1)
particleBuffer[index].velocity += force * world.deltaTime;

// New position based on velocity
let newPosition = particle.position + particleBuffer[index].velocity * world.deltaTime;

// Check for collision with walls
let intersection = findWallCollision(particle.position, newPosition);

if (intersection.hit) {
// Bounce coefficient (1.0 = perfect bounce, 0.0 = full stop)
let bounce = 0.8;

// Calculate reflection vector
let v = particleBuffer[index].velocity;
let n = intersection.normal;
let reflected = v - 2.0 * dot2D(v, n) * n;

// Update velocity with bounce effect
let newVelocity = reflected * bounce;
particleBuffer[index].velocity = newVelocity;

// Place particle at intersection point
particleBuffer[index].position = intersection.position + newVelocity * world.deltaTime;
} else {
// No collision, update particle position normally
particleBuffer[index].position = newPosition;
}
}

fn findWallCollision(particlePosition : vec2<f32>, newPosition : vec2<f32>) -> SegmentIntersection {
var closest : SegmentIntersection;
closest.hit = false;
closest.t = 999999.0;

let particlePath = Segment(particlePosition, newPosition);

for (var i: u32 = 0; i < arrayLength(&segments); i++) {
let wall = segments[i];
let intersection = segmentIntersection(wall, particlePath);

if (intersection.hit && intersection.t < closest.t) {
closest = intersection;
}
}

return closest;
}

fn segmentIntersection(s1 : Segment, s2 : Segment) -> SegmentIntersection {
var result : SegmentIntersection;
result.hit = false;

let p = s1.start;
let r = s1.end - s1.start;
let q = s2.start;
let s = s2.end - s2.start;

let r_cross_s = cross2D(r, s);
let q_p = q - p;

if (abs(r_cross_s) < 1e-8) {
return result; // Lines are parallel
}

let t = cross2D(q_p, s) / r_cross_s;
let u = cross2D(q_p, r) / r_cross_s;

if (t >= 0.0 && t <= 1.0 && u >= 0.0 && u <= 1.0) {
result.hit = true;
result.t = t;
result.position = p + t * r;
result.normal = normalize(vec2<f32>(-r.y, r.x)); // Perpendicular to wall
return result;
}

return result;
}

2 changes: 1 addition & 1 deletion OpenGL/res/shaders/stars.wgsl
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ fn starColor(seed: f32) -> vec3<f32> {
fn main_1() {
var uv_2: vec2<f32>;
var aspectRatio: f32;
var numStars: i32 = 502i;
var numStars: i32 = 2i;
var finalColor: vec3<f32> = vec3(0f);
var i: i32 = 0i;
var seed_2: f32;
Expand Down
Loading

0 comments on commit f357a0c

Please sign in to comment.