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