initial commit
[liquidmodel] / libs / psys / psys.c
1 /*
2 libpsys - reusable particle system library.
3 Copyright (C) 2011-2018  John Tsiombikas <nuclear@member.fsf.org>
4
5 This program is free software: you can redistribute it and/or modify
6 it under the terms of the GNU Lesser General Public License as published by
7 the Free Software Foundation, either version 3 of the License, or
8 (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 GNU Lesser General Public License for more details.
14
15 You should have received a copy of the GNU Lesser General Public License
16 along with this program.  If not, see <http://www.gnu.org/licenses/>.
17 */
18 #include <stdlib.h>
19 #include <string.h>
20 #include <math.h>
21 #include <float.h>
22 #include <assert.h>
23 #include "psys.h"
24 #ifdef PSYS_MULTITHREADED
25 #include <pthread.h>
26 #endif
27
28 static int spawn_particle(struct psys_emitter *em, struct psys_particle *p);
29 static void update_particle(struct psys_emitter *em, struct psys_particle *p, long tm, float dt, void *cls);
30
31 /* particle pool */
32 static struct psys_particle *ppool;
33 static int ppool_size;
34 #ifdef PSYS_MULTITHREADED
35 static pthread_mutex_t pool_lock = PTHREAD_MUTEX_INITIALIZER;
36 #endif
37
38 static struct psys_particle *palloc(void);
39 static void pfree(struct psys_particle *p);
40
41 /* --- constructors and shit --- */
42
43 struct psys_emitter *psys_create(void)
44 {
45         struct psys_emitter *em;
46
47         if(!(em = malloc(sizeof *em))) {
48                 return 0;
49         }
50         if(psys_init(em) == -1) {
51                 free(em);
52                 return 0;
53         }
54         return em;
55 }
56
57 void psys_free(struct psys_emitter *em)
58 {
59         psys_destroy(em);
60         free(em);
61 }
62
63 int psys_init(struct psys_emitter *em)
64 {
65         memset(em, 0, sizeof *em);
66
67         if(anm_init_node(&em->prs) == -1) {
68                 return -1;
69         }
70         if(psys_init_attr(&em->attr) == -1) {
71                 anm_destroy_node(&em->prs);
72                 return -1;
73         }
74
75         em->spawn = 0;  /* no custom spawning, just the defaults */
76         em->update = update_particle;
77         return 0;
78 }
79
80 void psys_destroy(struct psys_emitter *em)
81 {
82         struct psys_particle *part;
83
84         part = em->plist;
85         while(part) {
86                 struct psys_particle *tmp = part;
87                 part = part->next;
88                 pfree(tmp);
89         }
90
91         psys_destroy_attr(&em->attr);
92 }
93
94 void psys_set_pos(struct psys_emitter *em, const float *pos, long tm)
95 {
96         anm_set_position(&em->prs, pos, ANM_MSEC2TM(tm));
97 }
98
99 void psys_set_pos3f(struct psys_emitter *em, float x, float y, float z, long tm)
100 {
101         anm_set_position3f(&em->prs, x, y, z, ANM_MSEC2TM(tm));
102 }
103
104 void psys_set_rot(struct psys_emitter *em, const float *qrot, long tm)
105 {
106         anm_set_rotation(&em->prs, qrot, ANM_MSEC2TM(tm));
107 }
108
109 void psys_set_pivot(struct psys_emitter *em, const float *pivot)
110 {
111         anm_set_pivot(&em->prs, pivot[0], pivot[1], pivot[2]);
112 }
113
114 void psys_set_pivot3f(struct psys_emitter *em, float x, float y, float z)
115 {
116         anm_set_pivot(&em->prs, x, y, z);
117 }
118
119 void psys_get_pos(struct psys_emitter *em, float *pos, long tm)
120 {
121         anm_get_node_position(&em->prs, pos, ANM_MSEC2TM(tm));
122 }
123
124 void psys_get_rot(struct psys_emitter *em, float *qrot, long tm)
125 {
126         anm_get_node_rotation(&em->prs, qrot, ANM_MSEC2TM(tm));
127 }
128
129 void psys_get_pivot(struct psys_emitter *em, float *pivot)
130 {
131         anm_get_pivot(&em->prs, pivot, pivot + 1, pivot + 2);
132 }
133
134 void psys_clear_collision_planes(struct psys_emitter *em)
135 {
136         struct psys_plane *plane;
137
138         plane = em->planes;
139         while(plane) {
140                 struct psys_plane *tmp = plane;
141                 plane = plane->next;
142                 free(tmp);
143         }
144 }
145
146 int psys_add_collision_plane(struct psys_emitter *em, const float *plane, float elast)
147 {
148         struct psys_plane *node;
149
150         if(!(node = malloc(sizeof *node))) {
151                 return -1;
152         }
153         node->nx = plane[0];
154         node->ny = plane[1];
155         node->nz = plane[2];
156         node->d = plane[3];
157         node->elasticity = elast;
158         node->next = em->planes;
159         em->planes = node;
160         return 0;
161 }
162
163 void psys_add_particle(struct psys_emitter *em, struct psys_particle *p)
164 {
165         p->next = em->plist;
166         em->plist = p;
167
168         em->pcount++;
169 }
170
171 void psys_spawn_func(struct psys_emitter *em, psys_spawn_func_t func, void *cls)
172 {
173         em->spawn = func;
174         em->spawn_cls = cls;
175 }
176
177 void psys_update_func(struct psys_emitter *em, psys_update_func_t func, void *cls)
178 {
179         em->update = func;
180         em->upd_cls = cls;
181 }
182
183 void psys_draw_func(struct psys_emitter *em, psys_draw_func_t draw,
184                 psys_draw_start_func_t start, psys_draw_end_func_t end, void *cls)
185 {
186         em->draw = draw;
187         em->draw_start = start;
188         em->draw_end = end;
189         em->draw_cls = cls;
190 }
191
192 /* --- update and render --- */
193
194 void psys_update(struct psys_emitter *em, long tm)
195 {
196         long delta_ms, spawn_tm, spawn_dt;
197         float dt;
198         int i, spawn_count;
199         struct psys_particle *p, pdummy;
200         anm_time_t atm = ANM_MSEC2TM(tm);
201
202         if(!em->update) {
203                 static int once;
204                 if(!once) {
205                         once = 1;
206                         fprintf(stderr, "psys_update called without an update callback\n");
207                 }
208         }
209
210         delta_ms = tm - em->last_update;
211         if(delta_ms <= 0) {
212                 return;
213         }
214         dt = (float)delta_ms / 1000.0f;
215
216         psys_eval_attr(&em->attr, atm);
217
218         /* how many particles to spawn for this interval ? */
219         em->spawn_acc += psys_get_cur_value(&em->attr.rate) * delta_ms;
220         if(em->spawn_acc >= 1000) {
221                 spawn_count = em->spawn_acc / 1000;
222                 em->spawn_acc %= 1000;
223         } else {
224                 spawn_count = 0;
225         }
226
227         if(spawn_count) {
228                 spawn_dt = delta_ms / spawn_count;
229         }
230         spawn_tm = em->last_update;
231         for(i=0; i<spawn_count; i++) {
232                 if(em->attr.max_particles >= 0 && em->pcount >= em->attr.max_particles) {
233                         break;
234                 }
235
236                 /* update emitter position for this spawning */
237                 anm_get_position(&em->prs, em->cur_pos, ANM_MSEC2TM(spawn_tm));
238
239                 if(!(p = palloc())) {
240                         return;
241                 }
242                 if(spawn_particle(em, p) == -1) {
243                         pfree(p);
244                 }
245                 spawn_tm += spawn_dt;
246         }
247
248         /* update all particles */
249         p = em->plist;
250         while(p) {
251                 em->update(em, p, tm, dt, em->upd_cls);
252                 p = p->next;
253         }
254
255         /* cleanup dead particles */
256         pdummy.next = em->plist;
257         p = &pdummy;
258         while(p->next) {
259                 if(p->next->life <= 0) {
260                         struct psys_particle *tmp = p->next;
261                         p->next = p->next->next;
262                         pfree(tmp);
263                         em->pcount--;
264                 } else {
265                         p = p->next;
266                 }
267         }
268         em->plist = pdummy.next;
269
270         em->last_update = tm;
271 }
272
273 void psys_draw(const struct psys_emitter *em)
274 {
275         struct psys_particle *p;
276
277         assert(em->draw);
278
279         if(em->draw_start) {
280                 em->draw_start(em, em->draw_cls);
281         }
282
283         p = em->plist;
284         while(p) {
285                 em->draw(em, p, em->draw_cls);
286                 p = p->next;
287         }
288
289         if(em->draw_end) {
290                 em->draw_end(em, em->draw_cls);
291         }
292 }
293
294 static int spawn_particle(struct psys_emitter *em, struct psys_particle *p)
295 {
296         int i;
297         struct psys_rnd3 rpos;
298
299         for(i=0; i<3; i++) {
300                 rpos.value[i] = em->cur_pos[i];
301         }
302         psys_get_cur_value3(&em->attr.spawn_range, rpos.range);
303
304         psys_eval_rnd3(&rpos, &p->x);
305         psys_eval_anm_rnd3(&em->attr.dir, PSYS_EVAL_CUR, &p->vx);
306         p->base_size = psys_eval_anm_rnd(&em->attr.size, PSYS_EVAL_CUR);
307         p->max_life = p->life = psys_eval_anm_rnd(&em->attr.life, PSYS_EVAL_CUR);
308
309         p->pattr = &em->attr.part_attr;
310
311         if(em->spawn && em->spawn(em, p, em->spawn_cls) == -1) {
312                 return -1;
313         }
314
315         psys_add_particle(em, p);
316         return 0;
317 }
318
319 static void update_particle(struct psys_emitter *em, struct psys_particle *p, long tm, float dt, void *cls)
320 {
321         float accel[3], grav[3];
322         anm_time_t t;
323
324         psys_get_cur_value3(&em->attr.grav, grav);
325
326         accel[0] = grav[0] - p->vx * em->attr.drag;
327         accel[1] = grav[1] - p->vy * em->attr.drag;
328         accel[2] = grav[2] - p->vz * em->attr.drag;
329
330         p->vx += accel[0] * dt;
331         p->vy += accel[1] * dt;
332         p->vz += accel[2] * dt;
333
334         p->x += p->vx * dt;
335         p->y += p->vy * dt;
336         p->z += p->vz * dt;
337
338         /* update particle attributes */
339         t = (anm_time_t)(1000.0f * (p->max_life - p->life) / p->max_life);
340
341         psys_get_value3(&p->pattr->color, t, &p->r);
342         p->alpha = psys_get_value(&p->pattr->alpha, t);
343         p->size = p->base_size * psys_get_value(&p->pattr->size, t);
344
345         p->life -= dt;
346 }
347
348 /* --- particle allocation pool --- */
349
350 static struct psys_particle *palloc(void)
351 {
352         struct psys_particle *p;
353
354 #ifdef PSYS_MULTITHREADED
355         pthread_mutex_lock(&pool_lock);
356 #endif
357         if(ppool) {
358                 p = ppool;
359                 ppool = ppool->next;
360                 ppool_size--;
361         } else {
362                 p = malloc(sizeof *p);
363         }
364 #ifdef PSYS_MULTITHREADED
365         pthread_mutex_unlock(&pool_lock);
366 #endif
367
368         return p;
369 }
370
371 static void pfree(struct psys_particle *p)
372 {
373 #ifdef PSYS_MULTITHREADED
374         pthread_mutex_lock(&pool_lock);
375 #endif
376         p->next = ppool;
377         ppool = p;
378         ppool_size++;
379 #ifdef PSYS_MULTITHREADED
380         pthread_mutex_unlock(&pool_lock);
381 #endif
382 }