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/>.
47 float val[3], valrng[3];
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);
57 static unsigned int (*load_texture)(const char*, void*);
58 static void (*unload_texture)(unsigned int, void*);
61 void psys_texture_loader(unsigned int (*load)(const char*, void*), void (*unload)(unsigned int, void*), void *cls)
64 unload_texture = unload;
68 struct psys_attributes *psys_create_attr(void)
70 struct psys_attributes *attr = malloc(sizeof *attr);
72 if(psys_init_attr(attr) == -1) {
80 void psys_free_attr(struct psys_attributes *attr)
82 psys_destroy_attr(attr);
86 int psys_init_attr(struct psys_attributes *attr)
88 memset(attr, 0, sizeof *attr);
90 if(psys_init_track3(&attr->spawn_range) == -1)
92 if(psys_init_track(&attr->rate) == -1)
94 if(psys_init_anm_rnd(&attr->life) == -1)
96 if(psys_init_anm_rnd(&attr->size) == -1)
98 if(psys_init_anm_rnd3(&attr->dir) == -1)
100 if(psys_init_track3(&attr->grav) == -1)
103 if(init_particle_attr(&attr->part_attr) == -1)
106 attr->max_particles = -1;
108 anm_set_track_default(&attr->size.value.trk, 1.0);
109 anm_set_track_default(&attr->life.value.trk, 1.0);
111 attr->blending = PSYS_ADD;
115 psys_destroy_attr(attr);
120 static int init_particle_attr(struct psys_particle_attributes *pattr)
122 if(psys_init_track3(&pattr->color) == -1) {
125 if(psys_init_track(&pattr->alpha) == -1) {
126 psys_destroy_track3(&pattr->color);
129 if(psys_init_track(&pattr->size) == -1) {
130 psys_destroy_track3(&pattr->color);
131 psys_destroy_track(&pattr->alpha);
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);
144 void psys_destroy_attr(struct psys_attributes *attr)
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);
153 destroy_particle_attr(&attr->part_attr);
155 if(attr->tex && unload_texture) {
156 unload_texture(attr->tex, tex_cls);
160 static void destroy_particle_attr(struct psys_particle_attributes *pattr)
162 psys_destroy_track3(&pattr->color);
163 psys_destroy_track(&pattr->alpha);
164 psys_destroy_track(&pattr->size);
167 void psys_copy_attr(struct psys_attributes *dest, const struct psys_attributes *src)
169 dest->tex = src->tex;
171 psys_copy_track3(&dest->spawn_range, &src->spawn_range);
172 psys_copy_track(&dest->rate, &src->rate);
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);
178 psys_copy_track3(&dest->grav, &src->grav);
180 dest->drag = src->drag;
181 dest->max_particles = src->max_particles;
183 dest->blending = src->blending;
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);
191 void psys_eval_attr(struct psys_attributes *attr, anm_time_t tm)
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);
203 int psys_load_attr(struct psys_attributes *attr, const char *fname)
212 if(!(fp = fopen(fname, "r"))) {
213 fprintf(stderr, "psys_load_attr: failed to read file: %s: %s\n", fname, strerror(errno));
216 res = psys_load_attr_stream(attr, fp);
221 int psys_load_attr_stream(struct psys_attributes *attr, FILE *fp)
225 struct cfgopt *opt = 0;
227 psys_init_attr(attr);
229 while(fgets(buf, sizeof buf, fp)) {
233 if(!(opt = get_cfg_opt(buf))) {
237 if(strcmp(opt->name, "texture") == 0) {
238 if(opt->type != OPT_STR) {
242 fprintf(stderr, "particle system requests a texture, but no texture loader available!\n");
245 if(!(attr->tex = load_texture(opt->valstr, tex_cls))) {
246 fprintf(stderr, "failed to load texture: %s\n", opt->valstr);
250 release_cfg_opt(opt);
253 } else if(strcmp(opt->name, "blending") == 0) {
254 if(opt->type != OPT_STR) {
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;
264 fprintf(stderr, "invalid blending mode: %s\n", opt->valstr);
268 release_cfg_opt(opt);
271 } else if(opt->type == OPT_STR) {
272 fprintf(stderr, "invalid particle config: '%s'\n", opt->name);
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]);
297 fprintf(stderr, "unrecognized particle config option: %s\n", opt->name);
301 release_cfg_opt(opt);
307 fprintf(stderr, "Line %d: error parsing particle definition\n", lineno);
308 release_cfg_opt(opt);
312 static struct cfgopt *get_cfg_opt(const char *line)
317 /* allocate a working buffer on the stack that could fit the current line */
318 buf = alloca(strlen(line) + 1);
320 line = stripspace((char*)line);
321 if(line[0] == '#' || !line[0]) {
322 return 0; /* skip empty lines and comments */
325 if(!(opt = malloc(sizeof *opt))) {
328 memset(opt, 0, sizeof *opt);
330 if(!(opt->valstr = strchr(line, '='))) {
331 release_cfg_opt(opt);
335 opt->valstr = stripspace(opt->valstr);
338 buf = stripspace(buf);
340 /* parse the keyframe time specifier if it exists */
341 if((tmp = strchr(buf, '('))) {
346 opt->name = malloc(strlen(buf) + 1);
347 strcpy(opt->name, buf);
349 tval = strtod(tmp, &endp);
350 if(endp == tmp) { /* nada ... */
352 } else if(*endp == 's') { /* seconds suffix */
353 opt->tm = (long)(tval * 1000.0f);
355 opt->tm = (long)tval;
358 opt->name = malloc(strlen(buf) + 1);
359 strcpy(opt->name, buf);
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;
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];
374 } else if(sscanf(opt->valstr, "[%f %f %f]", opt->val, opt->val + 1, opt->val + 2) == 3) {
375 /* value is a vector */
377 opt->valrng[0] = opt->valrng[1] = opt->valrng[2] = 0.0f;
379 } else if(sscanf(opt->valstr, "%f", opt->val) == 1) {
380 /* value is a number */
382 opt->val[1] = opt->val[2] = opt->val[0];
383 opt->valrng[0] = opt->valrng[1] = opt->valrng[2] = 0.0f;
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;
391 opt->valstr = malloc(strlen(buf) + 1);
393 strcpy(opt->valstr, buf);
396 release_cfg_opt(opt);
403 static void release_cfg_opt(struct cfgopt *opt)
413 int psys_save_attr(const struct psys_attributes *attr, const char *fname)
418 if(!(fp = fopen(fname, "w"))) {
419 fprintf(stderr, "psys_save_attr: failed to write file: %s: %s\n", fname, strerror(errno));
422 res = psys_save_attr_stream(attr, fp);
427 int psys_save_attr_stream(const struct psys_attributes *attr, FILE *fp)
429 return -1; /* TODO */
433 static char *stripspace(char *str)
437 while(*str && isspace(*str)) {
441 end = str + strlen(str) - 1;
442 while(end >= str && isspace(*end)) {