GRK_Project/cw 9/Particle.cpp
2023-02-09 14:18:46 +01:00

380 lines
11 KiB
C++

#define FOURCC_DXT1 0x31545844 // Equivalent to "DXT1" in ASCII
#define FOURCC_DXT3 0x33545844 // Equivalent to "DXT3" in ASCII
#define FOURCC_DXT5 0x35545844 // Equivalent to "DXT5" in ASCII
#include "glew.h"
#include "freeglut.h"
#include "glm.hpp"
#include "../glm/gtx/norm.hpp"
#include <algorithm>
#include <iostream>
#include <cmath>
#include <vector>
#include "Particle.h"
GLuint billboard_vertex_buffer;
GLuint particles_position_buffer;
GLuint particles_color_buffer;
GLuint particleTexture;
static GLfloat* g_particule_position_size_data;
static GLubyte* g_particule_color_data;
struct ParticleSource {
glm::vec3 pos;
double amount;
float spread;
};
std::vector<ParticleSource> particleSources;
int ParticlesCount = 0;
double lastTime = glutGet(GLUT_ELAPSED_TIME) / 1000.0f;
// CPU representation of a particle
struct Particle {
glm::vec3 pos, speed;
unsigned char r, g, b, a;
float size, angle, weight;
float life;
float cameradistance;
bool operator<(const Particle& that) const {
return this->cameradistance > that.cameradistance;
}
};
void Core::Particle::addParticleSource(glm::vec3 pos, double amount, float spread) {
ParticleSource particleSource;
particleSource.pos = pos;
particleSource.amount = amount;
particleSource.spread = spread;
particleSources.push_back(particleSource);
}
const int MaxParticles = 100000;
Particle ParticlesContainer[MaxParticles];
int LastUsedParticle = 0;
// Finds a Particle in ParticlesContainer which isn't used yet.
// (i.e. life < 0);
int FindUnusedParticle() {
for (int i = LastUsedParticle; i < MaxParticles; i++) {
if (ParticlesContainer[i].life < 0) {
LastUsedParticle = i;
return i;
}
}
for (int i = 0; i < LastUsedParticle; i++) {
if (ParticlesContainer[i].life < 0) {
LastUsedParticle = i;
return i;
}
}
return 0;
}
void SortParticles() {
std::sort(&ParticlesContainer[0], &ParticlesContainer[MaxParticles]);
}
GLuint loadDDS(const char* imagepath)
{
unsigned char header[124];
FILE* fp;
/* try to open the file */
fp = fopen(imagepath, "rb");
if (fp == NULL) {
printf("%s could not be opened. Are you in the right directory ? Don't forget to read the FAQ !\n", imagepath); getchar();
return 0;
}
/* verify the type of file */
char filecode[4];
fread(filecode, 1, 4, fp);
if (strncmp(filecode, "DDS ", 4) != 0) {
fclose(fp);
return 0;
}
/* get the surface desc */
fread(&header, 124, 1, fp);
unsigned int height = *(unsigned int*)&(header[8]);
unsigned int width = *(unsigned int*)&(header[12]);
unsigned int linearSize = *(unsigned int*)&(header[16]);
unsigned int mipMapCount = *(unsigned int*)&(header[24]);
unsigned int fourCC = *(unsigned int*)&(header[80]);
unsigned char* buffer;
unsigned int bufsize;
/* how big is it going to be including all mipmaps? */
bufsize = mipMapCount > 1 ? linearSize * 2 : linearSize;
buffer = (unsigned char*)malloc(bufsize * sizeof(unsigned char));
fread(buffer, 1, bufsize, fp);
/* close the file pointer */
fclose(fp);
unsigned int components = (fourCC == FOURCC_DXT1) ? 3 : 4;
unsigned int format;
switch (fourCC)
{
case FOURCC_DXT1:
format = GL_COMPRESSED_RGBA_S3TC_DXT1_EXT;
break;
case FOURCC_DXT3:
format = GL_COMPRESSED_RGBA_S3TC_DXT3_EXT;
break;
case FOURCC_DXT5:
format = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT;
break;
default:
free(buffer);
return 0;
}
// Create one OpenGL texture
GLuint textureID;
glGenTextures(1, &textureID);
// "Bind" the newly created texture : all future texture functions will modify this texture
glBindTexture(GL_TEXTURE_2D, textureID);
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
unsigned int blockSize = (format == GL_COMPRESSED_RGBA_S3TC_DXT1_EXT) ? 8 : 16;
unsigned int offset = 0;
/* load the mipmaps */
for (unsigned int level = 0; level < mipMapCount && (width || height); ++level)
{
unsigned int size = ((width + 3) / 4) * ((height + 3) / 4) * blockSize;
glCompressedTexImage2D(GL_TEXTURE_2D, level, format, width, height,
0, size, buffer + offset);
offset += size;
width /= 2;
height /= 2;
// Deal with Non-Power-Of-Two textures. This code is not included in the webpage to reduce clutter.
if (width < 1) width = 1;
if (height < 1) height = 1;
}
free(buffer);
return textureID;
}
void Core::Particle::initParticles() {
particleTexture = loadDDS("textures/particle.dds");
g_particule_position_size_data = new GLfloat[MaxParticles * 4];
g_particule_color_data = new GLubyte[MaxParticles * 4];
for (int i = 0; i < MaxParticles; i++) {
ParticlesContainer[i].life = -1.0f;
ParticlesContainer[i].cameradistance = -1.0f;
}
// The VBO containing the 4 vertices of the particles.
// Thanks to instancing, they will be shared by all particles.
static const GLfloat g_vertex_buffer_data[] = {
-0.5f, -0.5f, 0.0f,
0.5f, -0.5f, 0.0f,
-0.5f, 0.5f, 0.0f,
0.5f, 0.5f, 0.0f,
};
glGenBuffers(1, &billboard_vertex_buffer);
glBindBuffer(GL_ARRAY_BUFFER, billboard_vertex_buffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(g_vertex_buffer_data), g_vertex_buffer_data, GL_STATIC_DRAW);
// The VBO containing the positions and sizes of the particles
glGenBuffers(1, &particles_position_buffer);
glBindBuffer(GL_ARRAY_BUFFER, particles_position_buffer);
// Initialize with empty (NULL) buffer : it will be updated later, each frame.
glBufferData(GL_ARRAY_BUFFER, MaxParticles * 4 * sizeof(GLfloat), NULL, GL_STREAM_DRAW);
// The VBO containing the colors of the particles
glGenBuffers(1, &particles_color_buffer);
glBindBuffer(GL_ARRAY_BUFFER, particles_color_buffer);
// Initialize with empty (NULL) buffer : it will be updated later, each frame.
glBufferData(GL_ARRAY_BUFFER, MaxParticles * 4 * sizeof(GLubyte), NULL, GL_STREAM_DRAW);
}
void spawnParticles(double deltaTime, glm::vec3 sourcePosition, double amount, float spread) {
int newparticles = (int)(deltaTime * amount);
if (newparticles > (int)(0.016f * amount))
newparticles = (int)(0.016f * amount);
for (int i = 0; i < newparticles; i++) {
int particleIndex = FindUnusedParticle();
ParticlesContainer[particleIndex].life = 20.0f;
ParticlesContainer[particleIndex].pos = sourcePosition;
glm::vec3 maindir = glm::vec3(0.0f, 0.4f, 0.0f);
glm::vec3 randomdir = glm::vec3(
(rand() % 2000 - 1000.0f) / 1000.0f,
(rand() % 2000 - 1000.0f) / 1000.0f,
(rand() % 2000 - 1000.0f) / 1000.0f
);
ParticlesContainer[particleIndex].speed = maindir + randomdir * spread;
ParticlesContainer[particleIndex].r = 200;
ParticlesContainer[particleIndex].g = 200;
ParticlesContainer[particleIndex].b = 255;
ParticlesContainer[particleIndex].a = (rand() % 32) / 3 + 100;
ParticlesContainer[particleIndex].size = (rand() % 1000) / 100000.0f + 0.05f;
}
}
void simulateParticles(glm::vec3 cameraPos, double deltaTime) {
ParticlesCount = 0;
for (int i = 0; i < MaxParticles; i++) {
Particle& p = ParticlesContainer[i];
if (p.life > 0.0f) {
// Decrease life
p.life -= deltaTime;
if (p.life > 0.0f) {
// Simulate simple physics : gravity only, no collisions
// decreased acceleration - bubbles were too fast
p.speed += glm::vec3(0.0f, 1.0f, 0.0f) * (float)deltaTime * 0.5f;
p.pos += p.speed * (float)deltaTime;
p.cameradistance = glm::length2(p.pos - cameraPos);
// Fill the GPU buffer
g_particule_position_size_data[4 * ParticlesCount + 0] = p.pos.x;
g_particule_position_size_data[4 * ParticlesCount + 1] = p.pos.y;
g_particule_position_size_data[4 * ParticlesCount + 2] = p.pos.z;
g_particule_position_size_data[4 * ParticlesCount + 3] = p.size;
g_particule_color_data[4 * ParticlesCount + 0] = p.r;
g_particule_color_data[4 * ParticlesCount + 1] = p.g;
g_particule_color_data[4 * ParticlesCount + 2] = p.b;
g_particule_color_data[4 * ParticlesCount + 3] = p.a;
}
else {
p.cameradistance = -1.0f;
}
ParticlesCount++;
}
}
SortParticles();
}
void updateParticles() {
glBindBuffer(GL_ARRAY_BUFFER, particles_position_buffer);
glBufferData(GL_ARRAY_BUFFER, MaxParticles * 4 * sizeof(GLfloat), NULL, GL_STREAM_DRAW);
glBufferSubData(GL_ARRAY_BUFFER, 0, ParticlesCount * sizeof(GLfloat) * 4, g_particule_position_size_data);
glBindBuffer(GL_ARRAY_BUFFER, particles_color_buffer);
glBufferData(GL_ARRAY_BUFFER, MaxParticles * 4 * sizeof(GLubyte), NULL, GL_STREAM_DRAW);
glBufferSubData(GL_ARRAY_BUFFER, 0, ParticlesCount * sizeof(GLubyte) * 4, g_particule_color_data);
}
void bindParticles(GLuint programParticles, glm::vec3 cameraSide, glm::vec3 cameraVertical, glm::mat4 cameraMatrix, glm::mat4 perspectiveMatrix) {
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glUseProgram(programParticles);
// Vertex shader
GLuint CameraRight_worldspace_ID = glGetUniformLocation(programParticles, "CameraRight_worldspace");
GLuint CameraUp_worldspace_ID = glGetUniformLocation(programParticles, "CameraUp_worldspace");
GLuint ViewProjMatrixID = glGetUniformLocation(programParticles, "VP");
// fragment shader
GLuint TextureID = glGetUniformLocation(programParticles, "myTextureSampler");
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, particleTexture);
glUniform1i(TextureID, 0);
glUniform3f(CameraRight_worldspace_ID, cameraSide.x, cameraSide.y, cameraSide.z);
glUniform3f(CameraUp_worldspace_ID, cameraVertical.x, cameraVertical.y, cameraVertical.z);
glm::mat4 transformation = perspectiveMatrix * cameraMatrix;
glUniformMatrix4fv(ViewProjMatrixID, 1, GL_FALSE, (float*)&transformation);
// vertices
glEnableVertexAttribArray(0);
glBindBuffer(GL_ARRAY_BUFFER, billboard_vertex_buffer);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, (void*)0);
// positions of particles' centers
glEnableVertexAttribArray(1);
glBindBuffer(GL_ARRAY_BUFFER, particles_position_buffer);
glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, 0, (void*)0);
// particles' colors
glEnableVertexAttribArray(2);
glBindBuffer(GL_ARRAY_BUFFER, particles_color_buffer);
glVertexAttribPointer(2, 4, GL_UNSIGNED_BYTE, GL_TRUE, 0, (void*)0);
}
void renderParticles() {
glDrawArraysInstanced(GL_TRIANGLE_STRIP, 0, 4, ParticlesCount);
glVertexAttribDivisor(0, 0); // particles vertices : always reuse the same 4 vertices -> 0
glVertexAttribDivisor(1, 1); // positions : one per quad (its center) -> 1
glVertexAttribDivisor(2, 1); // color : one per quad -> 1
glDrawArraysInstanced(GL_TRIANGLE_STRIP, 0, 4, ParticlesCount);
glDisableVertexAttribArray(0);
glDisableVertexAttribArray(1);
glDisableVertexAttribArray(2);
}
void Core::Particle::handleAllParticleSources(glm::vec3 cameraPos, GLuint programParticles, glm::vec3 cameraSide, glm::vec3 cameraVertical, glm::mat4 cameraMatrix, glm::mat4 perspectiveMatrix) {
double currentTime = glutGet(GLUT_ELAPSED_TIME) / 1000.0f;
double delta = currentTime - lastTime;
lastTime = currentTime;
for (ParticleSource particleSource : particleSources)
{
spawnParticles(delta, particleSource.pos, particleSource.amount, particleSource.spread);
}
if ((int)currentTime % 1 == 0) {
particleSources.clear();
}
simulateParticles(cameraPos, delta);
updateParticles();
bindParticles(programParticles, cameraSide, cameraVertical, cameraMatrix, perspectiveMatrix);
renderParticles();
}
void Core::Particle::shutdownParticles() {
delete[] g_particule_position_size_data;
glDeleteBuffers(1, &particles_color_buffer);
glDeleteBuffers(1, &particles_position_buffer);
glDeleteBuffers(1, &billboard_vertex_buffer);
}