Snowstorm/src/snowstorm.c

569 lines
15 KiB
C

/* Snowstorm - Particle effects in C
*
* Copyright (c) 2021 Evan Hemsley
*
* This software is provided 'as-is', without any express or implied warranty.
* In no event will the authors be held liable for any damages arising from
* the use of this software.
*
* Permission is granted to anyone to use this software for any purpose,
* including commercial applications, and to alter it and redistribute it
* freely, subject to the following restrictions:
*
* 1. The origin of this software must not be misrepresented; you must not
* claim that you wrote the original software. If you use this software in a
* product, an acknowledgment in the product documentation would be
* appreciated but is not required.
*
* 2. Altered source versions must be plainly marked as such, and must not be
* misrepresented as being the original software.
*
* 3. This notice may not be removed or altered from any source distribution.
*
* Evan "cosmonaut" Hemsley <evan@moonside.games>
*
*/
#include "snowstorm.h"
#include <stdlib.h>
#include <string.h>
#include <time.h>
#define PI 3.14159265358979323846
typedef struct Snowstorm_Color
{
uint8_t r;
uint8_t g;
uint8_t b;
uint8_t a;
} Snowstorm_Color;
typedef struct Snowstorm_UV
{
float left;
float top;
float right;
float bottom;
} Snowstorm_UV;
typedef struct Snowstorm_Vector2
{
float x;
float y;
} Snowstorm_Vector2;
static inline Snowstorm_Vector2 Vector2_Rotate(Snowstorm_Vector2 vector, float angle)
{
Snowstorm_Vector2 rotated;
rotated.x = vector.x * cosf(angle) - vector.y * sinf(angle);
rotated.y = vector.x * sinf(angle) + vector.y * cosf(angle);
return rotated;
}
static inline float Vector2_Magnitude(Snowstorm_Vector2 vector)
{
return sqrtf(vector.x * vector.x + vector.y * vector.y);
}
static inline Snowstorm_Vector2 Vector2_Normalize(Snowstorm_Vector2 vector)
{
float length = Vector2_Magnitude(vector);
Snowstorm_Vector2 normalized;
normalized.x = vector.x / length;
normalized.y = vector.y / length;
return normalized;
}
typedef struct Snowstorm_Matrix3x2
{
float M11;
float M12;
float M21;
float M22;
float M31;
float M32;
} Snowstorm_Matrix3x2;
static inline Snowstorm_Matrix3x2 Matrix3x2_CreateTranslation(float x, float y)
{
Snowstorm_Matrix3x2 result;
result.M11 = 1;
result.M12 = 0;
result.M21 = 0;
result.M22 = 1;
result.M31 = x;
result.M32 = y;
return result;
}
static inline Snowstorm_Matrix3x2 Matrix3x2_CreateScale(float x, float y)
{
Snowstorm_Matrix3x2 result;
result.M11 = x;
result.M12 = 0;
result.M21 = 0;
result.M22 = y;
result.M31 = 0;
result.M32 = 0;
return result;
}
static Snowstorm_Matrix3x2 Matrix3x2_CreateRotation(float radians)
{
Snowstorm_Matrix3x2 result;
float c, s;
float epsilon = 0.001f * PI / 180; /* 0.1 % of a degree */
radians = fmodf(radians, PI * 2);
if (radians > -epsilon && radians < epsilon)
{
/* Exact case for zero rotation. */
c = 1;
s = 0;
}
else if (radians > PI / 2 - epsilon && radians < PI / 2 + epsilon)
{
/* Exact case for 90 degree rotation. */
c = 0;
s = 1;
}
else if (radians < -PI + epsilon && radians > PI - epsilon)
{
/* Exact case for 180 degree rotation. */
c = -1;
s = 0;
}
else if (radians > -PI / 2 - epsilon && radians < -PI / 2 + epsilon)
{
/* Exact case for 270 degree rotation. */
c = 0;
s = -1;
}
else
{
/* Arbitrary rotation. */
c = cosf(radians);
s = sinf(radians);
}
/*
* [ c s ]
* [ -s c ]
* [ 0 0 ]
*/
result.M11 = c;
result.M12 = s;
result.M21 = -s;
result.M22 = c;
result.M31 = 0;
result.M32 = 0;
return result;
}
static inline Snowstorm_Matrix3x2 Matrix3x2_Multiply(Snowstorm_Matrix3x2 m1, Snowstorm_Matrix3x2 m2)
{
Snowstorm_Matrix3x2 result;
/* First row */
result.M11 = m1.M11 * m2.M11 + m1.M12 * m2.M21;
result.M12 = m1.M11 * m2.M12 + m1.M12 * m2.M22;
/* Second row */
result.M21 = m1.M21 * m2.M11 + m1.M22 * m2.M21;
result.M22 = m1.M21 * m2.M12 + m1.M22 * m2.M22;
/* Third row */
result.M31 = m1.M31 * m2.M11 + m1.M32 * m2.M21 + m2.M31;
result.M32 = m1.M31 * m2.M12 + m1.M32 * m2.M22 + m2.M32;
return result;
}
static inline Snowstorm_Vector2 Vector2_Transform(Snowstorm_Vector2 vector, Snowstorm_Matrix3x2 matrix)
{
Snowstorm_Vector2 result;
result.x = (vector.x * matrix.M11) + (vector.y * matrix.M21) + matrix.M31;
result.y = (vector.x * matrix.M12) + (vector.y * matrix.M22) + matrix.M32;
return result;
}
typedef struct Snowstorm_Particle
{
float time;
float timeRate;
Snowstorm_Vector2 position;
Snowstorm_Vector2 velocity;
Snowstorm_Vector2 scale;
float size;
float rotation;
float spin;
float directionVariance;
float speedVariance;
Snowstorm_Vector2 catchup;
uint32_t uvIndex;
} Snowstorm_Particle;
typedef struct Snowstorm_Context
{
Snowstorm_Particle* particles;
uint32_t particleCount;
uint32_t particleCapacity;
Snowstorm_Vector2 wind;
float particleSize;
float leftBound;
float topBound;
float rightBound;
float bottomBound;
Snowstorm_UV* uvs;
uint32_t uvCount;
uint8_t* currentBufferAddress; /* GM doesnt let you pass more than 4 arguments with different types lol */
} Snowstorm_Context;
static inline float Approach(float value, float target, float maxChange)
{
return value + min(max(target - value, -maxChange), maxChange);
}
static inline float WrapWithOvershoot(float value, float min, float max)
{
if (value < min)
{
value = max - (min - value);
}
if (value > max)
{
value = min + (value - max);
}
return value;
}
static inline float Random(float a)
{
return (float)rand() / (float)(RAND_MAX / a);
}
static inline float RandomRange(float min, float max)
{
return Random(max - min) + min;
}
/* i wanted to use pointers but game maker mangles string to pointer casts. oh well */
static Snowstorm_Context *contexts = NULL;
static uint32_t contextCount = 0;
void Snowstorm_Init()
{
srand((unsigned int)time(NULL));
}
/* FIXME: we should reuse IDs when contexts are destroyed */
double Snowstorm_Create(double particleSize, double leftBound, double topBound, double rightBound, double bottomBound)
{
contexts = realloc(contexts, sizeof(Snowstorm_Context) * (contextCount + 1));
contextCount += 1;
Snowstorm_Context* context = &contexts[contextCount - 1];
context->particleCapacity = 128;
context->particles = malloc(sizeof(Snowstorm_Particle) * context->particleCapacity);
context->particleCount = 0;
context->particleSize = particleSize;
context->leftBound = -10;
context->rightBound = 490;
context->topBound = -10;
context->bottomBound = 280;
context->wind.x = 0;
context->wind.y = 0;
context->uvs = NULL;
context->uvCount = 0;
context->currentBufferAddress = NULL;
return (double)(contextCount - 1);
}
void Snowstorm_SetUVCount(double contextId, double count)
{
Snowstorm_Context* context = &contexts[(uint32_t)contextId];
context->uvCount = (uint32_t)count;
context->uvs = realloc(context->uvs, sizeof(Snowstorm_UV) * context->uvCount);
}
void Snowstorm_SetLeftTopUV(double contextId, double index, double left, double top)
{
Snowstorm_Context* context = &contexts[(uint32_t)contextId];
context->uvs[(uint32_t)index].left = left;
context->uvs[(uint32_t)index].top = top;
}
void Snowstorm_SetRightBottomUV(double contextId, double index, double right, double bottom)
{
Snowstorm_Context* context = &contexts[(uint32_t)contextId];
context->uvs[(uint32_t)index].right = right;
context->uvs[(uint32_t)index].bottom = bottom;
}
void Snowstorm_Update(double contextId)
{
uint32_t i;
Snowstorm_Context* context = &contexts[(uint32_t)contextId];
for (i = 0; i < context->particleCount; i += 1)
{
Snowstorm_Particle *particle = &context->particles[i];
particle->time += particle->timeRate;
float speed = Vector2_Magnitude(particle->velocity);
float rotation = sinf(particle->time / (speed * 100)) * particle->directionVariance * (PI / 180.0f); // degrees to radians
float windSpeed = Vector2_Magnitude(context->wind);
Snowstorm_Vector2 wind = Vector2_Normalize(context->wind);
wind.x *= (windSpeed + particle->speedVariance);
wind.y *= (windSpeed + particle->speedVariance);
wind = Vector2_Rotate(wind, rotation);
particle->velocity.x = Approach(particle->velocity.x, wind.x, particle->catchup.x);
particle->velocity.y = Approach(particle->velocity.y, wind.y, particle->catchup.y);
particle->position.x = WrapWithOvershoot(particle->position.x + particle->velocity.x, context->leftBound, context->rightBound);
particle->position.y = WrapWithOvershoot(particle->position.y + particle->velocity.y, context->topBound, context->bottomBound);
particle->scale.y = particle->size * sinf(particle->time / 10);
particle->rotation += particle->spin;
}
}
void Snowstorm_ApplyChaos(double contextId, double directionChaos, double speedChaos)
{
uint32_t i;
Snowstorm_Context* context = &contexts[(uint32_t)contextId];
for (i = 0; i < context->particleCount; i += 1)
{
Snowstorm_Particle* particle = &context->particles[i];
particle->directionVariance = RandomRange(-directionChaos, directionChaos);
particle->speedVariance = RandomRange(-speedChaos, speedChaos);
}
}
void Snowstorm_ApplyWind(double contextId, double xSpeed, double ySpeed)
{
Snowstorm_Context* context = &contexts[(uint32_t)contextId];
context->wind.x = (float)xSpeed;
context->wind.y = (float)ySpeed;
}
void Snowstorm_SetParticles(double contextId, double count)
{
uint32_t i;
Snowstorm_Context* context = &contexts[(uint32_t)contextId];
if (count > context->particleCapacity)
{
context->particleCapacity *= 2;
context->particles = realloc(context->particles, sizeof(Snowstorm_Particle) * context->particleCapacity);
}
for (i = 0; i < count; i += 1)
{
Snowstorm_Particle* particle = &context->particles[i];
particle->time = 0;
particle->timeRate = 0.7f + RandomRange(-0.3f, 0.3f);
particle->position.x = RandomRange(context->leftBound, context->rightBound);
particle->position.y = RandomRange(context->topBound, context->bottomBound);
particle->directionVariance = 0;
particle->speedVariance = 0;
particle->catchup.x = RandomRange(0.2f, 1);
particle->catchup.y = RandomRange(0.2f, 1);
particle->velocity.x = context->wind.x;
particle->velocity.y = context->wind.y;
particle->size = 0.4f;
particle->scale.x = particle->size;
particle->scale.y = particle->size;
particle->spin = RandomRange(-0.1745329f, 0.1745329f); /* 10 degrees */
particle->rotation = 0;
particle->uvIndex = rand() % context->uvCount;
}
context->particleCount = count;
}
void Snowstorm_SetBufferAddress(double contextId, const char* bufferAddress)
{
Snowstorm_Context* context = &contexts[(uint32_t)contextId];
context->currentBufferAddress = (uint8_t*)bufferAddress;
}
double Snowstorm_RequiredSnowBufferSize(double contextId)
{
Snowstorm_Context* context = &contexts[(uint32_t)contextId];
return (double)context->particleCount * 6 * (sizeof(Snowstorm_Vector2) + sizeof(Snowstorm_Color) + (2 * sizeof(float)));
}
double Snowstorm_FillSnowBuffer(double contextId, double red, double green, double blue)
{
uint32_t i, vertexCount;
Snowstorm_Context* context = &contexts[(uint32_t)contextId];
uint8_t* bufferAddress = context->currentBufferAddress;
Snowstorm_Color color;
color.r = (uint8_t)red;
color.g = (uint8_t)green;
color.b = (uint8_t)blue;
color.a = 255;
vertexCount = 0;
for (i = 0; i < context->particleCount; i += 1)
{
Snowstorm_Particle* particle = &context->particles[i];
Snowstorm_Vector2 leftTop;
Snowstorm_Vector2 rightTop;
Snowstorm_Vector2 leftBottom;
Snowstorm_Vector2 rightBottom;
leftTop.x = context->particleSize * -particle->scale.x / 4;
leftTop.y = context->particleSize * -particle->scale.y / 4;
rightTop.x = context->particleSize * particle->scale.x / 4;
rightTop.y = context->particleSize * -particle->scale.y / 4;
leftBottom.x = context->particleSize * -particle->scale.x / 4;
leftBottom.y = context->particleSize * particle->scale.y / 4;
rightBottom.x = context->particleSize * particle->scale.x / 4;
rightBottom.y = context->particleSize * particle->scale.y / 4;
Snowstorm_Matrix3x2 translation = Matrix3x2_CreateTranslation(particle->position.x, particle->position.y);
Snowstorm_Matrix3x2 rotation = Matrix3x2_CreateRotation(particle->rotation);
Snowstorm_Matrix3x2 transform = Matrix3x2_Multiply(rotation, translation);
leftTop = Vector2_Transform(leftTop, transform);
rightTop = Vector2_Transform(rightTop, transform);
leftBottom = Vector2_Transform(leftBottom, transform);
rightBottom = Vector2_Transform(rightBottom, transform);
float leftUV = context->uvs[particle->uvIndex].left;
float topUV = context->uvs[particle->uvIndex].top;
float rightUV = context->uvs[particle->uvIndex].right;
float bottomUV = context->uvs[particle->uvIndex].bottom;
memcpy(bufferAddress, &leftTop, sizeof(Snowstorm_Vector2));
bufferAddress += sizeof(Snowstorm_Vector2);
memcpy(bufferAddress, &color, sizeof(Snowstorm_Color));
bufferAddress += sizeof(Snowstorm_Color);
memcpy(bufferAddress, &leftUV, sizeof(float));
bufferAddress += sizeof(float);
memcpy(bufferAddress, &topUV, sizeof(float));
bufferAddress += sizeof(float);
memcpy(bufferAddress, &rightTop, sizeof(Snowstorm_Vector2));
bufferAddress += sizeof(Snowstorm_Vector2);
memcpy(bufferAddress, &color, sizeof(Snowstorm_Color));
bufferAddress += sizeof(Snowstorm_Color);
memcpy(bufferAddress, &rightUV, sizeof(float));
bufferAddress += sizeof(float);
memcpy(bufferAddress, &topUV, sizeof(float));
bufferAddress += sizeof(float);
memcpy(bufferAddress, &leftBottom, sizeof(Snowstorm_Vector2));
bufferAddress += sizeof(Snowstorm_Vector2);
memcpy(bufferAddress, &color, sizeof(Snowstorm_Color));
bufferAddress += sizeof(Snowstorm_Color);
memcpy(bufferAddress, &leftUV, sizeof(float));
bufferAddress += sizeof(float);
memcpy(bufferAddress, &bottomUV, sizeof(float));
bufferAddress += sizeof(float);
memcpy(bufferAddress, &leftBottom, sizeof(Snowstorm_Vector2));
bufferAddress += sizeof(Snowstorm_Vector2);
memcpy(bufferAddress, &color, sizeof(Snowstorm_Color));
bufferAddress += sizeof(Snowstorm_Color);
memcpy(bufferAddress, &leftUV, sizeof(float));
bufferAddress += sizeof(float);
memcpy(bufferAddress, &bottomUV, sizeof(float));
bufferAddress += sizeof(float);
memcpy(bufferAddress, &rightTop, sizeof(Snowstorm_Vector2));
bufferAddress += sizeof(Snowstorm_Vector2);
memcpy(bufferAddress, &color, sizeof(Snowstorm_Color));
bufferAddress += sizeof(Snowstorm_Color);
memcpy(bufferAddress, &rightUV, sizeof(float));
bufferAddress += sizeof(float);
memcpy(bufferAddress, &topUV, sizeof(float));
bufferAddress += sizeof(float);
memcpy(bufferAddress, &rightBottom, sizeof(Snowstorm_Vector2));
bufferAddress += sizeof(Snowstorm_Vector2);
memcpy(bufferAddress, &color, sizeof(Snowstorm_Color));
bufferAddress += sizeof(Snowstorm_Color);
memcpy(bufferAddress, &rightUV, sizeof(float));
bufferAddress += sizeof(float);
memcpy(bufferAddress, &bottomUV, sizeof(float));
bufferAddress += sizeof(float);
vertexCount += 6;
}
return (double)vertexCount;
}
void Snowstorm_Destroy(double contextId)
{
Snowstorm_Context* context = &contexts[(uint32_t)contextId];
if (context->particles != NULL)
{
free(context->particles);
}
if (context->uvs != NULL)
{
free(context->uvs);
}
}