2 libpsys - reusable particle system library.
3 Copyright (C) 2011-2018 John Tsiombikas <nuclear@member.fsf.org>
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.
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.
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/>.
48 float val[3], valrng[3];
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);
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;
62 void psys_texture_loader(unsigned int (*load)(const char*, void*), void (*unload)(unsigned int, void*), void *cls)
65 unload_texture = unload;
69 struct psys_attributes *psys_create_attr(void)
71 struct psys_attributes *attr = malloc(sizeof *attr);
73 if(psys_init_attr(attr) == -1) {
81 void psys_free_attr(struct psys_attributes *attr)
83 psys_destroy_attr(attr);
87 int psys_init_attr(struct psys_attributes *attr)
89 memset(attr, 0, sizeof *attr);
91 if(psys_init_track3(&attr->spawn_range) == -1)
93 if(psys_init_track(&attr->rate) == -1)
95 if(psys_init_anm_rnd(&attr->life) == -1)
97 if(psys_init_anm_rnd(&attr->size) == -1)
99 if(psys_init_anm_rnd3(&attr->dir) == -1)
101 if(psys_init_track3(&attr->grav) == -1)
104 if(init_particle_attr(&attr->part_attr) == -1)
107 attr->max_particles = -1;
109 anm_set_track_default(&attr->size.value.trk, 1.0);
110 anm_set_track_default(&attr->life.value.trk, 1.0);
112 attr->blending = PSYS_ADD;
116 psys_destroy_attr(attr);
121 static int init_particle_attr(struct psys_particle_attributes *pattr)
123 if(psys_init_track3(&pattr->color) == -1) {
126 if(psys_init_track(&pattr->alpha) == -1) {
127 psys_destroy_track3(&pattr->color);
130 if(psys_init_track(&pattr->size) == -1) {
131 psys_destroy_track3(&pattr->color);
132 psys_destroy_track(&pattr->alpha);
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);
145 void psys_destroy_attr(struct psys_attributes *attr)
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);
154 destroy_particle_attr(&attr->part_attr);
156 if(attr->tex && unload_texture) {
157 unload_texture(attr->tex, tex_cls);
161 static void destroy_particle_attr(struct psys_particle_attributes *pattr)
163 psys_destroy_track3(&pattr->color);
164 psys_destroy_track(&pattr->alpha);
165 psys_destroy_track(&pattr->size);
168 void psys_copy_attr(struct psys_attributes *dest, const struct psys_attributes *src)
170 dest->tex = src->tex;
172 psys_copy_track3(&dest->spawn_range, &src->spawn_range);
173 psys_copy_track(&dest->rate, &src->rate);
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);
179 psys_copy_track3(&dest->grav, &src->grav);
181 dest->drag = src->drag;
182 dest->max_particles = src->max_particles;
184 dest->blending = src->blending;
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);
192 void psys_eval_attr(struct psys_attributes *attr, anm_time_t tm)
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);
204 int psys_load_attr(struct psys_attributes *attr, const char *fname)
213 if(!(fp = fopen(fname, "r"))) {
214 fprintf(stderr, "psys_load_attr: failed to read file: %s: %s\n", fname, strerror(errno));
217 res = psys_load_attr_stream(attr, fp);
222 int psys_load_attr_stream(struct psys_attributes *attr, FILE *fp)
226 struct cfgopt *opt = 0;
228 psys_init_attr(attr);
230 while(fgets(buf, sizeof buf, fp)) {
234 if(!(opt = get_cfg_opt(buf))) {
238 if(strcmp(opt->name, "texture") == 0) {
239 if(opt->type != OPT_STR) {
243 fprintf(stderr, "particle system requests a texture, but no texture loader available!\n");
246 if(!(attr->tex = load_texture(opt->valstr, tex_cls))) {
247 fprintf(stderr, "failed to load texture: %s\n", opt->valstr);
251 release_cfg_opt(opt);
254 } else if(strcmp(opt->name, "blending") == 0) {
255 if(opt->type != OPT_STR) {
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;
265 fprintf(stderr, "invalid blending mode: %s\n", opt->valstr);
269 release_cfg_opt(opt);
272 } else if(opt->type == OPT_STR) {
273 fprintf(stderr, "invalid particle config: '%s'\n", opt->name);
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]);
298 fprintf(stderr, "unrecognized particle config option: %s\n", opt->name);
302 release_cfg_opt(opt);
308 fprintf(stderr, "Line %d: error parsing particle definition\n", lineno);
309 release_cfg_opt(opt);
313 static struct cfgopt *get_cfg_opt(const char *line)
318 /* allocate a working buffer on the stack that could fit the current line */
319 buf = alloca(strlen(line) + 1);
321 line = stripspace((char*)line);
322 if(line[0] == '#' || !line[0]) {
323 return 0; /* skip empty lines and comments */
326 if(!(opt = malloc(sizeof *opt))) {
329 memset(opt, 0, sizeof *opt);
331 if(!(opt->valstr = strchr(line, '='))) {
332 release_cfg_opt(opt);
336 opt->valstr = stripspace(opt->valstr);
339 buf = stripspace(buf);
341 /* parse the keyframe time specifier if it exists */
342 if((tmp = strchr(buf, '('))) {
347 opt->name = malloc(strlen(buf) + 1);
348 strcpy(opt->name, buf);
350 tval = strtod(tmp, &endp);
351 if(endp == tmp) { /* nada ... */
353 } else if(*endp == 's') { /* seconds suffix */
354 opt->tm = (long)(tval * 1000.0f);
356 opt->tm = (long)tval;
359 opt->name = malloc(strlen(buf) + 1);
360 strcpy(opt->name, buf);
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;
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];
375 } else if(sscanf(opt->valstr, "[%f %f %f]", opt->val, opt->val + 1, opt->val + 2) == 3) {
376 /* value is a vector */
378 opt->valrng[0] = opt->valrng[1] = opt->valrng[2] = 0.0f;
380 } else if(sscanf(opt->valstr, "%f", opt->val) == 1) {
381 /* value is a number */
383 opt->val[1] = opt->val[2] = opt->val[0];
384 opt->valrng[0] = opt->valrng[1] = opt->valrng[2] = 0.0f;
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;
392 opt->valstr = malloc(strlen(buf) + 1);
394 strcpy(opt->valstr, buf);
397 release_cfg_opt(opt);
404 static void release_cfg_opt(struct cfgopt *opt)
414 int psys_save_attr(const struct psys_attributes *attr, const char *fname)
419 if(!(fp = fopen(fname, "w"))) {
420 fprintf(stderr, "psys_save_attr: failed to write file: %s: %s\n", fname, strerror(errno));
423 res = psys_save_attr_stream(attr, fp);
428 int psys_save_attr_stream(const struct psys_attributes *attr, FILE *fp)
430 return -1; /* TODO */
434 static char *stripspace(char *str)
438 while(*str && isspace(*str)) {
442 end = str + strlen(str) - 1;
443 while(end >= str && isspace(*end)) {