mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-03-22 23:30:14 +00:00
Add mount dust particle effects
Implemented dust cloud particles that spawn at mount feet when running on the ground, similar to the original WoW. Features: - Brownish/tan dust particles using point sprite rendering - Spawn rate proportional to movement speed - Particles rise up, drift backward, and fade out naturally - Only active when mounted, moving, and on ground (not flying) - Smooth particle animation with size growth and alpha fade Uses similar particle system architecture as swim effects with GL_POINTS rendering and custom shaders for soft circular particles.
This commit is contained in:
parent
c6a915397c
commit
c95c3db03a
5 changed files with 299 additions and 0 deletions
|
|
@ -143,6 +143,7 @@ set(WOWEE_SOURCES
|
||||||
src/rendering/minimap.cpp
|
src/rendering/minimap.cpp
|
||||||
src/rendering/world_map.cpp
|
src/rendering/world_map.cpp
|
||||||
src/rendering/swim_effects.cpp
|
src/rendering/swim_effects.cpp
|
||||||
|
src/rendering/mount_dust.cpp
|
||||||
src/rendering/loading_screen.cpp
|
src/rendering/loading_screen.cpp
|
||||||
src/rendering/video_player.cpp
|
src/rendering/video_player.cpp
|
||||||
|
|
||||||
|
|
|
||||||
50
include/rendering/mount_dust.hpp
Normal file
50
include/rendering/mount_dust.hpp
Normal file
|
|
@ -0,0 +1,50 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <GL/glew.h>
|
||||||
|
#include <glm/glm.hpp>
|
||||||
|
#include <memory>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace wowee {
|
||||||
|
namespace rendering {
|
||||||
|
|
||||||
|
class Camera;
|
||||||
|
class Shader;
|
||||||
|
|
||||||
|
class MountDust {
|
||||||
|
public:
|
||||||
|
MountDust();
|
||||||
|
~MountDust();
|
||||||
|
|
||||||
|
bool initialize();
|
||||||
|
void shutdown();
|
||||||
|
|
||||||
|
// Spawn dust particles at mount feet when moving on ground
|
||||||
|
void spawnDust(const glm::vec3& position, const glm::vec3& velocity, bool isMoving);
|
||||||
|
|
||||||
|
void update(float deltaTime);
|
||||||
|
void render(const Camera& camera);
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct Particle {
|
||||||
|
glm::vec3 position;
|
||||||
|
glm::vec3 velocity;
|
||||||
|
float lifetime;
|
||||||
|
float maxLifetime;
|
||||||
|
float size;
|
||||||
|
float alpha;
|
||||||
|
};
|
||||||
|
|
||||||
|
static constexpr int MAX_DUST_PARTICLES = 300;
|
||||||
|
std::vector<Particle> particles;
|
||||||
|
|
||||||
|
GLuint vao = 0;
|
||||||
|
GLuint vbo = 0;
|
||||||
|
std::unique_ptr<Shader> shader;
|
||||||
|
std::vector<float> vertexData;
|
||||||
|
|
||||||
|
float spawnAccum = 0.0f;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace rendering
|
||||||
|
} // namespace wowee
|
||||||
|
|
@ -27,6 +27,7 @@ class Clouds;
|
||||||
class LensFlare;
|
class LensFlare;
|
||||||
class Weather;
|
class Weather;
|
||||||
class SwimEffects;
|
class SwimEffects;
|
||||||
|
class MountDust;
|
||||||
class CharacterRenderer;
|
class CharacterRenderer;
|
||||||
class WMORenderer;
|
class WMORenderer;
|
||||||
class M2Renderer;
|
class M2Renderer;
|
||||||
|
|
@ -165,6 +166,7 @@ private:
|
||||||
std::unique_ptr<LensFlare> lensFlare;
|
std::unique_ptr<LensFlare> lensFlare;
|
||||||
std::unique_ptr<Weather> weather;
|
std::unique_ptr<Weather> weather;
|
||||||
std::unique_ptr<SwimEffects> swimEffects;
|
std::unique_ptr<SwimEffects> swimEffects;
|
||||||
|
std::unique_ptr<MountDust> mountDust;
|
||||||
std::unique_ptr<CharacterRenderer> characterRenderer;
|
std::unique_ptr<CharacterRenderer> characterRenderer;
|
||||||
std::unique_ptr<WMORenderer> wmoRenderer;
|
std::unique_ptr<WMORenderer> wmoRenderer;
|
||||||
std::unique_ptr<M2Renderer> m2Renderer;
|
std::unique_ptr<M2Renderer> m2Renderer;
|
||||||
|
|
|
||||||
210
src/rendering/mount_dust.cpp
Normal file
210
src/rendering/mount_dust.cpp
Normal file
|
|
@ -0,0 +1,210 @@
|
||||||
|
#include "rendering/mount_dust.hpp"
|
||||||
|
#include "rendering/camera.hpp"
|
||||||
|
#include "rendering/shader.hpp"
|
||||||
|
#include "core/logger.hpp"
|
||||||
|
#include <glm/gtc/matrix_transform.hpp>
|
||||||
|
#include <random>
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
|
namespace wowee {
|
||||||
|
namespace rendering {
|
||||||
|
|
||||||
|
static std::mt19937& rng() {
|
||||||
|
static std::random_device rd;
|
||||||
|
static std::mt19937 gen(rd());
|
||||||
|
return gen;
|
||||||
|
}
|
||||||
|
|
||||||
|
static float randFloat(float lo, float hi) {
|
||||||
|
std::uniform_real_distribution<float> dist(lo, hi);
|
||||||
|
return dist(rng());
|
||||||
|
}
|
||||||
|
|
||||||
|
MountDust::MountDust() = default;
|
||||||
|
MountDust::~MountDust() { shutdown(); }
|
||||||
|
|
||||||
|
bool MountDust::initialize() {
|
||||||
|
LOG_INFO("Initializing mount dust effects");
|
||||||
|
|
||||||
|
// Dust particle shader (brownish/tan dust clouds)
|
||||||
|
shader = std::make_unique<Shader>();
|
||||||
|
|
||||||
|
const char* dustVS = R"(
|
||||||
|
#version 330 core
|
||||||
|
layout (location = 0) in vec3 aPos;
|
||||||
|
layout (location = 1) in float aSize;
|
||||||
|
layout (location = 2) in float aAlpha;
|
||||||
|
|
||||||
|
uniform mat4 uView;
|
||||||
|
uniform mat4 uProjection;
|
||||||
|
|
||||||
|
out float vAlpha;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
gl_Position = uProjection * uView * vec4(aPos, 1.0);
|
||||||
|
gl_PointSize = aSize;
|
||||||
|
vAlpha = aAlpha;
|
||||||
|
}
|
||||||
|
)";
|
||||||
|
|
||||||
|
const char* dustFS = R"(
|
||||||
|
#version 330 core
|
||||||
|
in float vAlpha;
|
||||||
|
out vec4 FragColor;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vec2 coord = gl_PointCoord - vec2(0.5);
|
||||||
|
float dist = length(coord);
|
||||||
|
if (dist > 0.5) discard;
|
||||||
|
// Soft dust cloud with brownish/tan color
|
||||||
|
float alpha = smoothstep(0.5, 0.0, dist) * vAlpha;
|
||||||
|
vec3 dustColor = vec3(0.7, 0.65, 0.55); // Tan/brown dust
|
||||||
|
FragColor = vec4(dustColor, alpha * 0.4); // Semi-transparent
|
||||||
|
}
|
||||||
|
)";
|
||||||
|
|
||||||
|
if (!shader->loadFromSource(dustVS, dustFS)) {
|
||||||
|
LOG_ERROR("Failed to create mount dust shader");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create VAO/VBO
|
||||||
|
glGenVertexArrays(1, &vao);
|
||||||
|
glGenBuffers(1, &vbo);
|
||||||
|
|
||||||
|
glBindVertexArray(vao);
|
||||||
|
glBindBuffer(GL_ARRAY_BUFFER, vbo);
|
||||||
|
|
||||||
|
// Position (vec3) + Size (float) + Alpha (float) = 5 floats per vertex
|
||||||
|
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)0);
|
||||||
|
glEnableVertexAttribArray(0);
|
||||||
|
|
||||||
|
glVertexAttribPointer(1, 1, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)(3 * sizeof(float)));
|
||||||
|
glEnableVertexAttribArray(1);
|
||||||
|
|
||||||
|
glVertexAttribPointer(2, 1, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)(4 * sizeof(float)));
|
||||||
|
glEnableVertexAttribArray(2);
|
||||||
|
|
||||||
|
glBindVertexArray(0);
|
||||||
|
|
||||||
|
particles.reserve(MAX_DUST_PARTICLES);
|
||||||
|
vertexData.reserve(MAX_DUST_PARTICLES * 5);
|
||||||
|
|
||||||
|
LOG_INFO("Mount dust effects initialized");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MountDust::shutdown() {
|
||||||
|
if (vao) glDeleteVertexArrays(1, &vao);
|
||||||
|
if (vbo) glDeleteBuffers(1, &vbo);
|
||||||
|
vao = 0;
|
||||||
|
vbo = 0;
|
||||||
|
particles.clear();
|
||||||
|
shader.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MountDust::spawnDust(const glm::vec3& position, const glm::vec3& velocity, bool isMoving) {
|
||||||
|
if (!isMoving) {
|
||||||
|
spawnAccum = 0.0f;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Spawn rate based on speed
|
||||||
|
float speed = glm::length(velocity);
|
||||||
|
if (speed < 0.1f) return;
|
||||||
|
|
||||||
|
// Spawn dust particles at a rate proportional to speed
|
||||||
|
float spawnRate = speed * 8.0f; // More dust at higher speeds
|
||||||
|
spawnAccum += spawnRate * 0.016f; // Assume ~60 FPS
|
||||||
|
|
||||||
|
while (spawnAccum >= 1.0f && particles.size() < MAX_DUST_PARTICLES) {
|
||||||
|
spawnAccum -= 1.0f;
|
||||||
|
|
||||||
|
Particle p;
|
||||||
|
// Spawn slightly behind and to the sides of the mount
|
||||||
|
p.position = position + glm::vec3(
|
||||||
|
randFloat(-0.3f, 0.3f),
|
||||||
|
randFloat(-0.3f, 0.3f),
|
||||||
|
randFloat(-0.1f, 0.1f)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Dust rises up and drifts backward slightly
|
||||||
|
p.velocity = glm::vec3(
|
||||||
|
randFloat(-0.2f, 0.2f),
|
||||||
|
randFloat(-0.2f, 0.2f),
|
||||||
|
randFloat(0.5f, 1.2f) // Rise upward
|
||||||
|
) - velocity * 0.2f; // Drift backward relative to movement
|
||||||
|
|
||||||
|
p.lifetime = 0.0f;
|
||||||
|
p.maxLifetime = randFloat(0.4f, 0.8f);
|
||||||
|
p.size = randFloat(8.0f, 16.0f);
|
||||||
|
p.alpha = 1.0f;
|
||||||
|
|
||||||
|
particles.push_back(p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MountDust::update(float deltaTime) {
|
||||||
|
// Update existing particles
|
||||||
|
for (auto it = particles.begin(); it != particles.end(); ) {
|
||||||
|
it->lifetime += deltaTime;
|
||||||
|
|
||||||
|
if (it->lifetime >= it->maxLifetime) {
|
||||||
|
it = particles.erase(it);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update position
|
||||||
|
it->position += it->velocity * deltaTime;
|
||||||
|
|
||||||
|
// Slow down velocity (friction)
|
||||||
|
it->velocity *= 0.96f;
|
||||||
|
|
||||||
|
// Fade out
|
||||||
|
float t = it->lifetime / it->maxLifetime;
|
||||||
|
it->alpha = 1.0f - t;
|
||||||
|
|
||||||
|
// Grow slightly as they fade
|
||||||
|
it->size += deltaTime * 12.0f;
|
||||||
|
|
||||||
|
++it;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MountDust::render(const Camera& camera) {
|
||||||
|
if (particles.empty() || !shader) return;
|
||||||
|
|
||||||
|
// Build vertex data
|
||||||
|
vertexData.clear();
|
||||||
|
for (const auto& p : particles) {
|
||||||
|
vertexData.push_back(p.position.x);
|
||||||
|
vertexData.push_back(p.position.y);
|
||||||
|
vertexData.push_back(p.position.z);
|
||||||
|
vertexData.push_back(p.size);
|
||||||
|
vertexData.push_back(p.alpha);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Upload to GPU
|
||||||
|
glBindBuffer(GL_ARRAY_BUFFER, vbo);
|
||||||
|
glBufferData(GL_ARRAY_BUFFER, vertexData.size() * sizeof(float), vertexData.data(), GL_DYNAMIC_DRAW);
|
||||||
|
|
||||||
|
// Render
|
||||||
|
shader->use();
|
||||||
|
shader->setUniform("uView", camera.getViewMatrix());
|
||||||
|
shader->setUniform("uProjection", camera.getProjectionMatrix());
|
||||||
|
|
||||||
|
glEnable(GL_BLEND);
|
||||||
|
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||||
|
glDepthMask(GL_FALSE); // Don't write to depth buffer
|
||||||
|
glEnable(GL_PROGRAM_POINT_SIZE);
|
||||||
|
|
||||||
|
glBindVertexArray(vao);
|
||||||
|
glDrawArrays(GL_POINTS, 0, static_cast<GLsizei>(particles.size()));
|
||||||
|
glBindVertexArray(0);
|
||||||
|
|
||||||
|
glDepthMask(GL_TRUE);
|
||||||
|
glDisable(GL_PROGRAM_POINT_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace rendering
|
||||||
|
} // namespace wowee
|
||||||
|
|
@ -13,6 +13,7 @@
|
||||||
#include "rendering/lens_flare.hpp"
|
#include "rendering/lens_flare.hpp"
|
||||||
#include "rendering/weather.hpp"
|
#include "rendering/weather.hpp"
|
||||||
#include "rendering/swim_effects.hpp"
|
#include "rendering/swim_effects.hpp"
|
||||||
|
#include "rendering/mount_dust.hpp"
|
||||||
#include "rendering/character_renderer.hpp"
|
#include "rendering/character_renderer.hpp"
|
||||||
#include "rendering/wmo_renderer.hpp"
|
#include "rendering/wmo_renderer.hpp"
|
||||||
#include "rendering/m2_renderer.hpp"
|
#include "rendering/m2_renderer.hpp"
|
||||||
|
|
@ -295,6 +296,13 @@ bool Renderer::initialize(core::Window* win) {
|
||||||
swimEffects.reset();
|
swimEffects.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create mount dust effects
|
||||||
|
mountDust = std::make_unique<MountDust>();
|
||||||
|
if (!mountDust->initialize()) {
|
||||||
|
LOG_WARNING("Failed to initialize mount dust effects");
|
||||||
|
mountDust.reset();
|
||||||
|
}
|
||||||
|
|
||||||
// Create character renderer
|
// Create character renderer
|
||||||
characterRenderer = std::make_unique<CharacterRenderer>();
|
characterRenderer = std::make_unique<CharacterRenderer>();
|
||||||
if (!characterRenderer->initialize()) {
|
if (!characterRenderer->initialize()) {
|
||||||
|
|
@ -1251,6 +1259,29 @@ void Renderer::update(float deltaTime) {
|
||||||
swimEffects->update(*camera, *cameraController, *waterRenderer, deltaTime);
|
swimEffects->update(*camera, *cameraController, *waterRenderer, deltaTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update mount dust effects
|
||||||
|
if (mountDust) {
|
||||||
|
mountDust->update(deltaTime);
|
||||||
|
|
||||||
|
// Spawn dust when mounted and moving on ground
|
||||||
|
if (isMounted() && cameraController && !taxiFlight_) {
|
||||||
|
bool isMoving = cameraController->isMoving();
|
||||||
|
bool onGround = cameraController->isGrounded();
|
||||||
|
|
||||||
|
if (isMoving && onGround) {
|
||||||
|
// Calculate velocity from camera direction and speed
|
||||||
|
glm::vec3 forward = camera->getForward();
|
||||||
|
float speed = cameraController->getMovementSpeed();
|
||||||
|
glm::vec3 velocity = forward * speed;
|
||||||
|
velocity.z = 0.0f; // Ignore vertical component
|
||||||
|
|
||||||
|
// Spawn dust at mount's feet (slightly below character position)
|
||||||
|
glm::vec3 dustPos = characterPosition - glm::vec3(0.0f, 0.0f, mountHeightOffset_ * 0.8f);
|
||||||
|
mountDust->spawnDust(dustPos, velocity, isMoving);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Update character animations
|
// Update character animations
|
||||||
if (characterRenderer) {
|
if (characterRenderer) {
|
||||||
characterRenderer->update(deltaTime);
|
characterRenderer->update(deltaTime);
|
||||||
|
|
@ -1650,6 +1681,11 @@ void Renderer::renderWorld(game::World* world) {
|
||||||
swimEffects->render(*camera);
|
swimEffects->render(*camera);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Render mount dust effects
|
||||||
|
if (mountDust && camera) {
|
||||||
|
mountDust->render(*camera);
|
||||||
|
}
|
||||||
|
|
||||||
// Compute view/projection once for all sub-renderers
|
// Compute view/projection once for all sub-renderers
|
||||||
const glm::mat4& view = camera ? camera->getViewMatrix() : glm::mat4(1.0f);
|
const glm::mat4& view = camera ? camera->getViewMatrix() : glm::mat4(1.0f);
|
||||||
const glm::mat4& projection = camera ? camera->getProjectionMatrix() : glm::mat4(1.0f);
|
const glm::mat4& projection = camera ? camera->getProjectionMatrix() : glm::mat4(1.0f);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue