9f0f9adee588e22ee4ed594239cb9195af78ac01
[andemo] / libs / psys / pattr.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 #define _GNU_SOURCE
19 #include <stdio.h>
20 #include <stdlib.h>
21 #include <string.h>
22 #include <errno.h>
23 #include <ctype.h>
24 #include <assert.h>
25
26 #ifdef __MSVCRT__
27 #include <malloc.h>
28 #else
29 #include <alloca.h>
30 #endif
31
32 #include "pattr.h"
33 #include "psys_gl.h"
34
35 enum {
36         OPT_STR,
37         OPT_NUM,
38         OPT_NUM_RANGE,
39         OPT_VEC,
40         OPT_VEC_RANGE
41 };
42
43 struct cfgopt {
44         char *name;
45         int type;
46         long tm;
47         char *valstr;
48         float val[3], valrng[3];
49 };
50
51 static int init_particle_attr(struct psys_particle_attributes *pattr);
52 static void destroy_particle_attr(struct psys_particle_attributes *pattr);
53 static struct cfgopt *get_cfg_opt(const char *line);
54 static void release_cfg_opt(struct cfgopt *opt);
55 static char *stripspace(char *str);
56
57 static void *tex_cls;
58 static unsigned int (*load_texture)(const char*, void*) = psys_gl_load_texture;
59 static void (*unload_texture)(unsigned int, void*) = psys_gl_unload_texture;
60
61
62 void psys_texture_loader(unsigned int (*load)(const char*, void*), void (*unload)(unsigned int, void*), void *cls)
63 {
64         load_texture = load;
65         unload_texture = unload;
66         tex_cls = cls;
67 }
68
69 struct psys_attributes *psys_create_attr(void)
70 {
71         struct psys_attributes *attr = malloc(sizeof *attr);
72         if(attr) {
73                 if(psys_init_attr(attr) == -1) {
74                         free(attr);
75                         attr = 0;
76                 }
77         }
78         return attr;
79 }
80
81 void psys_free_attr(struct psys_attributes *attr)
82 {
83         psys_destroy_attr(attr);
84         free(attr);
85 }
86
87 int psys_init_attr(struct psys_attributes *attr)
88 {
89         memset(attr, 0, sizeof *attr);
90
91         if(psys_init_track3(&attr->spawn_range) == -1)
92                 goto err;
93         if(psys_init_track(&attr->rate) == -1)
94                 goto err;
95         if(psys_init_anm_rnd(&attr->life) == -1)
96                 goto err;
97         if(psys_init_anm_rnd(&attr->size) == -1)
98                 goto err;
99         if(psys_init_anm_rnd3(&attr->dir) == -1)
100                 goto err;
101         if(psys_init_track3(&attr->grav) == -1)
102                 goto err;
103
104         if(init_particle_attr(&attr->part_attr) == -1)
105                 goto err;
106
107         attr->max_particles = -1;
108
109         anm_set_track_default(&attr->size.value.trk, 1.0);
110         anm_set_track_default(&attr->life.value.trk, 1.0);
111
112         attr->blending = PSYS_ADD;
113         return 0;
114
115 err:
116         psys_destroy_attr(attr);
117         return -1;
118 }
119
120
121 static int init_particle_attr(struct psys_particle_attributes *pattr)
122 {
123         if(psys_init_track3(&pattr->color) == -1) {
124                 return -1;
125         }
126         if(psys_init_track(&pattr->alpha) == -1) {
127                 psys_destroy_track3(&pattr->color);
128                 return -1;
129         }
130         if(psys_init_track(&pattr->size) == -1) {
131                 psys_destroy_track3(&pattr->color);
132                 psys_destroy_track(&pattr->alpha);
133                 return -1;
134         }
135
136         anm_set_track_default(&pattr->color.x, 1.0);
137         anm_set_track_default(&pattr->color.y, 1.0);
138         anm_set_track_default(&pattr->color.z, 1.0);
139         anm_set_track_default(&pattr->alpha.trk, 1.0);
140         anm_set_track_default(&pattr->size.trk, 1.0);
141         return 0;
142 }
143
144
145 void psys_destroy_attr(struct psys_attributes *attr)
146 {
147         psys_destroy_track3(&attr->spawn_range);
148         psys_destroy_track(&attr->rate);
149         psys_destroy_anm_rnd(&attr->life);
150         psys_destroy_anm_rnd(&attr->size);
151         psys_destroy_anm_rnd3(&attr->dir);
152         psys_destroy_track3(&attr->grav);
153
154         destroy_particle_attr(&attr->part_attr);
155
156         if(attr->tex && unload_texture) {
157                 unload_texture(attr->tex, tex_cls);
158         }
159 }
160
161 static void destroy_particle_attr(struct psys_particle_attributes *pattr)
162 {
163         psys_destroy_track3(&pattr->color);
164         psys_destroy_track(&pattr->alpha);
165         psys_destroy_track(&pattr->size);
166 }
167
168 void psys_copy_attr(struct psys_attributes *dest, const struct psys_attributes *src)
169 {
170         dest->tex = src->tex;
171
172         psys_copy_track3(&dest->spawn_range, &src->spawn_range);
173         psys_copy_track(&dest->rate, &src->rate);
174
175         psys_copy_anm_rnd(&dest->life, &src->life);
176         psys_copy_anm_rnd(&dest->size, &src->size);
177         psys_copy_anm_rnd3(&dest->dir, &src->dir);
178
179         psys_copy_track3(&dest->grav, &src->grav);
180
181         dest->drag = src->drag;
182         dest->max_particles = src->max_particles;
183
184         dest->blending = src->blending;
185
186         /* also copy the particle attributes */
187         psys_copy_track3(&dest->part_attr.color, &src->part_attr.color);
188         psys_copy_track(&dest->part_attr.alpha, &src->part_attr.alpha);
189         psys_copy_track(&dest->part_attr.size, &src->part_attr.size);
190 }
191
192 void psys_eval_attr(struct psys_attributes *attr, anm_time_t tm)
193 {
194         float tmp[3];
195
196         psys_eval_track3(&attr->spawn_range, tm);
197         psys_eval_track(&attr->rate, tm);
198         psys_eval_anm_rnd(&attr->life, tm);
199         psys_eval_anm_rnd(&attr->size, tm);
200         psys_eval_anm_rnd3(&attr->dir, tm, tmp);
201         psys_eval_track3(&attr->grav, tm);
202 }
203
204 int psys_load_attr(struct psys_attributes *attr, const char *fname)
205 {
206         FILE *fp;
207         int res;
208
209         if(!fname) {
210                 return -1;
211         }
212
213         if(!(fp = fopen(fname, "r"))) {
214                 fprintf(stderr, "psys_load_attr: failed to read file: %s: %s\n", fname, strerror(errno));
215                 return -1;
216         }
217         res = psys_load_attr_stream(attr, fp);
218         fclose(fp);
219         return res;
220 }
221
222 int psys_load_attr_stream(struct psys_attributes *attr, FILE *fp)
223 {
224         int lineno = 0;
225         char buf[512];
226         struct cfgopt *opt = 0;
227
228         psys_init_attr(attr);
229
230         while(fgets(buf, sizeof buf, fp)) {
231
232                 lineno++;
233
234                 if(!(opt = get_cfg_opt(buf))) {
235                         continue;
236                 }
237
238                 if(strcmp(opt->name, "texture") == 0) {
239                         if(opt->type != OPT_STR) {
240                                 goto err;
241                         }
242                         if(!load_texture) {
243                                 fprintf(stderr, "particle system requests a texture, but no texture loader available!\n");
244                                 goto err;
245                         }
246                         if(!(attr->tex = load_texture(opt->valstr, tex_cls))) {
247                                 fprintf(stderr, "failed to load texture: %s\n", opt->valstr);
248                                 goto err;
249                         }
250
251                         release_cfg_opt(opt);
252                         continue;
253
254                 } else if(strcmp(opt->name, "blending") == 0) {
255                         if(opt->type != OPT_STR) {
256                                 goto err;
257                         }
258
259                         /* parse blending mode */
260                         if(strcmp(opt->valstr, "add") == 0 || strcmp(opt->valstr, "additive") == 0) {
261                                 attr->blending = PSYS_ADD;
262                         } else if(strcmp(opt->valstr, "alpha") == 0) {
263                                 attr->blending = PSYS_ALPHA;
264                         } else {
265                                 fprintf(stderr, "invalid blending mode: %s\n", opt->valstr);
266                                 goto err;
267                         }
268
269                         release_cfg_opt(opt);
270                         continue;
271
272                 } else if(opt->type == OPT_STR) {
273                         fprintf(stderr, "invalid particle config: '%s'\n", opt->name);
274                         goto err;
275                 }
276
277                 if(strcmp(opt->name, "spawn_range") == 0) {
278                         psys_set_value3(&attr->spawn_range, opt->tm, opt->val[0], opt->val[1], opt->val[2]);
279                 } else if(strcmp(opt->name, "rate") == 0) {
280                         psys_set_value(&attr->rate, opt->tm, opt->val[0]);
281                 } else if(strcmp(opt->name, "life") == 0) {
282                         psys_set_anm_rnd(&attr->life, opt->tm, opt->val[0], opt->valrng[0]);
283                 } else if(strcmp(opt->name, "size") == 0) {
284                         psys_set_anm_rnd(&attr->size, opt->tm, opt->val[0], opt->valrng[0]);
285                 } else if(strcmp(opt->name, "dir") == 0) {
286                         psys_set_anm_rnd3(&attr->dir, opt->tm, opt->val, opt->valrng);
287                 } else if(strcmp(opt->name, "grav") == 0) {
288                         psys_set_value3(&attr->grav, opt->tm, opt->val[0], opt->val[1], opt->val[2]);
289                 } else if(strcmp(opt->name, "drag") == 0) {
290                         attr->drag = opt->val[0];
291                 } else if(strcmp(opt->name, "pcolor") == 0) {
292                         psys_set_value3(&attr->part_attr.color, opt->tm, opt->val[0], opt->val[1], opt->val[2]);
293                 } else if(strcmp(opt->name, "palpha") == 0) {
294                         psys_set_value(&attr->part_attr.alpha, opt->tm, opt->val[0]);
295                 } else if(strcmp(opt->name, "psize") == 0) {
296                         psys_set_value(&attr->part_attr.size, opt->tm, opt->val[0]);
297                 } else {
298                         fprintf(stderr, "unrecognized particle config option: %s\n", opt->name);
299                         goto err;
300                 }
301
302                 release_cfg_opt(opt);
303         }
304
305         return 0;
306
307 err:
308         fprintf(stderr, "Line %d: error parsing particle definition\n", lineno);
309         release_cfg_opt(opt);
310         return -1;
311 }
312
313 static struct cfgopt *get_cfg_opt(const char *line)
314 {
315         char *buf, *tmp;
316         struct cfgopt *opt;
317
318         /* allocate a working buffer on the stack that could fit the current line */
319         buf = alloca(strlen(line) + 1);
320
321         line = stripspace((char*)line);
322         if(line[0] == '#' || !line[0]) {
323                 return 0;       /* skip empty lines and comments */
324         }
325
326         if(!(opt = malloc(sizeof *opt))) {
327                 return 0;
328         }
329         memset(opt, 0, sizeof *opt);
330
331         if(!(opt->valstr = strchr(line, '='))) {
332                 release_cfg_opt(opt);
333                 return 0;
334         }
335         *opt->valstr++ = 0;
336         opt->valstr = stripspace(opt->valstr);
337
338         strcpy(buf, line);
339         buf = stripspace(buf);
340
341         /* parse the keyframe time specifier if it exists */
342         if((tmp = strchr(buf, '('))) {
343                 char *endp;
344                 float tval;
345
346                 *tmp++ = 0;
347                 opt->name = malloc(strlen(buf) + 1);
348                 strcpy(opt->name, buf);
349
350                 tval = strtod(tmp, &endp);
351                 if(endp == tmp) { /* nada ... */
352                         opt->tm = 0;
353                 } else if(*endp == 's') {       /* seconds suffix */
354                         opt->tm = (long)(tval * 1000.0f);
355                 } else {
356                         opt->tm = (long)tval;
357                 }
358         } else {
359                 opt->name = malloc(strlen(buf) + 1);
360                 strcpy(opt->name, buf);
361                 opt->tm = 0;
362         }
363
364         if(sscanf(opt->valstr, "[%f %f %f] ~ [%f %f %f]", opt->val, opt->val + 1, opt->val + 2,
365                                 opt->valrng, opt->valrng + 1, opt->valrng + 2) == 6) {
366                 /* value is a vector range */
367                 opt->type = OPT_VEC_RANGE;
368
369         } else if(sscanf(opt->valstr, "%f ~ %f", opt->val, opt->valrng) == 2) {
370                 /* value is a number range */
371                 opt->type = OPT_NUM_RANGE;
372                 opt->val[1] = opt->val[2] = opt->val[0];
373                 opt->valrng[1] = opt->valrng[2] = opt->valrng[0];
374
375         } else if(sscanf(opt->valstr, "[%f %f %f]", opt->val, opt->val + 1, opt->val + 2) == 3) {
376                 /* value is a vector */
377                 opt->type = OPT_VEC;
378                 opt->valrng[0] = opt->valrng[1] = opt->valrng[2] = 0.0f;
379
380         } else if(sscanf(opt->valstr, "%f", opt->val) == 1) {
381                 /* value is a number */
382                 opt->type = OPT_NUM;
383                 opt->val[1] = opt->val[2] = opt->val[0];
384                 opt->valrng[0] = opt->valrng[1] = opt->valrng[2] = 0.0f;
385
386         } else if(sscanf(opt->valstr, "\"%s\"", buf) == 1) {
387                 /* just a string... strip the quotes */
388                 if(buf[strlen(buf) - 1] == '\"') {
389                         buf[strlen(buf) - 1] = 0;
390                 }
391                 opt->type = OPT_STR;
392                 opt->valstr = malloc(strlen(buf) + 1);
393                 assert(opt->valstr);
394                 strcpy(opt->valstr, buf);
395         } else {
396                 /* fuck it ... */
397                 release_cfg_opt(opt);
398                 return 0;
399         }
400
401         return opt;
402 }
403
404 static void release_cfg_opt(struct cfgopt *opt)
405 {
406         if(opt) {
407                 free(opt->name);
408                 opt->name = 0;
409         }
410         opt = 0;
411 }
412
413
414 int psys_save_attr(const struct psys_attributes *attr, const char *fname)
415 {
416         FILE *fp;
417         int res;
418
419         if(!(fp = fopen(fname, "w"))) {
420                 fprintf(stderr, "psys_save_attr: failed to write file: %s: %s\n", fname, strerror(errno));
421                 return -1;
422         }
423         res = psys_save_attr_stream(attr, fp);
424         fclose(fp);
425         return res;
426 }
427
428 int psys_save_attr_stream(const struct psys_attributes *attr, FILE *fp)
429 {
430         return -1;      /* TODO */
431 }
432
433
434 static char *stripspace(char *str)
435 {
436         char *end;
437
438         while(*str && isspace(*str)) {
439                 str++;
440         }
441
442         end = str + strlen(str) - 1;
443         while(end >= str && isspace(*end)) {
444                 *end-- = 0;
445         }
446         return str;
447 }