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