1 #include <bz/fx/particle_system_internal.h>
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>
12 enum BZParticleSystemValueType
{
13 BZParticleSystemValueTypeConstant
,
14 BZParticleSystemValueTypeRandomChoice
,
15 BZParticleSystemValueTypeRandomRange
,
17 typedef enum BZParticleSystemValueType BZParticleSystemValueType
;
19 struct BZParticleSystemFloatValue
{
20 BZParticleSystemValueType valueType
;
24 size_t choiceValueCount
;
33 typedef struct BZParticleSystemFloatValue BZParticleSystemFloatValue
;
35 struct BZParticleSystemFloatTableValue
{
37 BZParticleSystemFloatValue value
;
39 typedef struct BZParticleSystemFloatTableValue BZParticleSystemFloatTableValue
;
41 struct BZParticleSystemFloatTable
{
43 BZParticleSystemFloatTableValue
*values
;
46 typedef struct BZParticleSystemFloatTable BZParticleSystemFloatTable
;
48 struct BZParticleSubsystem
{
50 BZIdentifierHash identifier
;
52 float *lifetimeValueTable
;
53 float *angleValueTable
;
54 float *velocityXValueTable
;
55 float *velocityYValueTable
;
56 float *sizeValueTable
;
57 float *colorValueTable
;
58 float *alphaValueTable
;
60 typedef struct BZParticleSubsystem BZParticleSubsystem
;
62 struct BZParticleEventEmission
{
63 BZParticleSystemFloatValue countValue
;
64 BZParticleSubsystem
*particleSubsystem
;
66 typedef struct BZParticleEventEmission BZParticleEventEmission
;
68 struct BZParticleEvent
{
69 BZIdentifierHash identifier
;
71 BZParticleEventEmission
*emissions
;
73 typedef struct BZParticleEvent BZParticleEvent
;
75 struct BZParticleSystem
{
76 size_t subsystemCount
;
77 BZParticleSubsystem
*subsystems
;
79 BZParticleEvent
*events
;
82 static void particleJSONReadFloatValue(BZMemoryArenaID arena
, BZParticleSystemFloatValue
*valueOut
, JSON_Value
*value
, float defaultValue
) {
83 JSON_Value_Type valueType
= json_value_get_type(value
);
87 valueOut
->valueType
= BZParticleSystemValueTypeConstant
;
88 valueOut
->constantValue
= json_value_get_number(value
);
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
);
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");
111 valueOut
->valueType
= BZParticleSystemValueTypeConstant
;
112 valueOut
->constantValue
= defaultValue
;
117 static void particleJSONReadFloatTable(BZMemoryArenaID arena
, BZParticleSystemFloatTable
*tableOut
, JSON_Value
*value
, float defaultValue
) {
118 bool isTable
= false;
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
)) {
126 BZParticleSystemFloatValue previousValue
;
128 tableOut
->valueCount
= json_array_get_count(floatTable
);
129 tableOut
->values
= (BZParticleSystemFloatTableValue
*)bzMemoryAlloc(arena
, tableOut
->valueCount
* sizeof(BZParticleSystemFloatTableValue
));
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");
135 JSON_Value
*valueValue
= json_object_get_value(entry
, "value");
136 if (valueValue
!= NULL
) {
137 particleJSONReadFloatValue(arena
, &previousValue
, valueValue
, defaultValue
);
139 tableOut
->values
[i
].value
= previousValue
;
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
);
152 static void resolveParticleSystemFloatValue(float *valueOut
, const BZParticleSystemFloatValue
*value
) {
153 switch (value
->valueType
) {
154 case BZParticleSystemValueTypeConstant
:
155 *valueOut
= value
->constantValue
;
158 case BZParticleSystemValueTypeRandomChoice
:
159 *valueOut
= *((float *)bzRandomArrayValue(value
->choiceValueCount
, sizeof(float), value
->choiceValues
));
162 case BZParticleSystemValueTypeRandomRange
:
163 *valueOut
= bzRandomFloatRange(value
->rangeMinValue
, value
->rangeMaxValue
);
168 static void resolveParticleSystemFloatTableValue(float valuesOut
[], size_t valuesCount
, const BZParticleSystemFloatTable
*table
) {
169 float toT
= table
->values
[0].time
;
171 resolveParticleSystemFloatValue(&toValue
, &table
->values
[0].value
);
173 float fromValue
= toValue
;
177 for (size_t i
= 0; i
<= valuesCount
; ++i
) {
178 float t
= (float)i
/ (float)valuesCount
;
181 if (idx
< table
->valueCount
) {
185 toT
= table
->values
[idx
].time
;
186 resolveParticleSystemFloatValue(&toValue
, &table
->values
[idx
].value
);
194 float tt
= bzUnlerp(fromT
, toT
, t
);
195 valuesOut
[i
] = bzLerp(fromValue
, toValue
, tt
);
199 #define ParticleVariants 128
200 #define ParticleSlices 64
202 BZParticleSystemID
bzFXLoadParticleSystem(BZMemoryArenaID arena
, const char *identifierFmt
, ...) {
203 bzMakeIdentifier(identifier
, identifierFmt
);
205 BZParticleSystemID particleSystem
= bzMemoryAlloc(arena
, sizeof(BZParticleSystem
));
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
);
213 //json_set_allocation_functions
214 JSON_Value
*particleSystemJson
= json_parse_string(data
);
215 JSON_Object
*particleSystemJsonObject
= json_object(particleSystemJson
);
217 JSON_Array
*subsystemsArray
= json_object_get_array(particleSystemJsonObject
, "subsystems");
218 bzAssertMessage(subsystemsArray
!= NULL
, "Invalid particle JSON");
220 particleSystem
->subsystemCount
= json_array_get_count(subsystemsArray
);
221 particleSystem
->subsystems
= bzMemoryAlloc(arena
, particleSystem
->subsystemCount
* sizeof(BZParticleSubsystem
));
223 for (size_t i
= 0; i
< particleSystem
->subsystemCount
; ++i
) {
224 BZParticleSubsystem
*subsystem
= &particleSystem
->subsystems
[i
];
226 subsystem
->maxParticles
= 1024; // FIXME, load
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
);
237 for (size_t i
= 0; i
< particleSystem
->subsystemCount
; ++i
) {
238 BZParticleSubsystem
*subsystem
= &particleSystem
->subsystems
[i
];
240 JSON_Object
*subsystemObject
= json_array_get_object(subsystemsArray
, i
);
242 const char *identifier
= json_object_get_string(subsystemObject
, "identifier");
243 subsystem
->identifier
= bzIdentifierHashFromIdentifier(identifier
);
245 bzMemoryArenaPushWatermark(arena
); // for temp stuff
247 BZParticleSystemFloatValue lifetimeValue
;
248 particleJSONReadFloatValue(arena
, &lifetimeValue
, json_object_get_value(subsystemObject
, "lifetime"), 1.0f
);
250 BZParticleSystemFloatTable angleTable
;
251 particleJSONReadFloatTable(arena
, &angleTable
, json_object_get_value(subsystemObject
, "angle"), 0.0f
);
253 BZParticleSystemFloatTable velocityXTable
;
254 particleJSONReadFloatTable(arena
, &velocityXTable
, json_object_get_value(subsystemObject
, "velocity-x"), 1.0f
);
256 BZParticleSystemFloatTable velocityYTable
;
257 particleJSONReadFloatTable(arena
, &velocityYTable
, json_object_get_value(subsystemObject
, "velocity-y"), 1.0f
);
259 BZParticleSystemFloatTable sizeTable
;
260 particleJSONReadFloatTable(arena
, &sizeTable
, json_object_get_value(subsystemObject
, "size"), 1.0f
);
262 BZParticleSystemFloatTable colorTable
;
263 particleJSONReadFloatTable(arena
, &colorTable
, json_object_get_value(subsystemObject
, "color"), 7.0f
);
265 BZParticleSystemFloatTable alphaTable
;
266 particleJSONReadFloatTable(arena
, &alphaTable
, json_object_get_value(subsystemObject
, "alpha"), 0.0f
);
268 for (size_t i
= 0; i
< ParticleVariants
; ++i
) {
269 resolveParticleSystemFloatValue(&subsystem
->lifetimeValueTable
[i
], &lifetimeValue
);
271 size_t idx
= i
* ParticleSlices
;
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
);
281 bzMemoryArenaPopWatermark(arena
);
284 JSON_Array
*eventsArray
= json_object_get_array(particleSystemJsonObject
, "events");
285 bzAssertMessage(eventsArray
!= NULL
, "Invalid particle JSON");
287 particleSystem
->eventCount
= json_array_get_count(eventsArray
);
288 particleSystem
->events
= bzMemoryAlloc(arena
, particleSystem
->eventCount
* sizeof(BZParticleEvent
));
290 for (size_t i
= 0; i
< particleSystem
->eventCount
; ++i
) {
291 BZParticleEvent
*event
= &particleSystem
->events
[i
];
293 JSON_Object
*eventObject
= json_array_get_object(eventsArray
, i
);
295 const char *identifier
= json_object_get_string(eventObject
, "identifier");
296 event
->identifier
= bzIdentifierHashFromIdentifier(identifier
);
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
));
302 for (size_t j
= 0; j
< event
->emissionCount
; ++j
) {
303 BZParticleEventEmission
*emission
= &event
->emissions
[j
];
305 JSON_Object
*emissionObject
= json_array_get_object(emissionsArray
, j
);
307 BZParticleSystemFloatValue lifetimeValue
;
308 particleJSONReadFloatValue(arena
, &emission
->countValue
, json_object_get_value(emissionObject
, "count"), 1.0f
);
310 const char *subsystemIdentifier
= json_object_get_string(emissionObject
, "subsystem");
311 BZIdentifierHash subsystemIdentifierHash
= bzIdentifierHashFromIdentifier(subsystemIdentifier
);
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
;
321 bzAssertMessage(emission
->particleSubsystem
!= NULL
, "could not find particle subsystem '%s'", subsystemIdentifier
);
325 json_value_free(particleSystemJson
);
326 bzMemoryArenaResetTmp(arena
);
328 return particleSystem
;
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
;
335 float localX
= spawnPosition
->x
;
336 float localY
= spawnPosition
->y
;
337 float localAngle
= spawnAngle
;
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
;
347 resolveParticleSystemFloatValue(&count
, &emission
->countValue
);
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
;
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
[]) {
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
];
378 float t
= bzUnlerp(0, l
, particleTicks
);
379 size_t frame
= (int)(t
* ParticleSlices
);
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.
388 float a
= baseAngle
[i
] + particleSubsystem
->angleValueTable
[tableIdx1
];
389 bzMatrixRotation(&local
, a
);
392 BZVector v
= bzVectorMake(particleSubsystem
->velocityXValueTable
[tableIdx2
], particleSubsystem
->velocityYValueTable
[tableIdx2
]);
393 bzMatrixTransformVector(&v
, &v
, &local
);
397 size
[i
] = particleSubsystem
->sizeValueTable
[tableIdx3
];
398 color
[i
] = particleSubsystem
->colorValueTable
[tableIdx4
];
399 alpha
[i
] = particleSubsystem
->alphaValueTable
[tableIdx5
];