Skip to content
4 changes: 2 additions & 2 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -92,11 +92,11 @@
"ranges": "cpp",
"cfenv": "cpp",
"regex": "cpp",
"valarray": "cpp"
"valarray": "cpp",
"*.inc": "cpp"
},
"files.exclude": {
"build": true,
"OpenGL/vendor": true,
},
"C_Cpp.default.cppStandard": "c++20"
}
5 changes: 3 additions & 2 deletions OpenGL/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,15 @@ endif()
file(GLOB_RECURSE res_files "res/*")

# Add WebGPU
# Also adds Add GLFW
# Also adds GLFW
FetchContent_Declare(
webgpu
GIT_REPOSITORY https://github.com/anchpop/WebGPU-distribution.git
GIT_TAG 257f230bdad880339eef961b7ff5974a04e30dc5
GIT_TAG 2f6b3ec866a0c579782b759ef79649dae57ffc9b
)
FetchContent_MakeAvailable(webgpu)


add_subdirectory(${VENDOR_DIR}/glfw3webgpu)

# Add GLM
Expand Down
7 changes: 6 additions & 1 deletion OpenGL/res/shaders/particles.wgsl
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ struct Particle {
@location(1) position: vec2<f32>, // World space position
@location(2) velocity: vec2<f32>,
@location(3) color: vec4<f32>,
@location(4) age: f32,
@location(5) lifetime: f32,
};

struct VertexInput {
Expand Down Expand Up @@ -34,7 +36,10 @@ fn vertex_main(vertex: VertexInput, particle: Particle) -> VertexOutput {

// 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;
// Calculate opacity based on remaining lifetime
var finalColor = particle.color;
finalColor.a *= 1.0 - clamp(particle.age / particle.lifetime, 0.0, 1.0);
output.color = finalColor;
return output;
}

Expand Down
21 changes: 7 additions & 14 deletions OpenGL/res/shaders/particlesCompute.wgsl
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
struct WorldInfo {
deltaTime : f32,
mousePos : vec2<f32>,
};

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

struct Segment {
Expand Down Expand Up @@ -120,26 +121,15 @@ fn compute_main(@builtin(global_invocation_id) id : vec3<u32>) {

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
// New position based on current 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;
let bounce = 0.2;

// Calculate reflection vector
let v = particleBuffer[index].velocity;
Expand All @@ -156,6 +146,9 @@ fn compute_main(@builtin(global_invocation_id) id : vec3<u32>) {
// No collision, update particle position normally
particleBuffer[index].position = newPosition;
}

// Update age
particleBuffer[index].age += world.deltaTime;
}

fn findWallCollision(particlePosition : vec2<f32>, newPosition : vec2<f32>) -> SegmentIntersection {
Expand Down
3 changes: 2 additions & 1 deletion OpenGL/src/World.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,8 @@ void World::LoadMap(const std::filesystem::path& map_path) {
if (c == 'p') { // player
gameobjects.push_back(std::make_shared<Player>("Coolbox", (float)x, (float)y));
gameobjects.push_back(std::make_shared<Tile>("Floor", (float)x, (float)y));
gameobjects.push_back(std::make_shared<Particles>("Floor", DrawPriority::Character, glm::vec2(x, y)));
gameobjects.push_back(
std::make_shared<Particles>("Floor", DrawPriority::Character, glm::vec2(x, y), 1000, 8.0f, 8.0f));
}
if (c == 'f') { // floor
gameobjects.push_back(std::make_shared<Tile>("Floor", (float)x, (float)y));
Expand Down
98 changes: 58 additions & 40 deletions OpenGL/src/game_objects/Particles.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,39 +4,40 @@

#include <random>

Particles::Particles(const std::string& name, DrawPriority drawPriority, glm::vec2 position)
Particles::Particles(const std::string& name, DrawPriority drawPriority, glm::vec2 position, size_t particleCount,
float initialSpeed, float lifetime)
: GameObject(name, drawPriority, position)
, particles(std::vector<Particle>{
Particle{position + glm::vec2(0.0f, 0.0f), glm::vec2(1.0f, 0.0f), glm::vec4(1.0f, 1.0f, 0.0f, 1.0f)},
Particle{position + glm::vec2(0.0f, 0.0f), glm::vec2(0.0f, 1.0f), glm::vec4(0.0f, 1.0f, 0.0f, 1.0f)}
}),
particleBuffer(
std::make_shared<Buffer<Particle>>(particles,
wgpu::bothBufferUsages(wgpu::BufferUsage::Vertex, wgpu::BufferUsage::CopyDst,
wgpu::BufferUsage::CopySrc, wgpu::BufferUsage::Storage),
"Particles")),
pointBuffer(Buffer<ParticleVertex>::create(
{
ParticleVertex{glm::vec2(-0.5f, -0.5f) * (1.0f / 16.0f)}, // 0
ParticleVertex{glm::vec2(0.5f, -0.5f) * (1.0f / 16.0f)}, // 1
ParticleVertex{glm::vec2(0.5f, 0.5f) * (1.0f / 16.0f)}, // 2
ParticleVertex{glm::vec2(-0.5f, 0.5f) * (1.0f / 16.0f)}, // 3
},
wgpu::bothBufferUsages(wgpu::BufferUsage::CopyDst, wgpu::BufferUsage::Vertex))),
indexBuffer(IndexBuffer::create(
{
0, 1, 2, // Triangle #0 connects points #0, #1 and #2
0, 2, 3 // Triangle #1 connects points #0, #2 and #3
},
wgpu::bothBufferUsages(wgpu::BufferUsage::CopyDst, wgpu::BufferUsage::Index))),
segmentBuffer(Buffer<Segment>(
{}, wgpu::bothBufferUsages(wgpu::BufferUsage::CopySrc, wgpu::BufferUsage::CopyDst, wgpu::BufferUsage::Storage),
"segments")),
bvhBuffer(Buffer<BvhNode>(
{}, wgpu::bothBufferUsages(wgpu::BufferUsage::CopySrc, wgpu::BufferUsage::CopyDst, wgpu::BufferUsage::Storage),
"bvh")),
vertexUniform(UniformBufferView<ParticleVertexUniform>::create(ParticleVertexUniform{VP()})),
worldInfo(UniformBufferView<ParticleWorldInfo>::create(ParticleWorldInfo(0.0f, glm::vec2(0.0f)))) {}
, particles(std::vector<Particle>())
, particleBuffer(std::make_shared<Buffer<Particle>>(
particles,
wgpu::bothBufferUsages(wgpu::BufferUsage::Vertex, wgpu::BufferUsage::CopyDst, wgpu::BufferUsage::CopySrc,
wgpu::BufferUsage::Storage),
"Particles"))
, pointBuffer(Buffer<ParticleVertex>::create(
{
ParticleVertex{glm::vec2(-0.5f, -0.5f) * (1.0f / 16.0f)}, // 0
ParticleVertex{glm::vec2(0.5f, -0.5f) * (1.0f / 16.0f)}, // 1
ParticleVertex{glm::vec2(0.5f, 0.5f) * (1.0f / 16.0f)}, // 2
ParticleVertex{glm::vec2(-0.5f, 0.5f) * (1.0f / 16.0f)}, // 3
},
wgpu::bothBufferUsages(wgpu::BufferUsage::CopyDst, wgpu::BufferUsage::Vertex)))
, indexBuffer(IndexBuffer::create(
{
0, 1, 2, // Triangle #0 connects points #0, #1 and #2
0, 2, 3 // Triangle #1 connects points #0, #2 and #3
},
wgpu::bothBufferUsages(wgpu::BufferUsage::CopyDst, wgpu::BufferUsage::Index)))
, segmentBuffer(Buffer<Segment>(
{}, wgpu::bothBufferUsages(wgpu::BufferUsage::CopySrc, wgpu::BufferUsage::CopyDst, wgpu::BufferUsage::Storage),
"segments"))
, bvhBuffer(Buffer<BvhNode>(
{}, wgpu::bothBufferUsages(wgpu::BufferUsage::CopySrc, wgpu::BufferUsage::CopyDst, wgpu::BufferUsage::Storage),
"bvh"))
, vertexUniform(UniformBufferView<ParticleVertexUniform>::create(ParticleVertexUniform{VP()}))
, worldInfo(UniformBufferView<ParticleWorldInfo>::create(ParticleWorldInfo(0.01f)))
, particleCount(particleCount)
, initialSpeed(initialSpeed)
, lifetime(lifetime) {}

void Particles::render(Renderer& renderer, RenderPass& renderPass) {
if (particles.empty())
Expand All @@ -58,29 +59,46 @@ void Particles::pre_compute() {
}

void Particles::compute(Renderer& renderer, ComputePass& computePass) {
worldInfo.Update(ParticleWorldInfo(Input::deltaTime, Renderer::MousePos()));
worldInfo.Update(ParticleWorldInfo(Input::deltaTime));
BindGroup bindGroup =
ParticleComputeLayout::ToBindGroup(renderer.device, std::forward_as_tuple(*particleBuffer, 0), worldInfo,
std::forward_as_tuple(segmentBuffer, 0), std::forward_as_tuple(bvhBuffer, 0));
computePass.dispatch(renderer.particlesCompute, bindGroup, {(uint32_t)worldInfo.getOffset()}, particles.size());
}

void Particles::update() {
// add a particle if the p key is pressed
if (Input::keys_pressed[GLFW_KEY_P]) {
// Initialize particles if we haven't yet
if (particles.empty()) {
std::random_device rd;
std::mt19937 gen(rd()); // Mersenne Twister generator

// Define distribution from 0 to 1
// Define distributions
std::uniform_real_distribution<> pos_dist(-1.0, 1.0);
std::uniform_real_distribution<> vel_dist(-0.5, 0.5);
std::uniform_real_distribution<> color_dist(0.0, 1.0);
std::uniform_real_distribution<> lifetime_dist(lifetime * 0.7f, lifetime * 1.3f); // 30% variation

auto random_vel = glm::vec2(vel_dist(gen), vel_dist(gen)) * 2.0f;
addParticle(position, random_vel, glm::vec4(color_dist(gen), color_dist(gen), color_dist(gen), 1.0f));
// Reserve space for better performance
particles.reserve(particleCount);
particleViews.reserve(particleCount);

// Create all particles
for (size_t i = 0; i < particleCount; ++i) {
// Random position offset from center
glm::vec2 pos_offset(pos_dist(gen), pos_dist(gen));
glm::vec2 random_vel(vel_dist(gen), vel_dist(gen));
random_vel *= initialSpeed; // Scale velocity by parameter

float random_lifetime = lifetime_dist(gen);
addParticle(position + pos_offset, random_vel,
glm::vec4(color_dist(gen), color_dist(gen), color_dist(gen), 1.0f), 0.0f, random_lifetime);
}
}
}

void Particles::addParticle(const glm::vec2& pos, const glm::vec2& vel, const glm::vec4& color) {
particles.push_back({pos, vel, color});
void Particles::addParticle(const glm::vec2& pos, const glm::vec2& vel, const glm::vec4& color, float age,
float lifetime) {

particles.emplace_back(pos, vel, color, age, lifetime);
particleViews.push_back(particleBuffer->Add(particles.back()));
}
9 changes: 7 additions & 2 deletions OpenGL/src/game_objects/Particles.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,14 @@

class Particles : public GameObject {
public:
Particles(const std::string& name, DrawPriority drawPriority, glm::vec2 position);
Particles(const std::string& name, DrawPriority drawPriority, glm::vec2 position, size_t particleCount,
float initialSpeed = 2.0f, float lifetime = 2.0f);
virtual void render(Renderer& renderer, RenderPass& renderPass) override;
virtual void update() override;
virtual void pre_compute() override;
virtual void compute(Renderer& renderer, ComputePass& computePass) override;
void addParticle(const glm::vec2& pos, const glm::vec2& vel, const glm::vec4& color);
void addParticle(const glm::vec2& pos, const glm::vec2& vel, const glm::vec4& color,
float age, float lifetime);

private:
std::vector<Particle> particles;
Expand All @@ -24,6 +26,9 @@ class Particles : public GameObject {
Buffer<BvhNode> bvhBuffer;
UniformBufferView<ParticleVertexUniform> vertexUniform;
UniformBufferView<ParticleWorldInfo> worldInfo;
size_t particleCount;
float initialSpeed;
float lifetime;

private:
protected:
Expand Down
19 changes: 13 additions & 6 deletions OpenGL/src/rendering/DataFormats.h
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,16 @@ struct Particle {
glm::vec2 position;
glm::vec2 velocity;
glm::vec4 color;
float age;
float lifetime;
float _pad0[2];

Particle(glm::vec2 position, glm::vec2 velocity, glm::vec4 color, float age, float lifetime)
: position(position)
, velocity(velocity)
, color(color)
, age(age)
, lifetime(lifetime) {}

typedef InstanceBufferLayout<glm::vec2> Layout;
};
Expand Down Expand Up @@ -179,13 +189,10 @@ struct hash<ParticleVertex> {
} // namespace std

struct ParticleWorldInfo {
float deltaTime; // at byte offset 0
float _pad0;
glm::vec2 mousePos; // at byte offset 8
float deltaTime; // at byte offset 0

ParticleWorldInfo(float deltaTime, glm::vec2 mousePos)
: deltaTime(deltaTime)
, mousePos(mousePos) {}
ParticleWorldInfo(float deltaTime)
: deltaTime(deltaTime) {}
};

using ParticleLayout = BindGroupLayout<
Expand Down
9 changes: 4 additions & 5 deletions OpenGL/src/rendering/Renderer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,10 @@ Renderer::Renderer()
, line(
RenderPipeline<BindGroupLayouts<LineLayout>, VertexBufferLayouts<VertexBufferLayout<LineVertex>>>("line.wgsl"))
, fog(RenderPipeline<BindGroupLayouts<FogLayout>, VertexBufferLayouts<VertexBufferLayout<FogVertex>>>("fog.wgsl"))
, particles(
RenderPipeline<
BindGroupLayouts<ParticleLayout>,
VertexBufferLayouts<VertexBufferLayout<glm::vec2>, InstanceBufferLayout<glm::vec2, glm::vec2, glm::vec4>>>(
"particles.wgsl"))
, particles(RenderPipeline<BindGroupLayouts<ParticleLayout>,
VertexBufferLayouts<VertexBufferLayout<glm::vec2>,
InstanceBufferLayout<glm::vec2, glm::vec2, glm::vec4, float, float>>>(
"particles.wgsl"))
, particlesCompute(ComputePipeline<BindGroupLayouts<ParticleComputeLayout>>("particlesCompute.wgsl"))
, device(Application::get().getDevice())
, linePoints(
Expand Down
6 changes: 3 additions & 3 deletions OpenGL/src/rendering/Renderer.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,9 @@ class Renderer {
squareObject;
RenderPipeline<BindGroupLayouts<LineLayout>, VertexBufferLayouts<VertexBufferLayout<LineVertex>>> line;
RenderPipeline<BindGroupLayouts<FogLayout>, VertexBufferLayouts<VertexBufferLayout<FogVertex>>> fog;
RenderPipeline<
BindGroupLayouts<ParticleLayout>,
VertexBufferLayouts<VertexBufferLayout<glm::vec2>, InstanceBufferLayout<glm::vec2, glm::vec2, glm::vec4>>>
RenderPipeline<BindGroupLayouts<ParticleLayout>,
VertexBufferLayouts<VertexBufferLayout<glm::vec2>,
InstanceBufferLayout<glm::vec2, glm::vec2, glm::vec4, float, float>>>
particles;
ComputePipeline<BindGroupLayouts<ParticleComputeLayout>> particlesCompute;

Expand Down
Loading