-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* 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
Showing
37 changed files
with
1,634 additions
and
284 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -405,3 +405,7 @@ res_path.hpp | |
|
||
build_emscripten | ||
build_analysis | ||
.aider* | ||
.env | ||
|
||
build_xcode |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.