]> git.bts.cx Git - benzene.git/blob - src/bz/fx/particle_system.c
Sprites
[benzene.git] / src / bz / fx / particle_system.c
1 #include <bz/fx/particle_system_internal.h>
2
3 #include <bz/math/math.h>
4 #include <bz/math/random.h>
5 #include <bz/math/vector.h>
6 #include <bz/memory/allocator.h>
7 #include <bz/resources/resource.h>
8 #include <bz/types/common.h>
9 #include <bz/types/identifier_internal.h>
10 #include <parson.h>
11
12 enum BZParticleSystemValueType {
13 BZParticleSystemValueTypeConstant,
14 BZParticleSystemValueTypeRandomChoice,
15 BZParticleSystemValueTypeRandomRange,
16 };
17 typedef enum BZParticleSystemValueType BZParticleSystemValueType;
18
19 struct BZParticleSystemFloatValue {
20 BZParticleSystemValueType valueType;
21 union {
22 float constantValue;
23 struct {
24 size_t choiceValueCount;
25 float *choiceValues;
26 };
27 struct {
28 float rangeMinValue;
29 float rangeMaxValue;
30 };
31 };
32 };
33 typedef struct BZParticleSystemFloatValue BZParticleSystemFloatValue;
34
35 struct BZParticleSystemFloatTableValue {
36 float time;
37 BZParticleSystemFloatValue value;
38 };
39 typedef struct BZParticleSystemFloatTableValue BZParticleSystemFloatTableValue;
40
41 struct BZParticleSystemFloatTable {
42 size_t valueCount;
43 BZParticleSystemFloatTableValue *values;
44
45 };
46 typedef struct BZParticleSystemFloatTable BZParticleSystemFloatTable;
47
48 struct BZParticleSubsystem {
49 size_t maxParticles;
50 BZIdentifierHash identifier;
51
52 float *lifetimeValueTable;
53 float *angleValueTable;
54 float *velocityXValueTable;
55 float *velocityYValueTable;
56 float *sizeValueTable;
57 float *colorValueTable;
58 float *alphaValueTable;
59 };
60 typedef struct BZParticleSubsystem BZParticleSubsystem;
61
62 struct BZParticleEventEmission {
63 BZParticleSystemFloatValue countValue;
64 BZParticleSubsystem *particleSubsystem;
65 };
66 typedef struct BZParticleEventEmission BZParticleEventEmission;
67
68 struct BZParticleEvent {
69 BZIdentifierHash identifier;
70 size_t emissionCount;
71 BZParticleEventEmission *emissions;
72 };
73 typedef struct BZParticleEvent BZParticleEvent;
74
75 struct BZParticleSystem {
76 size_t subsystemCount;
77 BZParticleSubsystem *subsystems;
78 size_t eventCount;
79 BZParticleEvent *events;
80 };
81
82 static void particleJSONReadFloatValue(BZMemoryArenaID arena, BZParticleSystemFloatValue *valueOut, JSON_Value *value, float defaultValue) {
83 JSON_Value_Type valueType = json_value_get_type(value);
84
85 switch (valueType) {
86 case JSONNumber:
87 valueOut->valueType = BZParticleSystemValueTypeConstant;
88 valueOut->constantValue = json_value_get_number(value);
89 break;
90
91 case JSONArray: {
92 JSON_Array *values = json_value_get_array(value);
93 valueOut->valueType = BZParticleSystemValueTypeRandomChoice;
94 valueOut->choiceValueCount = json_array_get_count(values);
95 valueOut->choiceValues = (float *)bzMemoryAlloc(arena, valueOut->choiceValueCount * sizeof(float));
96 for (size_t i = 0; i < valueOut->choiceValueCount; ++i) {
97 valueOut->choiceValues[i] = json_array_get_number(values, i);
98 }
99 break;
100 }
101
102 case JSONObject: {
103 JSON_Object *rangeObject = json_value_get_object(value);
104 valueOut->valueType = BZParticleSystemValueTypeRandomRange;
105 valueOut->rangeMinValue = json_object_get_number(rangeObject, "min");
106 valueOut->rangeMaxValue = json_object_get_number(rangeObject, "max");
107 break;
108 }
109
110 default:
111 valueOut->valueType = BZParticleSystemValueTypeConstant;
112 valueOut->constantValue = defaultValue;
113 break;
114 }
115 }
116
117 static void particleJSONReadFloatTable(BZMemoryArenaID arena, BZParticleSystemFloatTable *tableOut, JSON_Value *value, float defaultValue) {
118 bool isTable = false;
119
120 JSON_Array *floatTable = json_value_get_array(value);
121 if (floatTable != NULL) {
122 JSON_Object *test = json_array_get_object(floatTable, 0);
123 if (test != NULL && json_object_has_value_of_type(test, "time", JSONNumber)) {
124 isTable = true;
125
126 BZParticleSystemFloatValue previousValue;
127
128 tableOut->valueCount = json_array_get_count(floatTable);
129 tableOut->values = (BZParticleSystemFloatTableValue *)bzMemoryAlloc(arena, tableOut->valueCount * sizeof(BZParticleSystemFloatTableValue));
130
131 for (size_t i = 0; i < tableOut->valueCount; ++i) {
132 JSON_Object *entry = json_array_get_object(floatTable, i);
133 tableOut->values[i].time = json_object_get_number(entry, "time");
134
135 JSON_Value *valueValue = json_object_get_value(entry, "value");
136 if (valueValue != NULL) {
137 particleJSONReadFloatValue(arena, &previousValue, valueValue, defaultValue);
138 }
139 tableOut->values[i].value = previousValue;
140 }
141 }
142 }
143
144 if (isTable == false) {
145 tableOut->valueCount = 1;
146 tableOut->values = (BZParticleSystemFloatTableValue *)bzMemoryAlloc(arena, tableOut->valueCount * sizeof(BZParticleSystemFloatTableValue));
147 tableOut->values[0].time = 1.0f;
148 particleJSONReadFloatValue(arena, &tableOut->values[0].value, value, defaultValue);
149 }
150 }
151
152 static void resolveParticleSystemFloatValue(float *valueOut, const BZParticleSystemFloatValue *value) {
153 switch (value->valueType) {
154 case BZParticleSystemValueTypeConstant:
155 *valueOut = value->constantValue;
156 break;
157
158 case BZParticleSystemValueTypeRandomChoice:
159 *valueOut = *((float *)bzRandomArrayValue(value->choiceValueCount, sizeof(float), value->choiceValues));
160 break;
161
162 case BZParticleSystemValueTypeRandomRange:
163 *valueOut = bzRandomFloatRange(value->rangeMinValue, value->rangeMaxValue);
164 break;
165 }
166 }
167
168 static void resolveParticleSystemFloatTableValue(float valuesOut[], size_t valuesCount, const BZParticleSystemFloatTable *table) {
169 float toT = table->values[0].time;
170 float toValue;
171 resolveParticleSystemFloatValue(&toValue, &table->values[0].value);
172
173 float fromValue = toValue;
174 float fromT = 0.0f;
175
176 size_t idx = 0;
177 for (size_t i = 0; i <= valuesCount; ++i) {
178 float t = (float)i / (float)valuesCount;
179
180 if (t >= toT) {
181 if (idx < table->valueCount) {
182 idx += 1;
183 fromT = toT;
184 fromValue = toValue;
185 toT = table->values[idx].time;
186 resolveParticleSystemFloatValue(&toValue, &table->values[idx].value);
187 } else {
188 fromT = toT;
189 fromValue = toValue;
190 toT = 1.0f;
191 }
192 }
193
194 float tt = bzUnlerp(fromT, toT, t);
195 valuesOut[i] = bzLerp(fromValue, toValue, tt);
196 }
197 }
198
199 #define ParticleVariants 128
200 #define ParticleSlices 64
201
202 BZParticleSystemID bzFXLoadParticleSystem(BZMemoryArenaID arena, const char *identifierFmt, ...) {
203 bzMakeIdentifier(identifier, identifierFmt);
204
205 BZParticleSystemID particleSystem = bzMemoryAlloc(arena, sizeof(BZParticleSystem));
206
207 BZResourceID handle = bzResourcesOpenResource("particles", "assets/particles/%s.system.json", identifier);
208 size_t length = bzResourcesFileLength(handle);
209 char *data = bzMemoryAllocTmp(arena, length);
210 bzResourcesReadBytes(handle, data, length);
211 bzResourcesCloseResource(handle);
212
213 //json_set_allocation_functions
214 JSON_Value *particleSystemJson = json_parse_string(data);
215 JSON_Object *particleSystemJsonObject = json_object(particleSystemJson);
216
217 JSON_Array *subsystemsArray = json_object_get_array(particleSystemJsonObject, "subsystems");
218 bzAssertMessage(subsystemsArray != NULL, "Invalid particle JSON");
219
220 particleSystem->subsystemCount = json_array_get_count(subsystemsArray);
221 particleSystem->subsystems = bzMemoryAlloc(arena, particleSystem->subsystemCount * sizeof(BZParticleSubsystem));
222
223 for (size_t i = 0; i < particleSystem->subsystemCount; ++i) {
224 BZParticleSubsystem *subsystem = &particleSystem->subsystems[i];
225
226 subsystem->maxParticles = 1024; // FIXME, load
227
228 subsystem->lifetimeValueTable = bzMemoryAlloc(arena, ParticleVariants * sizeof(float));
229 subsystem->angleValueTable = bzMemoryAlloc(arena, ParticleVariants * sizeof(float) * ParticleSlices);
230 subsystem->velocityXValueTable = bzMemoryAlloc(arena, ParticleVariants * sizeof(float) * ParticleSlices);
231 subsystem->velocityYValueTable = bzMemoryAlloc(arena, ParticleVariants * sizeof(float) * ParticleSlices);
232 subsystem->sizeValueTable = bzMemoryAlloc(arena, ParticleVariants * sizeof(float) * ParticleSlices);
233 subsystem->colorValueTable = bzMemoryAlloc(arena, ParticleVariants * sizeof(float) * ParticleSlices);
234 subsystem->alphaValueTable = bzMemoryAlloc(arena, ParticleVariants * sizeof(float) * ParticleSlices);
235 }
236
237 for (size_t i = 0; i < particleSystem->subsystemCount; ++i) {
238 BZParticleSubsystem *subsystem = &particleSystem->subsystems[i];
239
240 JSON_Object *subsystemObject = json_array_get_object(subsystemsArray, i);
241
242 const char *identifier = json_object_get_string(subsystemObject, "identifier");
243 subsystem->identifier = bzIdentifierHashFromIdentifier(identifier);
244
245 bzMemoryArenaPushWatermark(arena); // for temp stuff
246
247 BZParticleSystemFloatValue lifetimeValue;
248 particleJSONReadFloatValue(arena, &lifetimeValue, json_object_get_value(subsystemObject, "lifetime"), 1.0f);
249
250 BZParticleSystemFloatTable angleTable;
251 particleJSONReadFloatTable(arena, &angleTable, json_object_get_value(subsystemObject, "angle"), 0.0f);
252
253 BZParticleSystemFloatTable velocityXTable;
254 particleJSONReadFloatTable(arena, &velocityXTable, json_object_get_value(subsystemObject, "velocity-x"), 1.0f);
255
256 BZParticleSystemFloatTable velocityYTable;
257 particleJSONReadFloatTable(arena, &velocityYTable, json_object_get_value(subsystemObject, "velocity-y"), 1.0f);
258
259 BZParticleSystemFloatTable sizeTable;
260 particleJSONReadFloatTable(arena, &sizeTable, json_object_get_value(subsystemObject, "size"), 1.0f);
261
262 BZParticleSystemFloatTable colorTable;
263 particleJSONReadFloatTable(arena, &colorTable, json_object_get_value(subsystemObject, "color"), 7.0f);
264
265 BZParticleSystemFloatTable alphaTable;
266 particleJSONReadFloatTable(arena, &alphaTable, json_object_get_value(subsystemObject, "alpha"), 0.0f);
267
268 for (size_t i = 0; i < ParticleVariants; ++i) {
269 resolveParticleSystemFloatValue(&subsystem->lifetimeValueTable[i], &lifetimeValue);
270
271 size_t idx = i * ParticleSlices;
272
273 resolveParticleSystemFloatTableValue(&subsystem->angleValueTable[idx], ParticleSlices, &angleTable);
274 resolveParticleSystemFloatTableValue(&subsystem->velocityXValueTable[idx], ParticleSlices, &velocityXTable);
275 resolveParticleSystemFloatTableValue(&subsystem->velocityYValueTable[idx], ParticleSlices, &velocityYTable);
276 resolveParticleSystemFloatTableValue(&subsystem->sizeValueTable[idx], ParticleSlices, &sizeTable);
277 resolveParticleSystemFloatTableValue(&subsystem->colorValueTable[idx], ParticleSlices, &colorTable);
278 resolveParticleSystemFloatTableValue(&subsystem->alphaValueTable[idx], ParticleSlices, &alphaTable);
279 }
280
281 bzMemoryArenaPopWatermark(arena);
282 }
283
284 JSON_Array *eventsArray = json_object_get_array(particleSystemJsonObject, "events");
285 bzAssertMessage(eventsArray != NULL, "Invalid particle JSON");
286
287 particleSystem->eventCount = json_array_get_count(eventsArray);
288 particleSystem->events = bzMemoryAlloc(arena, particleSystem->eventCount * sizeof(BZParticleEvent));
289
290 for (size_t i = 0; i < particleSystem->eventCount; ++i) {
291 BZParticleEvent *event = &particleSystem->events[i];
292
293 JSON_Object *eventObject = json_array_get_object(eventsArray, i);
294
295 const char *identifier = json_object_get_string(eventObject, "identifier");
296 event->identifier = bzIdentifierHashFromIdentifier(identifier);
297
298 JSON_Array *emissionsArray = json_object_get_array(eventObject, "emissions");
299 event->emissionCount = json_array_get_count(emissionsArray);
300 event->emissions = bzMemoryAlloc(arena, event->emissionCount * sizeof(BZParticleEventEmission));
301
302 for (size_t j = 0; j < event->emissionCount; ++j) {
303 BZParticleEventEmission *emission = &event->emissions[j];
304
305 JSON_Object *emissionObject = json_array_get_object(emissionsArray, j);
306
307 BZParticleSystemFloatValue lifetimeValue;
308 particleJSONReadFloatValue(arena, &emission->countValue, json_object_get_value(emissionObject, "count"), 1.0f);
309
310 const char *subsystemIdentifier = json_object_get_string(emissionObject, "subsystem");
311 BZIdentifierHash subsystemIdentifierHash = bzIdentifierHashFromIdentifier(subsystemIdentifier);
312
313 for (size_t k = 0; k < particleSystem->subsystemCount; ++k) {
314 BZParticleSubsystem *subsystem = &particleSystem->subsystems[k];
315 if (subsystem->identifier == subsystemIdentifierHash) {
316 emission->particleSubsystem = subsystem;
317 break;
318 }
319 }
320
321 bzAssertMessage(emission->particleSubsystem != NULL, "could not find particle subsystem '%s'", subsystemIdentifier);
322 }
323 }
324
325 json_value_free(particleSystemJson);
326 bzMemoryArenaResetTmp(arena);
327
328 return particleSystem;
329 }
330
331 size_t bzFXSpawnParticleSystemParticles(BZParticleSystemID particleSystem, BZIdentifierHash eventIdentifier, const BZVector *spawnPosition, const float spawnAngle, size_t maxCount, size_t currentIdx, float time, float spawnTime[], float lifetime[], float positionX[], float positionY[], float size[], float baseAngle[], float angle[], float color[], float alpha[], void *userData[]) {
332 size_t spawnedCount = 0;
333 size_t idx = currentIdx;
334
335 float localX = spawnPosition->x;
336 float localY = spawnPosition->y;
337 float localAngle = spawnAngle;
338
339 for (size_t i = 0; i < particleSystem->eventCount; ++i) {
340 BZParticleEvent *event = &particleSystem->events[i];
341 if (event->identifier == eventIdentifier) {
342 for (size_t j = 0; j < event->emissionCount; ++j) {
343 BZParticleEventEmission *emission = &event->emissions[j];
344 BZParticleSubsystem *particleSubsystem = emission->particleSubsystem;
345
346 float count;
347 resolveParticleSystemFloatValue(&count, &emission->countValue);
348
349 for (size_t k = 0; k < count; ++k) {
350 spawnTime[idx] = time;
351 lifetime[idx] = particleSubsystem->lifetimeValueTable[idx % ParticleVariants];
352 positionX[idx] = localX;
353 positionY[idx] = localY;
354 baseAngle[idx] = localAngle;
355 angle[idx] = localAngle;
356 userData[idx] = particleSubsystem;
357 idx += 1;
358 idx %= maxCount;
359 spawnedCount += 1;
360 }
361 }
362 }
363 }
364
365 return spawnedCount;
366 }
367
368 #include <bz/math/matrix.h>
369 void bzFXUpdateParticleSystemParticles(BZParticleSystemID particleSystem, size_t count, float time, float spawnTime[], float lifetime[], float positionX[], float positionY[], float size[], float baseAngle[], float angle[], float color[], float alpha[], void *userData[]) {
370 BZMatrix local;
371
372 for (size_t i = 0; i < count; ++i) {
373 float particleTicks = time - spawnTime[i];
374 float l = lifetime[i];
375 if (particleTicks <= l) {
376 BZParticleSubsystem *particleSubsystem = (BZParticleSubsystem *)userData[i];
377
378 float t = bzUnlerp(0, l, particleTicks);
379 size_t frame = (int)(t * ParticleSlices);
380
381 size_t startIdx = bzFloor((spawnTime[i] + l) * 1000);
382 size_t tableIdx1 = frame + ((startIdx + 12) % ParticleVariants) * ParticleSlices;
383 size_t tableIdx2 = frame + ((startIdx + 34) % ParticleVariants) * ParticleSlices;
384 size_t tableIdx3 = frame + ((startIdx + 26) % ParticleVariants) * ParticleSlices;
385 size_t tableIdx4 = frame + ((startIdx + 74) % ParticleVariants) * ParticleSlices;
386 size_t tableIdx5 = frame + ((startIdx + 87) % ParticleVariants) * ParticleSlices; // These numbers don't matter. Use anything.
387
388 float a = baseAngle[i] + particleSubsystem->angleValueTable[tableIdx1];
389 bzMatrixRotation(&local, a);
390 angle[i] = a;
391
392 BZVector v = bzVectorMake(particleSubsystem->velocityXValueTable[tableIdx2], particleSubsystem->velocityYValueTable[tableIdx2]);
393 bzMatrixTransformVector(&v, &v, &local);
394
395 positionX[i] += v.x;
396 positionY[i] += v.y;
397 size[i] = particleSubsystem->sizeValueTable[tableIdx3];
398 color[i] = particleSubsystem->colorValueTable[tableIdx4];
399 alpha[i] = particleSubsystem->alphaValueTable[tableIdx5];
400 }
401 }
402 }