added cgmath, libanim, and libpsys
[andemo] / libs / psys / pattr.c
diff --git a/libs/psys/pattr.c b/libs/psys/pattr.c
new file mode 100644 (file)
index 0000000..9f0f9ad
--- /dev/null
@@ -0,0 +1,447 @@
+/*
+libpsys - reusable particle system library.
+Copyright (C) 2011-2018  John Tsiombikas <nuclear@member.fsf.org>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License
+along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+#define _GNU_SOURCE
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <ctype.h>
+#include <assert.h>
+
+#ifdef __MSVCRT__
+#include <malloc.h>
+#else
+#include <alloca.h>
+#endif
+
+#include "pattr.h"
+#include "psys_gl.h"
+
+enum {
+       OPT_STR,
+       OPT_NUM,
+       OPT_NUM_RANGE,
+       OPT_VEC,
+       OPT_VEC_RANGE
+};
+
+struct cfgopt {
+       char *name;
+       int type;
+       long tm;
+       char *valstr;
+       float val[3], valrng[3];
+};
+
+static int init_particle_attr(struct psys_particle_attributes *pattr);
+static void destroy_particle_attr(struct psys_particle_attributes *pattr);
+static struct cfgopt *get_cfg_opt(const char *line);
+static void release_cfg_opt(struct cfgopt *opt);
+static char *stripspace(char *str);
+
+static void *tex_cls;
+static unsigned int (*load_texture)(const char*, void*) = psys_gl_load_texture;
+static void (*unload_texture)(unsigned int, void*) = psys_gl_unload_texture;
+
+
+void psys_texture_loader(unsigned int (*load)(const char*, void*), void (*unload)(unsigned int, void*), void *cls)
+{
+       load_texture = load;
+       unload_texture = unload;
+       tex_cls = cls;
+}
+
+struct psys_attributes *psys_create_attr(void)
+{
+       struct psys_attributes *attr = malloc(sizeof *attr);
+       if(attr) {
+               if(psys_init_attr(attr) == -1) {
+                       free(attr);
+                       attr = 0;
+               }
+       }
+       return attr;
+}
+
+void psys_free_attr(struct psys_attributes *attr)
+{
+       psys_destroy_attr(attr);
+       free(attr);
+}
+
+int psys_init_attr(struct psys_attributes *attr)
+{
+       memset(attr, 0, sizeof *attr);
+
+       if(psys_init_track3(&attr->spawn_range) == -1)
+               goto err;
+       if(psys_init_track(&attr->rate) == -1)
+               goto err;
+       if(psys_init_anm_rnd(&attr->life) == -1)
+               goto err;
+       if(psys_init_anm_rnd(&attr->size) == -1)
+               goto err;
+       if(psys_init_anm_rnd3(&attr->dir) == -1)
+               goto err;
+       if(psys_init_track3(&attr->grav) == -1)
+               goto err;
+
+       if(init_particle_attr(&attr->part_attr) == -1)
+               goto err;
+
+       attr->max_particles = -1;
+
+       anm_set_track_default(&attr->size.value.trk, 1.0);
+       anm_set_track_default(&attr->life.value.trk, 1.0);
+
+       attr->blending = PSYS_ADD;
+       return 0;
+
+err:
+       psys_destroy_attr(attr);
+       return -1;
+}
+
+
+static int init_particle_attr(struct psys_particle_attributes *pattr)
+{
+       if(psys_init_track3(&pattr->color) == -1) {
+               return -1;
+       }
+       if(psys_init_track(&pattr->alpha) == -1) {
+               psys_destroy_track3(&pattr->color);
+               return -1;
+       }
+       if(psys_init_track(&pattr->size) == -1) {
+               psys_destroy_track3(&pattr->color);
+               psys_destroy_track(&pattr->alpha);
+               return -1;
+       }
+
+       anm_set_track_default(&pattr->color.x, 1.0);
+       anm_set_track_default(&pattr->color.y, 1.0);
+       anm_set_track_default(&pattr->color.z, 1.0);
+       anm_set_track_default(&pattr->alpha.trk, 1.0);
+       anm_set_track_default(&pattr->size.trk, 1.0);
+       return 0;
+}
+
+
+void psys_destroy_attr(struct psys_attributes *attr)
+{
+       psys_destroy_track3(&attr->spawn_range);
+       psys_destroy_track(&attr->rate);
+       psys_destroy_anm_rnd(&attr->life);
+       psys_destroy_anm_rnd(&attr->size);
+       psys_destroy_anm_rnd3(&attr->dir);
+       psys_destroy_track3(&attr->grav);
+
+       destroy_particle_attr(&attr->part_attr);
+
+       if(attr->tex && unload_texture) {
+               unload_texture(attr->tex, tex_cls);
+       }
+}
+
+static void destroy_particle_attr(struct psys_particle_attributes *pattr)
+{
+       psys_destroy_track3(&pattr->color);
+       psys_destroy_track(&pattr->alpha);
+       psys_destroy_track(&pattr->size);
+}
+
+void psys_copy_attr(struct psys_attributes *dest, const struct psys_attributes *src)
+{
+       dest->tex = src->tex;
+
+       psys_copy_track3(&dest->spawn_range, &src->spawn_range);
+       psys_copy_track(&dest->rate, &src->rate);
+
+       psys_copy_anm_rnd(&dest->life, &src->life);
+       psys_copy_anm_rnd(&dest->size, &src->size);
+       psys_copy_anm_rnd3(&dest->dir, &src->dir);
+
+       psys_copy_track3(&dest->grav, &src->grav);
+
+       dest->drag = src->drag;
+       dest->max_particles = src->max_particles;
+
+       dest->blending = src->blending;
+
+       /* also copy the particle attributes */
+       psys_copy_track3(&dest->part_attr.color, &src->part_attr.color);
+       psys_copy_track(&dest->part_attr.alpha, &src->part_attr.alpha);
+       psys_copy_track(&dest->part_attr.size, &src->part_attr.size);
+}
+
+void psys_eval_attr(struct psys_attributes *attr, anm_time_t tm)
+{
+       float tmp[3];
+
+       psys_eval_track3(&attr->spawn_range, tm);
+       psys_eval_track(&attr->rate, tm);
+       psys_eval_anm_rnd(&attr->life, tm);
+       psys_eval_anm_rnd(&attr->size, tm);
+       psys_eval_anm_rnd3(&attr->dir, tm, tmp);
+       psys_eval_track3(&attr->grav, tm);
+}
+
+int psys_load_attr(struct psys_attributes *attr, const char *fname)
+{
+       FILE *fp;
+       int res;
+
+       if(!fname) {
+               return -1;
+       }
+
+       if(!(fp = fopen(fname, "r"))) {
+               fprintf(stderr, "psys_load_attr: failed to read file: %s: %s\n", fname, strerror(errno));
+               return -1;
+       }
+       res = psys_load_attr_stream(attr, fp);
+       fclose(fp);
+       return res;
+}
+
+int psys_load_attr_stream(struct psys_attributes *attr, FILE *fp)
+{
+       int lineno = 0;
+       char buf[512];
+       struct cfgopt *opt = 0;
+
+       psys_init_attr(attr);
+
+       while(fgets(buf, sizeof buf, fp)) {
+
+               lineno++;
+
+               if(!(opt = get_cfg_opt(buf))) {
+                       continue;
+               }
+
+               if(strcmp(opt->name, "texture") == 0) {
+                       if(opt->type != OPT_STR) {
+                               goto err;
+                       }
+                       if(!load_texture) {
+                               fprintf(stderr, "particle system requests a texture, but no texture loader available!\n");
+                               goto err;
+                       }
+                       if(!(attr->tex = load_texture(opt->valstr, tex_cls))) {
+                               fprintf(stderr, "failed to load texture: %s\n", opt->valstr);
+                               goto err;
+                       }
+
+                       release_cfg_opt(opt);
+                       continue;
+
+               } else if(strcmp(opt->name, "blending") == 0) {
+                       if(opt->type != OPT_STR) {
+                               goto err;
+                       }
+
+                       /* parse blending mode */
+                       if(strcmp(opt->valstr, "add") == 0 || strcmp(opt->valstr, "additive") == 0) {
+                               attr->blending = PSYS_ADD;
+                       } else if(strcmp(opt->valstr, "alpha") == 0) {
+                               attr->blending = PSYS_ALPHA;
+                       } else {
+                               fprintf(stderr, "invalid blending mode: %s\n", opt->valstr);
+                               goto err;
+                       }
+
+                       release_cfg_opt(opt);
+                       continue;
+
+               } else if(opt->type == OPT_STR) {
+                       fprintf(stderr, "invalid particle config: '%s'\n", opt->name);
+                       goto err;
+               }
+
+               if(strcmp(opt->name, "spawn_range") == 0) {
+                       psys_set_value3(&attr->spawn_range, opt->tm, opt->val[0], opt->val[1], opt->val[2]);
+               } else if(strcmp(opt->name, "rate") == 0) {
+                       psys_set_value(&attr->rate, opt->tm, opt->val[0]);
+               } else if(strcmp(opt->name, "life") == 0) {
+                       psys_set_anm_rnd(&attr->life, opt->tm, opt->val[0], opt->valrng[0]);
+               } else if(strcmp(opt->name, "size") == 0) {
+                       psys_set_anm_rnd(&attr->size, opt->tm, opt->val[0], opt->valrng[0]);
+               } else if(strcmp(opt->name, "dir") == 0) {
+                       psys_set_anm_rnd3(&attr->dir, opt->tm, opt->val, opt->valrng);
+               } else if(strcmp(opt->name, "grav") == 0) {
+                       psys_set_value3(&attr->grav, opt->tm, opt->val[0], opt->val[1], opt->val[2]);
+               } else if(strcmp(opt->name, "drag") == 0) {
+                       attr->drag = opt->val[0];
+               } else if(strcmp(opt->name, "pcolor") == 0) {
+                       psys_set_value3(&attr->part_attr.color, opt->tm, opt->val[0], opt->val[1], opt->val[2]);
+               } else if(strcmp(opt->name, "palpha") == 0) {
+                       psys_set_value(&attr->part_attr.alpha, opt->tm, opt->val[0]);
+               } else if(strcmp(opt->name, "psize") == 0) {
+                       psys_set_value(&attr->part_attr.size, opt->tm, opt->val[0]);
+               } else {
+                       fprintf(stderr, "unrecognized particle config option: %s\n", opt->name);
+                       goto err;
+               }
+
+               release_cfg_opt(opt);
+       }
+
+       return 0;
+
+err:
+       fprintf(stderr, "Line %d: error parsing particle definition\n", lineno);
+       release_cfg_opt(opt);
+       return -1;
+}
+
+static struct cfgopt *get_cfg_opt(const char *line)
+{
+       char *buf, *tmp;
+       struct cfgopt *opt;
+
+       /* allocate a working buffer on the stack that could fit the current line */
+       buf = alloca(strlen(line) + 1);
+
+       line = stripspace((char*)line);
+       if(line[0] == '#' || !line[0]) {
+               return 0;       /* skip empty lines and comments */
+       }
+
+       if(!(opt = malloc(sizeof *opt))) {
+               return 0;
+       }
+       memset(opt, 0, sizeof *opt);
+
+       if(!(opt->valstr = strchr(line, '='))) {
+               release_cfg_opt(opt);
+               return 0;
+       }
+       *opt->valstr++ = 0;
+       opt->valstr = stripspace(opt->valstr);
+
+       strcpy(buf, line);
+       buf = stripspace(buf);
+
+       /* parse the keyframe time specifier if it exists */
+       if((tmp = strchr(buf, '('))) {
+               char *endp;
+               float tval;
+
+               *tmp++ = 0;
+               opt->name = malloc(strlen(buf) + 1);
+               strcpy(opt->name, buf);
+
+               tval = strtod(tmp, &endp);
+               if(endp == tmp) { /* nada ... */
+                       opt->tm = 0;
+               } else if(*endp == 's') {       /* seconds suffix */
+                       opt->tm = (long)(tval * 1000.0f);
+               } else {
+                       opt->tm = (long)tval;
+               }
+       } else {
+               opt->name = malloc(strlen(buf) + 1);
+               strcpy(opt->name, buf);
+               opt->tm = 0;
+       }
+
+       if(sscanf(opt->valstr, "[%f %f %f] ~ [%f %f %f]", opt->val, opt->val + 1, opt->val + 2,
+                               opt->valrng, opt->valrng + 1, opt->valrng + 2) == 6) {
+               /* value is a vector range */
+               opt->type = OPT_VEC_RANGE;
+
+       } else if(sscanf(opt->valstr, "%f ~ %f", opt->val, opt->valrng) == 2) {
+               /* value is a number range */
+               opt->type = OPT_NUM_RANGE;
+               opt->val[1] = opt->val[2] = opt->val[0];
+               opt->valrng[1] = opt->valrng[2] = opt->valrng[0];
+
+       } else if(sscanf(opt->valstr, "[%f %f %f]", opt->val, opt->val + 1, opt->val + 2) == 3) {
+               /* value is a vector */
+               opt->type = OPT_VEC;
+               opt->valrng[0] = opt->valrng[1] = opt->valrng[2] = 0.0f;
+
+       } else if(sscanf(opt->valstr, "%f", opt->val) == 1) {
+               /* value is a number */
+               opt->type = OPT_NUM;
+               opt->val[1] = opt->val[2] = opt->val[0];
+               opt->valrng[0] = opt->valrng[1] = opt->valrng[2] = 0.0f;
+
+       } else if(sscanf(opt->valstr, "\"%s\"", buf) == 1) {
+               /* just a string... strip the quotes */
+               if(buf[strlen(buf) - 1] == '\"') {
+                       buf[strlen(buf) - 1] = 0;
+               }
+               opt->type = OPT_STR;
+               opt->valstr = malloc(strlen(buf) + 1);
+               assert(opt->valstr);
+               strcpy(opt->valstr, buf);
+       } else {
+               /* fuck it ... */
+               release_cfg_opt(opt);
+               return 0;
+       }
+
+       return opt;
+}
+
+static void release_cfg_opt(struct cfgopt *opt)
+{
+       if(opt) {
+               free(opt->name);
+               opt->name = 0;
+       }
+       opt = 0;
+}
+
+
+int psys_save_attr(const struct psys_attributes *attr, const char *fname)
+{
+       FILE *fp;
+       int res;
+
+       if(!(fp = fopen(fname, "w"))) {
+               fprintf(stderr, "psys_save_attr: failed to write file: %s: %s\n", fname, strerror(errno));
+               return -1;
+       }
+       res = psys_save_attr_stream(attr, fp);
+       fclose(fp);
+       return res;
+}
+
+int psys_save_attr_stream(const struct psys_attributes *attr, FILE *fp)
+{
+       return -1;      /* TODO */
+}
+
+
+static char *stripspace(char *str)
+{
+       char *end;
+
+       while(*str && isspace(*str)) {
+               str++;
+       }
+
+       end = str + strlen(str) - 1;
+       while(end >= str && isspace(*end)) {
+               *end-- = 0;
+       }
+       return str;
+}