adding goat3d to the project
authorJohn Tsiombikas <nuclear@member.fsf.org>
Thu, 30 Mar 2023 21:57:34 +0000 (23:57 +0200)
committerJohn Tsiombikas <nuclear@member.fsf.org>
Thu, 30 Mar 2023 21:57:34 +0000 (23:57 +0200)
42 files changed:
Makefile.sgi
libs/Makefile
libs/cgmath/cgmath.h
libs/cgmath/cgmmat.inl
libs/cgmath/cgmmisc.inl
libs/cgmath/cgmquat.inl
libs/cgmath/cgmray.inl
libs/cgmath/cgmvec3.inl
libs/cgmath/cgmvec4.inl
libs/goat3d/COPYING [new file with mode: 0644]
libs/goat3d/COPYING.LESSER [new file with mode: 0644]
libs/goat3d/Makefile [new file with mode: 0644]
libs/goat3d/README.md [new file with mode: 0644]
libs/goat3d/include/goat3d.h [new file with mode: 0644]
libs/goat3d/src/aabox.c [new file with mode: 0644]
libs/goat3d/src/aabox.h [new file with mode: 0644]
libs/goat3d/src/chunk.c [new file with mode: 0644]
libs/goat3d/src/chunk.h [new file with mode: 0644]
libs/goat3d/src/dynarr.c [new file with mode: 0644]
libs/goat3d/src/dynarr.h [new file with mode: 0644]
libs/goat3d/src/extmesh.c [new file with mode: 0644]
libs/goat3d/src/g3danm.c [new file with mode: 0644]
libs/goat3d/src/g3danm.h [new file with mode: 0644]
libs/goat3d/src/g3dscn.c [new file with mode: 0644]
libs/goat3d/src/g3dscn.h [new file with mode: 0644]
libs/goat3d/src/goat3d.c [new file with mode: 0644]
libs/goat3d/src/log.c [new file with mode: 0644]
libs/goat3d/src/log.h [new file with mode: 0644]
libs/goat3d/src/read.c [new file with mode: 0644]
libs/goat3d/src/track.c [new file with mode: 0644]
libs/goat3d/src/track.h [new file with mode: 0644]
libs/goat3d/src/write.c [new file with mode: 0644]
libs/imago/Makefile
libs/treestor/LICENSE [new file with mode: 0644]
libs/treestor/Makefile [new file with mode: 0644]
libs/treestor/README.md [new file with mode: 0644]
libs/treestor/include/treestor.h [new file with mode: 0644]
libs/treestor/src/dynarr.c [new file with mode: 0644]
libs/treestor/src/dynarr.h [new file with mode: 0644]
libs/treestor/src/text.c [new file with mode: 0644]
libs/treestor/src/treestor.c [new file with mode: 0644]
src/scr_game.c

index d924436..fe4c6e9 100644 (file)
@@ -1,27 +1,33 @@
-obj = src/main.o src/miniglut.o src/game.o src/scr_menu.o src/scr_game.o src/util.o
+obj = src/main.o src/miniglut.o src/game.o src/scr_menu.o src/scr_game.o \
+         src/util.o
 bin = game
 
 dbg = -g
 opt = -O3
 def = -DMINIGLUT_USE_LIBC
+inc = -Ilibs -Ilibs/imago -Ilibs/treestor/include -Ilibs/goat3d/include
+libs = libs/unix/imago.a libs/unix/goat3d.a libs/unix/treestor.a
 
 CFLAGS = $(warn) $(dbg) $(opt) $(inc) $(def)
-LDFLAGS = -lGL -lGLU -lX11 -lm
+LDFLAGS = $(libs) -lGL -lGLU -lX11 -lm
 
 $(bin): $(obj) libs
        $(CC) -o $@ $(obj) $(LDFLAGS)
 
 .c.o:
-       $(CC) -c $< $(CFLAGS) -o $@
+       $(CC) $(CFLAGS) -c $< -o $@
 
 .PHONY: clean
 clean:
        rm -f $(obj) $(bin)
 
 .PHONY: libs
-libs:
-       $(MAKE) -C libs
+libs: make-libs
+
+.PHONY: make-libs
+make-libs:
+       cd libs; $(MAKE)
 
 .PHONY: clean-libs
 clean-libs:
-       $(MAKE) -C libs clean
+       cd libs; $(MAKE) clean
index e197eb2..757c5d0 100644 (file)
@@ -1,14 +1,38 @@
 .PHONY: all
-all: imago
+all: make-imago make-treestor make-goat3d
 
 .PHONY: clean
-clean: clean-imago
-
+clean: clean-imago clean-treestor clean-goat3d
 
 .PHONY: imago
-imago:
-       $(MAKE) -C imago
+imago: make-imago
+
+.PHONY: make-imago
+make-imago:
+       cd imago; $(MAKE)
 
 .PHONY: clean-imago
 clean-imago:
-       $(MAKE) -C imago clean
+       cd imago; $(MAKE) clean
+
+.PHONY: treestor
+treestor: make-treestor
+
+.PHONY: make-treestor
+make-treestor:
+       cd treestor; $(MAKE)
+
+.PHONY: clean-treestor
+clean-treestor:
+       cd treestor; $(MAKE) clean
+
+.PHONY: goat3d
+goat3d: make-goat3d
+
+.PHONY: make-goat3d
+make-goat3d:
+       cd goat3d; $(MAKE)
+
+.PHONY: clean-goat3d
+clean-goat3d:
+       cd goat3d; $(MAKE) clean
index c41608d..d1ead57 100644 (file)
@@ -59,196 +59,204 @@ typedef enum cgm_euler_mode {
 
 #ifdef __cplusplus
 extern "C" {
+#else
+
+#if (__STDC_VERSION__ >= 199901) || defined(__GNUC__)
+#define CGM_INLINE inline
+#else
+#define CGM_INLINE __inline
+#endif
+
 #endif
 
 /* --- operations on cgm_vec3 --- */
-static inline void cgm_vcons(cgm_vec3 *v, float x, float y, float z);
-
-static inline void cgm_vadd(cgm_vec3 *a, const cgm_vec3 *b);
-static inline void cgm_vadd_scaled(cgm_vec3 *a, const cgm_vec3 *b, float s); /* a+b*s */
-static inline void cgm_vsub(cgm_vec3 *a, const cgm_vec3 *b);
-static inline void cgm_vsub_scaled(cgm_vec3 *a, const cgm_vec3 *b, float s); /* a-b*s */
-static inline void cgm_vmul(cgm_vec3 *a, const cgm_vec3 *b);
-static inline void cgm_vscale(cgm_vec3 *v, float s);
-static inline void cgm_vmul_m4v3(cgm_vec3 *v, const float *m); /* m4x4 * v */
-static inline void cgm_vmul_v3m4(cgm_vec3 *v, const float *m); /* v * m4x4 */
-static inline void cgm_vmul_m3v3(cgm_vec3 *v, const float *m); /* m3x3 * v (m still 16 floats) */
-static inline void cgm_vmul_v3m3(cgm_vec3 *v, const float *m); /* v * m3x3 (m still 16 floats) */
-
-static inline float cgm_vdot(const cgm_vec3 *a, const cgm_vec3 *b);
-static inline void cgm_vcross(cgm_vec3 *res, const cgm_vec3 *a, const cgm_vec3 *b);
-static inline float cgm_vlength(const cgm_vec3 *v);
-static inline float cgm_vlength_sq(const cgm_vec3 *v);
-static inline float cgm_vdist(const cgm_vec3 *a, const cgm_vec3 *b);
-static inline float cgm_vdist_sq(const cgm_vec3 *a, const cgm_vec3 *b);
-static inline void cgm_vnormalize(cgm_vec3 *v);
-
-static inline void cgm_vreflect(cgm_vec3 *v, const cgm_vec3 *n);
-static inline void cgm_vrefract(cgm_vec3 *v, const cgm_vec3 *n, float ior);
-
-static inline void cgm_vrotate_quat(cgm_vec3 *v, const cgm_quat *q);
-static inline void cgm_vrotate_axis(cgm_vec3 *v, int axis, float angle);
-static inline void cgm_vrotate(cgm_vec3 *v, float angle, float x, float y, float z);
-static inline void cgm_vrotate_euler(cgm_vec3 *v, float a, float b, float c, enum cgm_euler_mode mode);
-
-static inline void cgm_vlerp(cgm_vec3 *res, const cgm_vec3 *a, const cgm_vec3 *b, float t);
+static CGM_INLINE void cgm_vcons(cgm_vec3 *v, float x, float y, float z);
+
+static CGM_INLINE void cgm_vadd(cgm_vec3 *a, const cgm_vec3 *b);
+static CGM_INLINE void cgm_vadd_scaled(cgm_vec3 *a, const cgm_vec3 *b, float s); /* a+b*s */
+static CGM_INLINE void cgm_vsub(cgm_vec3 *a, const cgm_vec3 *b);
+static CGM_INLINE void cgm_vsub_scaled(cgm_vec3 *a, const cgm_vec3 *b, float s); /* a-b*s */
+static CGM_INLINE void cgm_vmul(cgm_vec3 *a, const cgm_vec3 *b);
+static CGM_INLINE void cgm_vscale(cgm_vec3 *v, float s);
+static CGM_INLINE void cgm_vmul_m4v3(cgm_vec3 *v, const float *m);     /* m4x4 * v */
+static CGM_INLINE void cgm_vmul_v3m4(cgm_vec3 *v, const float *m);     /* v * m4x4 */
+static CGM_INLINE void cgm_vmul_m3v3(cgm_vec3 *v, const float *m);     /* m3x3 * v (m still 16 floats) */
+static CGM_INLINE void cgm_vmul_v3m3(cgm_vec3 *v, const float *m);     /* v * m3x3 (m still 16 floats) */
+
+static CGM_INLINE float cgm_vdot(const cgm_vec3 *a, const cgm_vec3 *b);
+static CGM_INLINE void cgm_vcross(cgm_vec3 *res, const cgm_vec3 *a, const cgm_vec3 *b);
+static CGM_INLINE float cgm_vlength(const cgm_vec3 *v);
+static CGM_INLINE float cgm_vlength_sq(const cgm_vec3 *v);
+static CGM_INLINE float cgm_vdist(const cgm_vec3 *a, const cgm_vec3 *b);
+static CGM_INLINE float cgm_vdist_sq(const cgm_vec3 *a, const cgm_vec3 *b);
+static CGM_INLINE void cgm_vnormalize(cgm_vec3 *v);
+
+static CGM_INLINE void cgm_vreflect(cgm_vec3 *v, const cgm_vec3 *n);
+static CGM_INLINE void cgm_vrefract(cgm_vec3 *v, const cgm_vec3 *n, float ior);
+
+static CGM_INLINE void cgm_vrotate_quat(cgm_vec3 *v, const cgm_quat *q);
+static CGM_INLINE void cgm_vrotate_axis(cgm_vec3 *v, int axis, float angle);
+static CGM_INLINE void cgm_vrotate(cgm_vec3 *v, float angle, float x, float y, float z);
+static CGM_INLINE void cgm_vrotate_euler(cgm_vec3 *v, float a, float b, float c, enum cgm_euler_mode mode);
+
+static CGM_INLINE void cgm_vlerp(cgm_vec3 *res, const cgm_vec3 *a, const cgm_vec3 *b, float t);
 
 #define cgm_velem(vptr, idx)   ((&(vptr)->x)[idx])
 
 /* --- operations on cgm_vec4 --- */
-static inline void cgm_wcons(cgm_vec4 *v, float x, float y, float z, float w);
+static CGM_INLINE void cgm_wcons(cgm_vec4 *v, float x, float y, float z, float w);
 
-static inline void cgm_wadd(cgm_vec4 *a, const cgm_vec4 *b);
-static inline void cgm_wsub(cgm_vec4 *a, const cgm_vec4 *b);
-static inline void cgm_wmul(cgm_vec4 *a, const cgm_vec4 *b);
-static inline void cgm_wscale(cgm_vec4 *v, float s);
+static CGM_INLINE void cgm_wadd(cgm_vec4 *a, const cgm_vec4 *b);
+static CGM_INLINE void cgm_wsub(cgm_vec4 *a, const cgm_vec4 *b);
+static CGM_INLINE void cgm_wmul(cgm_vec4 *a, const cgm_vec4 *b);
+static CGM_INLINE void cgm_wscale(cgm_vec4 *v, float s);
 
-static inline void cgm_wmul_m4v4(cgm_vec4 *v, const float *m);
-static inline void cgm_wmul_v4m4(cgm_vec4 *v, const float *m);
-static inline void cgm_wmul_m34v4(cgm_vec4 *v, const float *m);        /* doesn't affect w */
-static inline void cgm_wmul_v4m43(cgm_vec4 *v, const float *m);        /* doesn't affect w */
-static inline void cgm_wmul_m3v4(cgm_vec4 *v, const float *m); /* (m still 16 floats) */
-static inline void cgm_wmul_v4m3(cgm_vec4 *v, const float *m); /* (m still 16 floats) */
+static CGM_INLINE void cgm_wmul_m4v4(cgm_vec4 *v, const float *m);
+static CGM_INLINE void cgm_wmul_v4m4(cgm_vec4 *v, const float *m);
+static CGM_INLINE void cgm_wmul_m34v4(cgm_vec4 *v, const float *m);    /* doesn't affect w */
+static CGM_INLINE void cgm_wmul_v4m43(cgm_vec4 *v, const float *m);    /* doesn't affect w */
+static CGM_INLINE void cgm_wmul_m3v4(cgm_vec4 *v, const float *m); /* (m still 16 floats) */
+static CGM_INLINE void cgm_wmul_v4m3(cgm_vec4 *v, const float *m); /* (m still 16 floats) */
 
-static inline float cgm_wdot(const cgm_vec4 *a, const cgm_vec4 *b);
+static CGM_INLINE float cgm_wdot(const cgm_vec4 *a, const cgm_vec4 *b);
 
-static inline float cgm_wlength(const cgm_vec4 *v);
-static inline float cgm_wlength_sq(const cgm_vec4 *v);
-static inline float cgm_wdist(const cgm_vec4 *a, const cgm_vec4 *b);
-static inline float cgm_wdist_sq(const cgm_vec4 *a, const cgm_vec4 *b);
-static inline void cgm_wnormalize(cgm_vec4 *v);
+static CGM_INLINE float cgm_wlength(const cgm_vec4 *v);
+static CGM_INLINE float cgm_wlength_sq(const cgm_vec4 *v);
+static CGM_INLINE float cgm_wdist(const cgm_vec4 *a, const cgm_vec4 *b);
+static CGM_INLINE float cgm_wdist_sq(const cgm_vec4 *a, const cgm_vec4 *b);
+static CGM_INLINE void cgm_wnormalize(cgm_vec4 *v);
 
-static inline void cgm_wlerp(cgm_vec4 *res, const cgm_vec4 *a, const cgm_vec4 *b, float t);
+static CGM_INLINE void cgm_wlerp(cgm_vec4 *res, const cgm_vec4 *a, const cgm_vec4 *b, float t);
 
 #define cgm_welem(vptr, idx)   ((&(vptr)->x)[idx])
 
 /* --- operations on quaternions --- */
-static inline void cgm_qcons(cgm_quat *q, float x, float y, float z, float w);
+static CGM_INLINE void cgm_qcons(cgm_quat *q, float x, float y, float z, float w);
 
-static inline void cgm_qneg(cgm_quat *q);
-static inline void cgm_qadd(cgm_quat *a, const cgm_quat *b);
-static inline void cgm_qsub(cgm_quat *a, const cgm_quat *b);
-static inline void cgm_qmul(cgm_quat *a, const cgm_quat *b);
+static CGM_INLINE void cgm_qneg(cgm_quat *q);
+static CGM_INLINE void cgm_qadd(cgm_quat *a, const cgm_quat *b);
+static CGM_INLINE void cgm_qsub(cgm_quat *a, const cgm_quat *b);
+static CGM_INLINE void cgm_qmul(cgm_quat *a, const cgm_quat *b);
 
-static inline float cgm_qlength(const cgm_quat *q);
-static inline float cgm_qlength_sq(const cgm_quat *q);
-static inline void cgm_qnormalize(cgm_quat *q);
-static inline void cgm_qconjugate(cgm_quat *q);
-static inline void cgm_qinvert(cgm_quat *q);
+static CGM_INLINE float cgm_qlength(const cgm_quat *q);
+static CGM_INLINE float cgm_qlength_sq(const cgm_quat *q);
+static CGM_INLINE void cgm_qnormalize(cgm_quat *q);
+static CGM_INLINE void cgm_qconjugate(cgm_quat *q);
+static CGM_INLINE void cgm_qinvert(cgm_quat *q);
 
-static inline void cgm_qrotation(cgm_quat *q, float angle, float x, float y, float z);
-static inline void cgm_qrotate(cgm_quat *q, float angle, float x, float y, float z);
+static CGM_INLINE void cgm_qrotation(cgm_quat *q, float angle, float x, float y, float z);
+static CGM_INLINE void cgm_qrotate(cgm_quat *q, float angle, float x, float y, float z);
 
-static inline void cgm_qslerp(cgm_quat *res, const cgm_quat *a, const cgm_quat *b, float t);
-static inline void cgm_qlerp(cgm_quat *res, const cgm_quat *a, const cgm_quat *b, float t);
+static CGM_INLINE void cgm_qslerp(cgm_quat *res, const cgm_quat *a, const cgm_quat *b, float t);
+static CGM_INLINE void cgm_qlerp(cgm_quat *res, const cgm_quat *a, const cgm_quat *b, float t);
 
 #define cgm_qelem(qptr, idx)   ((&(qptr)->x)[idx])
 
 /* --- operations on matrices --- */
-static inline void cgm_mcopy(float *dest, const float *src);
-static inline void cgm_mzero(float *m);
-static inline void cgm_midentity(float *m);
-
-static inline void cgm_mmul(float *a, const float *b);
-static inline void cgm_mpremul(float *a, const float *b);
-
-static inline void cgm_msubmatrix(float *m, int row, int col);
-static inline void cgm_mupper3(float *m);
-static inline float cgm_msubdet(const float *m, int row, int col);
-static inline float cgm_mcofactor(const float *m, int row, int col);
-static inline float cgm_mdet(const float *m);
-static inline void cgm_mtranspose(float *m);
-static inline void cgm_mcofmatrix(float *m);
-static inline int cgm_minverse(float *m);      /* returns 0 on success, -1 for singular */
-
-static inline void cgm_mtranslation(float *m, float x, float y, float z);
-static inline void cgm_mscaling(float *m, float sx, float sy, float sz);
-static inline void cgm_mrotation_x(float *m, float angle);
-static inline void cgm_mrotation_y(float *m, float angle);
-static inline void cgm_mrotation_z(float *m, float angle);
-static inline void cgm_mrotation_axis(float *m, int idx, float angle);
-static inline void cgm_mrotation(float *m, float angle, float x, float y, float z);
-static inline void cgm_mrotation_euler(float *m, float a, float b, float c, int mode);
-static inline void cgm_mrotation_quat(float *m, const cgm_quat *q);
-
-static inline void cgm_mtranslate(float *m, float x, float y, float z);
-static inline void cgm_mscale(float *m, float sx, float sy, float sz);
-static inline void cgm_mrotate_x(float *m, float angle);
-static inline void cgm_mrotate_y(float *m, float angle);
-static inline void cgm_mrotate_z(float *m, float angle);
-static inline void cgm_mrotate_axis(float *m, int idx, float angle);
-static inline void cgm_mrotate(float *m, float angle, float x, float y, float z);
-static inline void cgm_mrotate_euler(float *m, float a, float b, float c, int mode);
-static inline void cgm_mrotate_quat(float *m, const cgm_quat *q);
-
-static inline void cgm_mpretranslate(float *m, float x, float y, float z);
-static inline void cgm_mprescale(float *m, float sx, float sy, float sz);
-static inline void cgm_mprerotate_x(float *m, float angle);
-static inline void cgm_mprerotate_y(float *m, float angle);
-static inline void cgm_mprerotate_z(float *m, float angle);
-static inline void cgm_mprerotate_axis(float *m, int idx, float angle);
-static inline void cgm_mprerotate(float *m, float angle, float x, float y, float z);
-static inline void cgm_mprerotate_euler(float *m, float a, float b, float c, int mode);
-static inline void cgm_mprerotate_quat(float *m, const cgm_quat *q);
-
-static inline void cgm_mget_translation(const float *m, cgm_vec3 *res);
-static inline void cgm_mget_rotation(const float *m, cgm_quat *res);
-static inline void cgm_mget_scaling(const float *m, cgm_vec3 *res);
-static inline void cgm_mget_frustum_plane(const float *m, int p, cgm_vec4 *res);
-
-static inline void cgm_mlookat(float *m, const cgm_vec3 *pos, const cgm_vec3 *targ,
+static CGM_INLINE void cgm_mcopy(float *dest, const float *src);
+static CGM_INLINE void cgm_mzero(float *m);
+static CGM_INLINE void cgm_midentity(float *m);
+
+static CGM_INLINE void cgm_mmul(float *a, const float *b);
+static CGM_INLINE void cgm_mpremul(float *a, const float *b);
+
+static CGM_INLINE void cgm_msubmatrix(float *m, int row, int col);
+static CGM_INLINE void cgm_mupper3(float *m);
+static CGM_INLINE float cgm_msubdet(const float *m, int row, int col);
+static CGM_INLINE float cgm_mcofactor(const float *m, int row, int col);
+static CGM_INLINE float cgm_mdet(const float *m);
+static CGM_INLINE void cgm_mtranspose(float *m);
+static CGM_INLINE void cgm_mcofmatrix(float *m);
+static CGM_INLINE int cgm_minverse(float *m);  /* returns 0 on success, -1 for singular */
+
+static CGM_INLINE void cgm_mtranslation(float *m, float x, float y, float z);
+static CGM_INLINE void cgm_mscaling(float *m, float sx, float sy, float sz);
+static CGM_INLINE void cgm_mrotation_x(float *m, float angle);
+static CGM_INLINE void cgm_mrotation_y(float *m, float angle);
+static CGM_INLINE void cgm_mrotation_z(float *m, float angle);
+static CGM_INLINE void cgm_mrotation_axis(float *m, int idx, float angle);
+static CGM_INLINE void cgm_mrotation(float *m, float angle, float x, float y, float z);
+static CGM_INLINE void cgm_mrotation_euler(float *m, float a, float b, float c, int mode);
+static CGM_INLINE void cgm_mrotation_quat(float *m, const cgm_quat *q);
+
+static CGM_INLINE void cgm_mtranslate(float *m, float x, float y, float z);
+static CGM_INLINE void cgm_mscale(float *m, float sx, float sy, float sz);
+static CGM_INLINE void cgm_mrotate_x(float *m, float angle);
+static CGM_INLINE void cgm_mrotate_y(float *m, float angle);
+static CGM_INLINE void cgm_mrotate_z(float *m, float angle);
+static CGM_INLINE void cgm_mrotate_axis(float *m, int idx, float angle);
+static CGM_INLINE void cgm_mrotate(float *m, float angle, float x, float y, float z);
+static CGM_INLINE void cgm_mrotate_euler(float *m, float a, float b, float c, int mode);
+static CGM_INLINE void cgm_mrotate_quat(float *m, const cgm_quat *q);
+
+static CGM_INLINE void cgm_mpretranslate(float *m, float x, float y, float z);
+static CGM_INLINE void cgm_mprescale(float *m, float sx, float sy, float sz);
+static CGM_INLINE void cgm_mprerotate_x(float *m, float angle);
+static CGM_INLINE void cgm_mprerotate_y(float *m, float angle);
+static CGM_INLINE void cgm_mprerotate_z(float *m, float angle);
+static CGM_INLINE void cgm_mprerotate_axis(float *m, int idx, float angle);
+static CGM_INLINE void cgm_mprerotate(float *m, float angle, float x, float y, float z);
+static CGM_INLINE void cgm_mprerotate_euler(float *m, float a, float b, float c, int mode);
+static CGM_INLINE void cgm_mprerotate_quat(float *m, const cgm_quat *q);
+
+static CGM_INLINE void cgm_mget_translation(const float *m, cgm_vec3 *res);
+static CGM_INLINE void cgm_mget_rotation(const float *m, cgm_quat *res);
+static CGM_INLINE void cgm_mget_scaling(const float *m, cgm_vec3 *res);
+static CGM_INLINE void cgm_mget_frustum_plane(const float *m, int p, cgm_vec4 *res);
+
+static CGM_INLINE void cgm_mlookat(float *m, const cgm_vec3 *pos, const cgm_vec3 *targ,
                const cgm_vec3 *up);
-static inline void cgm_minv_lookat(float *m, const cgm_vec3 *pos, const cgm_vec3 *targ,
+static CGM_INLINE void cgm_minv_lookat(float *m, const cgm_vec3 *pos, const cgm_vec3 *targ,
                const cgm_vec3 *up);
-static inline void cgm_mortho(float *m, float left, float right, float bot, float top,
+static CGM_INLINE void cgm_mortho(float *m, float left, float right, float bot, float top,
                float znear, float zfar);
-static inline void cgm_mfrustum(float *m, float left, float right, float bot, float top,
+static CGM_INLINE void cgm_mfrustum(float *m, float left, float right, float bot, float top,
                float znear, float zfar);
-static inline void cgm_mperspective(float *m, float vfov, float aspect, float znear, float zfar);
+static CGM_INLINE void cgm_mperspective(float *m, float vfov, float aspect, float znear, float zfar);
 
-static inline void cgm_mmirror(float *m, float a, float b, float c, float d);
+static CGM_INLINE void cgm_mmirror(float *m, float a, float b, float c, float d);
 
 /* --- operations on rays --- */
-static inline void cgm_rcons(cgm_ray *r, float x, float y, float z, float dx, float dy, float dz);
+static CGM_INLINE void cgm_rcons(cgm_ray *r, float x, float y, float z, float dx, float dy, float dz);
 
-static inline void cgm_rmul_mr(cgm_ray *ray, const float *m);  /* m4x4 * ray */
-static inline void cgm_rmul_rm(cgm_ray *ray, const float *m);  /* ray * m4x4 */
+static CGM_INLINE void cgm_rmul_mr(cgm_ray *ray, const float *m);      /* m4x4 * ray */
+static CGM_INLINE void cgm_rmul_rm(cgm_ray *ray, const float *m);      /* ray * m4x4 */
 
-static inline void cgm_rreflect(cgm_ray *ray, const cgm_vec3 *n);
-static inline void cgm_rrefract(cgm_ray *ray, const cgm_vec3 *n, float ior);
+static CGM_INLINE void cgm_rreflect(cgm_ray *ray, const cgm_vec3 *n);
+static CGM_INLINE void cgm_rrefract(cgm_ray *ray, const cgm_vec3 *n, float ior);
 
 /* --- miscellaneous utility functions --- */
-static inline float cgm_deg_to_rad(float deg);
-static inline float cgm_rad_to_deg(float rad);
+static CGM_INLINE float cgm_deg_to_rad(float deg);
+static CGM_INLINE float cgm_rad_to_deg(float rad);
 
-static inline float cgm_smoothstep(float a, float b, float x);
-static inline float cgm_lerp(float a, float b, float t);
-static inline float cgm_logerp(float a, float b, float t);
-static inline float cgm_bezier(float a, float b, float c, float d, float t);
-static inline float cgm_bspline(float a, float b, float c, float d, float t);
-static inline float cgm_spline(float a, float b, float c, float d, float t);
+static CGM_INLINE float cgm_smoothstep(float a, float b, float x);
+static CGM_INLINE float cgm_lerp(float a, float b, float t);
+static CGM_INLINE float cgm_logerp(float a, float b, float t);
+static CGM_INLINE float cgm_bezier(float a, float b, float c, float d, float t);
+static CGM_INLINE float cgm_bspline(float a, float b, float c, float d, float t);
+static CGM_INLINE float cgm_spline(float a, float b, float c, float d, float t);
 
-static inline void cgm_discrand(cgm_vec3 *v, float rad);
-static inline void cgm_sphrand(cgm_vec3 *v, float rad);
+static CGM_INLINE void cgm_discrand(cgm_vec3 *v, float rad);
+static CGM_INLINE void cgm_sphrand(cgm_vec3 *v, float rad);
 
-static inline void cgm_unproject(cgm_vec3 *res, const cgm_vec3 *norm_scrpos,
+static CGM_INLINE void cgm_unproject(cgm_vec3 *res, const cgm_vec3 *norm_scrpos,
                const float *inv_viewproj);
-static inline void cgm_glu_unproject(float winx, float winy, float winz,
+static CGM_INLINE void cgm_glu_unproject(float winx, float winy, float winz,
                const float *view, const float *proj, const int *vp,
                float *objx, float *objy, float *objz);
 
-static inline void cgm_pick_ray(cgm_ray *ray, float nx, float ny,
+static CGM_INLINE void cgm_pick_ray(cgm_ray *ray, float nx, float ny,
                const float *viewmat, const float *projmat);
 
-static inline void cgm_raypos(cgm_vec3 *p, const cgm_ray *ray, float t);
+static CGM_INLINE void cgm_raypos(cgm_vec3 *p, const cgm_ray *ray, float t);
 
 /* calculate barycentric coordinates of point pt in triangle (a, b, c) */
-static inline void cgm_bary(cgm_vec3 *bary, const cgm_vec3 *a,
+static CGM_INLINE void cgm_bary(cgm_vec3 *bary, const cgm_vec3 *a,
                const cgm_vec3 *b, const cgm_vec3 *c, const cgm_vec3 *pt);
 
 /* convert between unit vectors and spherical coordinates */
-static inline void cgm_uvec_to_sph(float *theta, float *phi, const cgm_vec3 *v);
-static inline void cgm_sph_to_uvec(cgm_vec3 *v, float theta, float phi);
+static CGM_INLINE void cgm_uvec_to_sph(float *theta, float *phi, const cgm_vec3 *v);
+static CGM_INLINE void cgm_sph_to_uvec(cgm_vec3 *v, float theta, float phi);
 
 #include "cgmvec3.inl"
 #include "cgmvec4.inl"
index 2eb4519..2fb54fb 100644 (file)
@@ -6,24 +6,24 @@
  * If you intend to redistribute parts of the code without the LICENSE file
  * replace this paragraph with the full contents of the LICENSE file.
  */
-static inline void cgm_mcopy(float *dest, const float *src)
+static CGM_INLINE void cgm_mcopy(float *dest, const float *src)
 {
        memcpy(dest, src, 16 * sizeof(float));
 }
 
-static inline void cgm_mzero(float *m)
+static CGM_INLINE void cgm_mzero(float *m)
 {
        static float z[16];
        cgm_mcopy(m, z);
 }
 
-static inline void cgm_midentity(float *m)
+static CGM_INLINE void cgm_midentity(float *m)
 {
        static float id[16] = {1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1};
        cgm_mcopy(m, id);
 }
 
-static inline void cgm_mmul(float *a, const float *b)
+static CGM_INLINE void cgm_mmul(float *a, const float *b)
 {
        int i, j;
        float res[16];
@@ -40,7 +40,7 @@ static inline void cgm_mmul(float *a, const float *b)
        cgm_mcopy(a, res);
 }
 
-static inline void cgm_mpremul(float *a, const float *b)
+static CGM_INLINE void cgm_mpremul(float *a, const float *b)
 {
        int i, j;
        float res[16];
@@ -57,7 +57,7 @@ static inline void cgm_mpremul(float *a, const float *b)
        cgm_mcopy(a, res);
 }
 
-static inline void cgm_msubmatrix(float *m, int row, int col)
+static CGM_INLINE void cgm_msubmatrix(float *m, int row, int col)
 {
        float orig[16];
        int i, j, subi, subj;
@@ -80,13 +80,13 @@ static inline void cgm_msubmatrix(float *m, int row, int col)
        cgm_mupper3(m);
 }
 
-static inline void cgm_mupper3(float *m)
+static CGM_INLINE void cgm_mupper3(float *m)
 {
        m[3] = m[7] = m[11] = m[12] = m[13] = m[14] = 0.0f;
        m[15] = 1.0f;
 }
 
-static inline float cgm_msubdet(const float *m, int row, int col)
+static CGM_INLINE float cgm_msubdet(const float *m, int row, int col)
 {
        float tmp[16];
        float subdet00, subdet01, subdet02;
@@ -101,19 +101,19 @@ static inline float cgm_msubdet(const float *m, int row, int col)
        return tmp[0] * subdet00 - tmp[1] * subdet01 + tmp[2] * subdet02;
 }
 
-static inline float cgm_mcofactor(const float *m, int row, int col)
+static CGM_INLINE float cgm_mcofactor(const float *m, int row, int col)
 {
        float min = cgm_msubdet(m, row, col);
        return (row + col) & 1 ? -min : min;
 }
 
-static inline float cgm_mdet(const float *m)
+static CGM_INLINE float cgm_mdet(const float *m)
 {
        return m[0] * cgm_msubdet(m, 0, 0) - m[1] * cgm_msubdet(m, 0, 1) +
                m[2] * cgm_msubdet(m, 0, 2) - m[3] * cgm_msubdet(m, 0, 3);
 }
 
-static inline void cgm_mtranspose(float *m)
+static CGM_INLINE void cgm_mtranspose(float *m)
 {
        int i, j;
        for(i=0; i<4; i++) {
@@ -127,7 +127,7 @@ static inline void cgm_mtranspose(float *m)
        }
 }
 
-static inline void cgm_mcofmatrix(float *m)
+static CGM_INLINE void cgm_mcofmatrix(float *m)
 {
        float tmp[16];
        int i, j;
@@ -141,7 +141,7 @@ static inline void cgm_mcofmatrix(float *m)
        }
 }
 
-static inline int cgm_minverse(float *m)
+static CGM_INLINE int cgm_minverse(float *m)
 {
        int i, j;
        float tmp[16];
@@ -160,7 +160,7 @@ static inline int cgm_minverse(float *m)
        return 0;
 }
 
-static inline void cgm_mtranslation(float *m, float x, float y, float z)
+static CGM_INLINE void cgm_mtranslation(float *m, float x, float y, float z)
 {
        cgm_midentity(m);
        m[12] = x;
@@ -168,7 +168,7 @@ static inline void cgm_mtranslation(float *m, float x, float y, float z)
        m[14] = z;
 }
 
-static inline void cgm_mscaling(float *m, float sx, float sy, float sz)
+static CGM_INLINE void cgm_mscaling(float *m, float sx, float sy, float sz)
 {
        cgm_mzero(m);
        m[0] = sx;
@@ -177,7 +177,7 @@ static inline void cgm_mscaling(float *m, float sx, float sy, float sz)
        m[15] = 1.0f;
 }
 
-static inline void cgm_mrotation_x(float *m, float angle)
+static CGM_INLINE void cgm_mrotation_x(float *m, float angle)
 {
        float sa = sin(angle);
        float ca = cos(angle);
@@ -189,7 +189,7 @@ static inline void cgm_mrotation_x(float *m, float angle)
        m[10] = ca;
 }
 
-static inline void cgm_mrotation_y(float *m, float angle)
+static CGM_INLINE void cgm_mrotation_y(float *m, float angle)
 {
        float sa = sin(angle);
        float ca = cos(angle);
@@ -201,7 +201,7 @@ static inline void cgm_mrotation_y(float *m, float angle)
        m[10] = ca;
 }
 
-static inline void cgm_mrotation_z(float *m, float angle)
+static CGM_INLINE void cgm_mrotation_z(float *m, float angle)
 {
        float sa = sin(angle);
        float ca = cos(angle);
@@ -213,7 +213,7 @@ static inline void cgm_mrotation_z(float *m, float angle)
        m[5] = ca;
 }
 
-static inline void cgm_mrotation_axis(float *m, int idx, float angle)
+static CGM_INLINE void cgm_mrotation_axis(float *m, int idx, float angle)
 {
        switch(idx) {
        case 0:
@@ -228,7 +228,7 @@ static inline void cgm_mrotation_axis(float *m, int idx, float angle)
        }
 }
 
-static inline void cgm_mrotation(float *m, float angle, float x, float y, float z)
+static CGM_INLINE void cgm_mrotation(float *m, float angle, float x, float y, float z)
 {
        float sa = sin(angle);
        float ca = cos(angle);
@@ -253,7 +253,7 @@ static inline void cgm_mrotation(float *m, float angle, float x, float y, float
        m[10] = zsq + (1.0f - zsq) * ca;
 }
 
-static inline void cgm_mrotation_euler(float *m, float a, float b, float c, int mode)
+static CGM_INLINE void cgm_mrotation_euler(float *m, float a, float b, float c, int mode)
 {
        /* this array must match the EulerMode enum */
        static const int axis[][3] = {
@@ -273,7 +273,7 @@ static inline void cgm_mrotation_euler(float *m, float a, float b, float c, int
        cgm_mmul(m, ma);
 }
 
-static inline void cgm_mrotation_quat(float *m, const cgm_quat *q)
+static CGM_INLINE void cgm_mrotation_quat(float *m, const cgm_quat *q)
 {
        float xsq2 = 2.0f * q->x * q->x;
        float ysq2 = 2.0f * q->y * q->y;
@@ -296,63 +296,63 @@ static inline void cgm_mrotation_quat(float *m, const cgm_quat *q)
        m[10] = sz;
 }
 
-static inline void cgm_mtranslate(float *m, float x, float y, float z)
+static CGM_INLINE void cgm_mtranslate(float *m, float x, float y, float z)
 {
        float tm[16];
        cgm_mtranslation(tm, x, y, z);
        cgm_mmul(m, tm);
 }
 
-static inline void cgm_mscale(float *m, float sx, float sy, float sz)
+static CGM_INLINE void cgm_mscale(float *m, float sx, float sy, float sz)
 {
        float sm[16];
        cgm_mscaling(sm, sx, sy, sz);
        cgm_mmul(m, sm);
 }
 
-static inline void cgm_mrotate_x(float *m, float angle)
+static CGM_INLINE void cgm_mrotate_x(float *m, float angle)
 {
        float rm[16];
        cgm_mrotation_x(rm, angle);
        cgm_mmul(m, rm);
 }
 
-static inline void cgm_mrotate_y(float *m, float angle)
+static CGM_INLINE void cgm_mrotate_y(float *m, float angle)
 {
        float rm[16];
        cgm_mrotation_y(rm, angle);
        cgm_mmul(m, rm);
 }
 
-static inline void cgm_mrotate_z(float *m, float angle)
+static CGM_INLINE void cgm_mrotate_z(float *m, float angle)
 {
        float rm[16];
        cgm_mrotation_z(rm, angle);
        cgm_mmul(m, rm);
 }
 
-static inline void cgm_mrotate_axis(float *m, int idx, float angle)
+static CGM_INLINE void cgm_mrotate_axis(float *m, int idx, float angle)
 {
        float rm[16];
        cgm_mrotation_axis(rm, idx, angle);
        cgm_mmul(m, rm);
 }
 
-static inline void cgm_mrotate(float *m, float angle, float x, float y, float z)
+static CGM_INLINE void cgm_mrotate(float *m, float angle, float x, float y, float z)
 {
        float rm[16];
        cgm_mrotation(rm, angle, x, y, z);
        cgm_mmul(m, rm);
 }
 
-static inline void cgm_mrotate_euler(float *m, float a, float b, float c, int mode)
+static CGM_INLINE void cgm_mrotate_euler(float *m, float a, float b, float c, int mode)
 {
        float rm[16];
        cgm_mrotation_euler(rm, a, b, c, mode);
        cgm_mmul(m, rm);
 }
 
-static inline void cgm_mrotate_quat(float *m, const cgm_quat *q)
+static CGM_INLINE void cgm_mrotate_quat(float *m, const cgm_quat *q)
 {
        float rm[16];
        cgm_mrotation_quat(rm, q);
@@ -360,63 +360,63 @@ static inline void cgm_mrotate_quat(float *m, const cgm_quat *q)
 }
 
 
-static inline void cgm_mpretranslate(float *m, float x, float y, float z)
+static CGM_INLINE void cgm_mpretranslate(float *m, float x, float y, float z)
 {
        float tm[16];
        cgm_mtranslation(tm, x, y, z);
        cgm_mpremul(m, tm);
 }
 
-static inline void cgm_mprescale(float *m, float sx, float sy, float sz)
+static CGM_INLINE void cgm_mprescale(float *m, float sx, float sy, float sz)
 {
        float sm[16];
        cgm_mscaling(sm, sx, sy, sz);
        cgm_mpremul(m, sm);
 }
 
-static inline void cgm_mprerotate_x(float *m, float angle)
+static CGM_INLINE void cgm_mprerotate_x(float *m, float angle)
 {
        float rm[16];
        cgm_mrotation_x(rm, angle);
        cgm_mpremul(m, rm);
 }
 
-static inline void cgm_mprerotate_y(float *m, float angle)
+static CGM_INLINE void cgm_mprerotate_y(float *m, float angle)
 {
        float rm[16];
        cgm_mrotation_y(rm, angle);
        cgm_mpremul(m, rm);
 }
 
-static inline void cgm_mprerotate_z(float *m, float angle)
+static CGM_INLINE void cgm_mprerotate_z(float *m, float angle)
 {
        float rm[16];
        cgm_mrotation_z(rm, angle);
        cgm_mpremul(m, rm);
 }
 
-static inline void cgm_mprerotate_axis(float *m, int idx, float angle)
+static CGM_INLINE void cgm_mprerotate_axis(float *m, int idx, float angle)
 {
        float rm[16];
        cgm_mrotation_axis(rm, idx, angle);
        cgm_mpremul(m, rm);
 }
 
-static inline void cgm_mprerotate(float *m, float angle, float x, float y, float z)
+static CGM_INLINE void cgm_mprerotate(float *m, float angle, float x, float y, float z)
 {
        float rm[16];
        cgm_mrotation(rm, angle, x, y, z);
        cgm_mpremul(m, rm);
 }
 
-static inline void cgm_mprerotate_euler(float *m, float a, float b, float c, int mode)
+static CGM_INLINE void cgm_mprerotate_euler(float *m, float a, float b, float c, int mode)
 {
        float rm[16];
        cgm_mrotation_euler(rm, a, b, c, mode);
        cgm_mpremul(m, rm);
 }
 
-static inline void cgm_mprerotate_quat(float *m, const cgm_quat *q)
+static CGM_INLINE void cgm_mprerotate_quat(float *m, const cgm_quat *q)
 {
        float rm[16];
        cgm_mrotation_quat(rm, q);
@@ -424,7 +424,7 @@ static inline void cgm_mprerotate_quat(float *m, const cgm_quat *q)
 }
 
 
-static inline void cgm_mget_translation(const float *m, cgm_vec3 *res)
+static CGM_INLINE void cgm_mget_translation(const float *m, cgm_vec3 *res)
 {
        res->x = m[12];
        res->y = m[13];
@@ -435,7 +435,7 @@ static inline void cgm_mget_translation(const float *m, cgm_vec3 *res)
  * article "Quaternion Calculus and Fast Animation".
  * adapted from: http://www.geometrictools.com/LibMathematics/Algebra/Wm5Quaternion.inl
  */
-static inline void cgm_mget_rotation(const float *m, cgm_quat *res)
+static CGM_INLINE void cgm_mget_rotation(const float *m, cgm_quat *res)
 {
        static const int next[3] = {1, 2, 0};
        float quat[4];
@@ -477,14 +477,14 @@ static inline void cgm_mget_rotation(const float *m, cgm_quat *res)
        }
 }
 
-static inline void cgm_mget_scaling(const float *m, cgm_vec3 *res)
+static CGM_INLINE void cgm_mget_scaling(const float *m, cgm_vec3 *res)
 {
        res->x = sqrt(m[0] * m[0] + m[4] * m[4] + m[8] * m[8]);
        res->y = sqrt(m[1] * m[1] + m[5] * m[5] + m[9] * m[9]);
        res->z = sqrt(m[2] * m[2] + m[6] * m[6] + m[10] * m[10]);
 }
 
-static inline void cgm_mget_frustum_plane(const float *m, int p, cgm_vec4 *res)
+static CGM_INLINE void cgm_mget_frustum_plane(const float *m, int p, cgm_vec4 *res)
 {
        int row = p >> 1;
        const float *rowptr = m + row * 4;
@@ -502,7 +502,7 @@ static inline void cgm_mget_frustum_plane(const float *m, int p, cgm_vec4 *res)
        }
 }
 
-static inline void cgm_mlookat(float *m, const cgm_vec3 *pos, const cgm_vec3 *targ,
+static CGM_INLINE void cgm_mlookat(float *m, const cgm_vec3 *pos, const cgm_vec3 *targ,
                const cgm_vec3 *up)
 {
        float trans[16];
@@ -530,7 +530,7 @@ static inline void cgm_mlookat(float *m, const cgm_vec3 *pos, const cgm_vec3 *ta
        cgm_mmul(m, trans);
 }
 
-static inline void cgm_minv_lookat(float *m, const cgm_vec3 *pos, const cgm_vec3 *targ,
+static CGM_INLINE void cgm_minv_lookat(float *m, const cgm_vec3 *pos, const cgm_vec3 *targ,
                const cgm_vec3 *up)
 {
        float rot[16];
@@ -558,7 +558,7 @@ static inline void cgm_minv_lookat(float *m, const cgm_vec3 *pos, const cgm_vec3
        cgm_mmul(m, rot);
 }
 
-static inline void cgm_mortho(float *m, float left, float right, float bot, float top,
+static CGM_INLINE void cgm_mortho(float *m, float left, float right, float bot, float top,
                float znear, float zfar)
 {
        float dx = right - left;
@@ -574,7 +574,7 @@ static inline void cgm_mortho(float *m, float left, float right, float bot, floa
        m[14] = -(zfar + znear) / dz;
 }
 
-static inline void cgm_mfrustum(float *m, float left, float right, float bot, float top,
+static CGM_INLINE void cgm_mfrustum(float *m, float left, float right, float bot, float top,
                float znear, float zfar)
 {
        float dx = right - left;
@@ -591,7 +591,7 @@ static inline void cgm_mfrustum(float *m, float left, float right, float bot, fl
        m[11] = -1.0f;
 }
 
-static inline void cgm_mperspective(float *m, float vfov, float aspect, float znear, float zfar)
+static CGM_INLINE void cgm_mperspective(float *m, float vfov, float aspect, float znear, float zfar)
 {
        float s = 1.0f / (float)tan(vfov / 2.0f);
        float range = znear - zfar;
@@ -604,7 +604,7 @@ static inline void cgm_mperspective(float *m, float vfov, float aspect, float zn
        m[11] = -1.0f;
 }
 
-static inline void cgm_mmirror(float *m, float a, float b, float c, float d)
+static CGM_INLINE void cgm_mmirror(float *m, float a, float b, float c, float d)
 {
        m[0] = 1.0f - 2.0f * a * a;
        m[5] = 1.0f - 2.0f * b * b;
index fafaf4a..2ef8a11 100644 (file)
@@ -8,17 +8,17 @@
  */
 #include <stdlib.h>
 
-static inline float cgm_deg_to_rad(float deg)
+static CGM_INLINE float cgm_deg_to_rad(float deg)
 {
        return M_PI * deg / 180.0f;
 }
 
-static inline float cgm_rad_to_deg(float rad)
+static CGM_INLINE float cgm_rad_to_deg(float rad)
 {
        return 180.0f * rad / M_PI;
 }
 
-static inline float cgm_smoothstep(float a, float b, float x)
+static CGM_INLINE float cgm_smoothstep(float a, float b, float x)
 {
        if(x < a) return 0.0f;
        if(x >= b) return 1.0f;
@@ -27,18 +27,18 @@ static inline float cgm_smoothstep(float a, float b, float x)
        return x * x * (3.0f - 2.0f * x);
 }
 
-static inline float cgm_lerp(float a, float b, float t)
+static CGM_INLINE float cgm_lerp(float a, float b, float t)
 {
        return a + (b - a) * t;
 }
 
-static inline float cgm_logerp(float a, float b, float t)
+static CGM_INLINE float cgm_logerp(float a, float b, float t)
 {
        if(a == 0.0f) return 0.0f;
        return a * pow(b / a, t);
 }
 
-static inline float cgm_bezier(float a, float b, float c, float d, float t)
+static CGM_INLINE float cgm_bezier(float a, float b, float c, float d, float t)
 {
        float omt, omt3, t3, f;
        t3 = t * t * t;
@@ -49,7 +49,7 @@ static inline float cgm_bezier(float a, float b, float c, float d, float t)
        return (a * omt3) + (b * f * omt) + (c * f * t) + (d * t3);
 }
 
-static inline float cgm_bspline(float a, float b, float c, float d, float t)
+static CGM_INLINE float cgm_bspline(float a, float b, float c, float d, float t)
 {
        static const float mat[] = {
                -1, 3, -3, 1,
@@ -67,7 +67,7 @@ static inline float cgm_bspline(float a, float b, float c, float d, float t)
        return cgm_wdot(&tmp, &qfact);
 }
 
-static inline float cgm_spline(float a, float b, float c, float d, float t)
+static CGM_INLINE float cgm_spline(float a, float b, float c, float d, float t)
 {
        static const float mat[] = {
                -1, 2, -1, 0,
@@ -85,7 +85,7 @@ static inline float cgm_spline(float a, float b, float c, float d, float t)
        return cgm_wdot(&tmp, &qfact);
 }
 
-static inline void cgm_discrand(cgm_vec3 *pt, float rad)
+static CGM_INLINE void cgm_discrand(cgm_vec3 *pt, float rad)
 {
        float theta = 2.0f * M_PI * (float)rand() / RAND_MAX;
        float r = sqrt((float)rand() / RAND_MAX) * rad;
@@ -94,7 +94,7 @@ static inline void cgm_discrand(cgm_vec3 *pt, float rad)
        pt->z = 0.0f;
 }
 
-static inline void cgm_sphrand(cgm_vec3 *pt, float rad)
+static CGM_INLINE void cgm_sphrand(cgm_vec3 *pt, float rad)
 {
        float u, v, theta, phi;
 
@@ -109,7 +109,7 @@ static inline void cgm_sphrand(cgm_vec3 *pt, float rad)
        pt->z = cos(phi) * rad;
 }
 
-static inline void cgm_unproject(cgm_vec3 *res, const cgm_vec3 *norm_scrpos,
+static CGM_INLINE void cgm_unproject(cgm_vec3 *res, const cgm_vec3 *norm_scrpos,
                const float *inv_viewproj)
 {
        cgm_vec4 pos;
@@ -126,7 +126,7 @@ static inline void cgm_unproject(cgm_vec3 *res, const cgm_vec3 *norm_scrpos,
        res->z = pos.z / pos.w;
 }
 
-static inline void cgm_glu_unproject(float winx, float winy, float winz,
+static CGM_INLINE void cgm_glu_unproject(float winx, float winy, float winz,
                const float *view, const float *proj, const int *vp,
                float *objx, float *objy, float *objz)
 {
@@ -147,7 +147,7 @@ static inline void cgm_glu_unproject(float winx, float winy, float winz,
        *objz = res.z;
 }
 
-static inline void cgm_pick_ray(cgm_ray *ray, float nx, float ny,
+static CGM_INLINE void cgm_pick_ray(cgm_ray *ray, float nx, float ny,
                const float *viewmat, const float *projmat)
 {
        cgm_vec3 npos, farpt;
@@ -166,14 +166,14 @@ static inline void cgm_pick_ray(cgm_ray *ray, float nx, float ny,
        ray->dir.z = farpt.z - ray->origin.z;
 }
 
-static inline void cgm_raypos(cgm_vec3 *p, const cgm_ray *ray, float t)
+static CGM_INLINE void cgm_raypos(cgm_vec3 *p, const cgm_ray *ray, float t)
 {
        p->x = ray->origin.x + ray->dir.x * t;
        p->y = ray->origin.y + ray->dir.y * t;
        p->z = ray->origin.z + ray->dir.z * t;
 }
 
-static inline void cgm_bary(cgm_vec3 *bary, const cgm_vec3 *a,
+static CGM_INLINE void cgm_bary(cgm_vec3 *bary, const cgm_vec3 *a,
                const cgm_vec3 *b, const cgm_vec3 *c, const cgm_vec3 *pt)
 {
        float d00, d01, d11, d20, d21, denom;
@@ -195,13 +195,13 @@ static inline void cgm_bary(cgm_vec3 *bary, const cgm_vec3 *a,
        bary->x = 1.0f - bary->y - bary->z;
 }
 
-static inline void cgm_uvec_to_sph(float *theta, float *phi, const cgm_vec3 *v)
+static CGM_INLINE void cgm_uvec_to_sph(float *theta, float *phi, const cgm_vec3 *v)
 {
        *theta = atan2(v->z, v->x);
        *phi = acos(v->y);
 }
 
-static inline void cgm_sph_to_uvec(cgm_vec3 *v, float theta, float phi)
+static CGM_INLINE void cgm_sph_to_uvec(cgm_vec3 *v, float theta, float phi)
 {
        v->x = sin(theta) * cos(phi);
        v->y = sin(phi);
index 743d818..4351a1f 100644 (file)
@@ -6,7 +6,7 @@
  * If you intend to redistribute parts of the code without the LICENSE file
  * replace this paragraph with the full contents of the LICENSE file.
  */
-static inline void cgm_qcons(cgm_quat *q, float x, float y, float z, float w)
+static CGM_INLINE void cgm_qcons(cgm_quat *q, float x, float y, float z, float w)
 {
        q->x = x;
        q->y = y;
@@ -15,7 +15,7 @@ static inline void cgm_qcons(cgm_quat *q, float x, float y, float z, float w)
 }
 
 
-static inline void cgm_qneg(cgm_quat *q)
+static CGM_INLINE void cgm_qneg(cgm_quat *q)
 {
        q->x = -q->x;
        q->y = -q->y;
@@ -23,7 +23,7 @@ static inline void cgm_qneg(cgm_quat *q)
        q->w = -q->w;
 }
 
-static inline void cgm_qadd(cgm_quat *a, const cgm_quat *b)
+static CGM_INLINE void cgm_qadd(cgm_quat *a, const cgm_quat *b)
 {
        a->x += b->x;
        a->y += b->y;
@@ -31,7 +31,7 @@ static inline void cgm_qadd(cgm_quat *a, const cgm_quat *b)
        a->w += b->w;
 }
 
-static inline void cgm_qsub(cgm_quat *a, const cgm_quat *b)
+static CGM_INLINE void cgm_qsub(cgm_quat *a, const cgm_quat *b)
 {
        a->x -= b->x;
        a->y -= b->y;
@@ -39,7 +39,7 @@ static inline void cgm_qsub(cgm_quat *a, const cgm_quat *b)
        a->w -= b->w;
 }
 
-static inline void cgm_qmul(cgm_quat *a, const cgm_quat *b)
+static CGM_INLINE void cgm_qmul(cgm_quat *a, const cgm_quat *b)
 {
        float x, y, z, dot;
        cgm_vec3 cross;
@@ -56,17 +56,17 @@ static inline void cgm_qmul(cgm_quat *a, const cgm_quat *b)
        a->z = z;
 }
 
-static inline float cgm_qlength(const cgm_quat *q)
+static CGM_INLINE float cgm_qlength(const cgm_quat *q)
 {
        return sqrt(q->x * q->x + q->y * q->y + q->z * q->z + q->w * q->w);
 }
 
-static inline float cgm_qlength_sq(const cgm_quat *q)
+static CGM_INLINE float cgm_qlength_sq(const cgm_quat *q)
 {
        return q->x * q->x + q->y * q->y + q->z * q->z + q->w * q->w;
 }
 
-static inline void cgm_qnormalize(cgm_quat *q)
+static CGM_INLINE void cgm_qnormalize(cgm_quat *q)
 {
        float len = cgm_qlength(q);
        if(len != 0.0f) {
@@ -78,14 +78,14 @@ static inline void cgm_qnormalize(cgm_quat *q)
        }
 }
 
-static inline void cgm_qconjugate(cgm_quat *q)
+static CGM_INLINE void cgm_qconjugate(cgm_quat *q)
 {
        q->x = -q->x;
        q->y = -q->y;
        q->z = -q->z;
 }
 
-static inline void cgm_qinvert(cgm_quat *q)
+static CGM_INLINE void cgm_qinvert(cgm_quat *q)
 {
        float len_sq = cgm_qlength_sq(q);
        cgm_qconjugate(q);
@@ -98,7 +98,7 @@ static inline void cgm_qinvert(cgm_quat *q)
        }
 }
 
-static inline void cgm_qrotation(cgm_quat *q, float angle, float x, float y, float z)
+static CGM_INLINE void cgm_qrotation(cgm_quat *q, float angle, float x, float y, float z)
 {
        float hangle = angle * 0.5f;
        float sin_ha = sin(hangle);
@@ -108,14 +108,14 @@ static inline void cgm_qrotation(cgm_quat *q, float angle, float x, float y, flo
        q->z = z * sin_ha;
 }
 
-static inline void cgm_qrotate(cgm_quat *q, float angle, float x, float y, float z)
+static CGM_INLINE void cgm_qrotate(cgm_quat *q, float angle, float x, float y, float z)
 {
        cgm_quat qrot;
        cgm_qrotation(&qrot, angle, x, y, z);
        cgm_qmul(q, &qrot);
 }
 
-static inline void cgm_qslerp(cgm_quat *res, const cgm_quat *quat1, const cgm_quat *q2, float t)
+static CGM_INLINE void cgm_qslerp(cgm_quat *res, const cgm_quat *quat1, const cgm_quat *q2, float t)
 {
        float angle, dot, a, b, sin_angle;
        cgm_quat q1 = *quat1;
@@ -150,7 +150,7 @@ static inline void cgm_qslerp(cgm_quat *res, const cgm_quat *quat1, const cgm_qu
        res->w = q1.w * a + q2->w * b;
 }
 
-static inline void cgm_qlerp(cgm_quat *res, const cgm_quat *a, const cgm_quat *b, float t)
+static CGM_INLINE void cgm_qlerp(cgm_quat *res, const cgm_quat *a, const cgm_quat *b, float t)
 {
        res->x = a->x + (b->x - a->x) * t;
        res->y = a->y + (b->y - a->y) * t;
index 063a7e0..c396a55 100644 (file)
@@ -6,7 +6,7 @@
  * If you intend to redistribute parts of the code without the LICENSE file
  * replace this paragraph with the full contents of the LICENSE file.
  */
-static inline void cgm_rcons(cgm_ray *r, float x, float y, float z, float dx, float dy, float dz)
+static CGM_INLINE void cgm_rcons(cgm_ray *r, float x, float y, float z, float dx, float dy, float dz)
 {
        r->origin.x = x;
        r->origin.y = y;
@@ -16,24 +16,24 @@ static inline void cgm_rcons(cgm_ray *r, float x, float y, float z, float dx, fl
        r->dir.z = dz;
 }
 
-static inline void cgm_rmul_mr(cgm_ray *ray, const float *m)
+static CGM_INLINE void cgm_rmul_mr(cgm_ray *ray, const float *m)
 {
        cgm_vmul_m4v3(&ray->origin, m);
        cgm_vmul_m3v3(&ray->dir, m);
 }
 
-static inline void cgm_rmul_rm(cgm_ray *ray, const float *m)
+static CGM_INLINE void cgm_rmul_rm(cgm_ray *ray, const float *m)
 {
        cgm_vmul_v3m4(&ray->origin, m);
        cgm_vmul_v3m3(&ray->dir, m);
 }
 
-static inline void cgm_rreflect(cgm_ray *ray, const cgm_vec3 *n)
+static CGM_INLINE void cgm_rreflect(cgm_ray *ray, const cgm_vec3 *n)
 {
        cgm_vreflect(&ray->dir, n);
 }
 
-static inline void cgm_rrefract(cgm_ray *ray, const cgm_vec3 *n, float ior)
+static CGM_INLINE void cgm_rrefract(cgm_ray *ray, const cgm_vec3 *n, float ior)
 {
        cgm_vrefract(&ray->dir, n, ior);
 }
index e712b5b..0774706 100644 (file)
@@ -6,56 +6,56 @@
  * If you intend to redistribute parts of the code without the LICENSE file
  * replace this paragraph with the full contents of the LICENSE file.
  */
-static inline void cgm_vcons(cgm_vec3 *v, float x, float y, float z)
+static CGM_INLINE void cgm_vcons(cgm_vec3 *v, float x, float y, float z)
 {
        v->x = x;
        v->y = y;
        v->z = z;
 }
 
-static inline void cgm_vadd(cgm_vec3 *a, const cgm_vec3 *b)
+static CGM_INLINE void cgm_vadd(cgm_vec3 *a, const cgm_vec3 *b)
 {
        a->x += b->x;
        a->y += b->y;
        a->z += b->z;
 }
 
-static inline void cgm_vadd_scaled(cgm_vec3 *a, const cgm_vec3 *b, float s)
+static CGM_INLINE void cgm_vadd_scaled(cgm_vec3 *a, const cgm_vec3 *b, float s)
 {
        a->x += b->x * s;
        a->y += b->y * s;
        a->z += b->z * s;
 }
 
-static inline void cgm_vsub(cgm_vec3 *a, const cgm_vec3 *b)
+static CGM_INLINE void cgm_vsub(cgm_vec3 *a, const cgm_vec3 *b)
 {
        a->x -= b->x;
        a->y -= b->y;
        a->z -= b->z;
 }
 
-static inline void cgm_vsub_scaled(cgm_vec3 *a, const cgm_vec3 *b, float s)
+static CGM_INLINE void cgm_vsub_scaled(cgm_vec3 *a, const cgm_vec3 *b, float s)
 {
        a->x -= b->x * s;
        a->y -= b->y * s;
        a->z -= b->z * s;
 }
 
-static inline void cgm_vmul(cgm_vec3 *a, const cgm_vec3 *b)
+static CGM_INLINE void cgm_vmul(cgm_vec3 *a, const cgm_vec3 *b)
 {
        a->x *= b->x;
        a->y *= b->y;
        a->z *= b->z;
 }
 
-static inline void cgm_vscale(cgm_vec3 *v, float s)
+static CGM_INLINE void cgm_vscale(cgm_vec3 *v, float s)
 {
        v->x *= s;
        v->y *= s;
        v->z *= s;
 }
 
-static inline void cgm_vmul_m4v3(cgm_vec3 *v, const float *m)
+static CGM_INLINE void cgm_vmul_m4v3(cgm_vec3 *v, const float *m)
 {
        float x = v->x * m[0] + v->y * m[4] + v->z * m[8] + m[12];
        float y = v->x * m[1] + v->y * m[5] + v->z * m[9] + m[13];
@@ -64,7 +64,7 @@ static inline void cgm_vmul_m4v3(cgm_vec3 *v, const float *m)
        v->y = y;
 }
 
-static inline void cgm_vmul_v3m4(cgm_vec3 *v, const float *m)
+static CGM_INLINE void cgm_vmul_v3m4(cgm_vec3 *v, const float *m)
 {
        float x = v->x * m[0] + v->y * m[1] + v->z * m[2] + m[3];
        float y = v->x * m[4] + v->y * m[5] + v->z * m[6] + m[7];
@@ -73,7 +73,7 @@ static inline void cgm_vmul_v3m4(cgm_vec3 *v, const float *m)
        v->y = y;
 }
 
-static inline void cgm_vmul_m3v3(cgm_vec3 *v, const float *m)
+static CGM_INLINE void cgm_vmul_m3v3(cgm_vec3 *v, const float *m)
 {
        float x = v->x * m[0] + v->y * m[4] + v->z * m[8];
        float y = v->x * m[1] + v->y * m[5] + v->z * m[9];
@@ -82,7 +82,7 @@ static inline void cgm_vmul_m3v3(cgm_vec3 *v, const float *m)
        v->y = y;
 }
 
-static inline void cgm_vmul_v3m3(cgm_vec3 *v, const float *m)
+static CGM_INLINE void cgm_vmul_v3m3(cgm_vec3 *v, const float *m)
 {
        float x = v->x * m[0] + v->y * m[1] + v->z * m[2];
        float y = v->x * m[4] + v->y * m[5] + v->z * m[6];
@@ -91,29 +91,29 @@ static inline void cgm_vmul_v3m3(cgm_vec3 *v, const float *m)
        v->y = y;
 }
 
-static inline float cgm_vdot(const cgm_vec3 *a, const cgm_vec3 *b)
+static CGM_INLINE float cgm_vdot(const cgm_vec3 *a, const cgm_vec3 *b)
 {
        return a->x * b->x + a->y * b->y + a->z * b->z;
 }
 
-static inline void cgm_vcross(cgm_vec3 *res, const cgm_vec3 *a, const cgm_vec3 *b)
+static CGM_INLINE void cgm_vcross(cgm_vec3 *res, const cgm_vec3 *a, const cgm_vec3 *b)
 {
        res->x = a->y * b->z - a->z * b->y;
        res->y = a->z * b->x - a->x * b->z;
        res->z = a->x * b->y - a->y * b->x;
 }
 
-static inline float cgm_vlength(const cgm_vec3 *v)
+static CGM_INLINE float cgm_vlength(const cgm_vec3 *v)
 {
        return sqrt(v->x * v->x + v->y * v->y + v->z * v->z);
 }
 
-static inline float cgm_vlength_sq(const cgm_vec3 *v)
+static CGM_INLINE float cgm_vlength_sq(const cgm_vec3 *v)
 {
        return v->x * v->x + v->y * v->y + v->z * v->z;
 }
 
-static inline float cgm_vdist(const cgm_vec3 *a, const cgm_vec3 *b)
+static CGM_INLINE float cgm_vdist(const cgm_vec3 *a, const cgm_vec3 *b)
 {
        float dx = a->x - b->x;
        float dy = a->y - b->y;
@@ -121,7 +121,7 @@ static inline float cgm_vdist(const cgm_vec3 *a, const cgm_vec3 *b)
        return sqrt(dx * dx + dy * dy + dz * dz);
 }
 
-static inline float cgm_vdist_sq(const cgm_vec3 *a, const cgm_vec3 *b)
+static CGM_INLINE float cgm_vdist_sq(const cgm_vec3 *a, const cgm_vec3 *b)
 {
        float dx = a->x - b->x;
        float dy = a->y - b->y;
@@ -129,7 +129,7 @@ static inline float cgm_vdist_sq(const cgm_vec3 *a, const cgm_vec3 *b)
        return dx * dx + dy * dy + dz * dz;
 }
 
-static inline void cgm_vnormalize(cgm_vec3 *v)
+static CGM_INLINE void cgm_vnormalize(cgm_vec3 *v)
 {
        float len = cgm_vlength(v);
        if(len != 0.0f) {
@@ -140,7 +140,7 @@ static inline void cgm_vnormalize(cgm_vec3 *v)
        }
 }
 
-static inline void cgm_vreflect(cgm_vec3 *v, const cgm_vec3 *n)
+static CGM_INLINE void cgm_vreflect(cgm_vec3 *v, const cgm_vec3 *n)
 {
        float ndotv2 = cgm_vdot(v, n) * 2.0f;
        v->x -= n->x * ndotv2;
@@ -148,7 +148,7 @@ static inline void cgm_vreflect(cgm_vec3 *v, const cgm_vec3 *n)
        v->z -= n->z * ndotv2;
 }
 
-static inline void cgm_vrefract(cgm_vec3 *v, const cgm_vec3 *n, float ior)
+static CGM_INLINE void cgm_vrefract(cgm_vec3 *v, const cgm_vec3 *n, float ior)
 {
        float ndotv = cgm_vdot(v, n);
        float k = 1.0f - ior * ior * (1.0f - ndotv * ndotv);
@@ -162,7 +162,7 @@ static inline void cgm_vrefract(cgm_vec3 *v, const cgm_vec3 *n, float ior)
        }
 }
 
-static inline void cgm_vrotate_quat(cgm_vec3 *v, const cgm_quat *q)
+static CGM_INLINE void cgm_vrotate_quat(cgm_vec3 *v, const cgm_quat *q)
 {
        cgm_quat vq, inv_q = *q, tmp_q = *q;
 
@@ -173,28 +173,28 @@ static inline void cgm_vrotate_quat(cgm_vec3 *v, const cgm_quat *q)
        cgm_vcons(v, tmp_q.x, tmp_q.y, tmp_q.z);
 }
 
-static inline void cgm_vrotate_axis(cgm_vec3 *v, int axis, float angle)
+static CGM_INLINE void cgm_vrotate_axis(cgm_vec3 *v, int axis, float angle)
 {
        float m[16];
        cgm_mrotation_axis(m, axis, angle);
        cgm_vmul_m3v3(v, m);
 }
 
-static inline void cgm_vrotate(cgm_vec3 *v, float angle, float x, float y, float z)
+static CGM_INLINE void cgm_vrotate(cgm_vec3 *v, float angle, float x, float y, float z)
 {
        float m[16];
        cgm_mrotation(m, angle, x, y, z);
        cgm_vmul_m3v3(v, m);
 }
 
-static inline void cgm_vrotate_euler(cgm_vec3 *v, float a, float b, float c, enum cgm_euler_mode mode)
+static CGM_INLINE void cgm_vrotate_euler(cgm_vec3 *v, float a, float b, float c, enum cgm_euler_mode mode)
 {
        float m[16];
        cgm_mrotation_euler(m, a, b, c, mode);
        cgm_vmul_m3v3(v, m);
 }
 
-static inline void cgm_vlerp(cgm_vec3 *res, const cgm_vec3 *a, const cgm_vec3 *b, float t)
+static CGM_INLINE void cgm_vlerp(cgm_vec3 *res, const cgm_vec3 *a, const cgm_vec3 *b, float t)
 {
        res->x = a->x + (b->x - a->x) * t;
        res->y = a->y + (b->y - a->y) * t;
index b68856c..47f9ab0 100644 (file)
@@ -6,7 +6,7 @@
  * If you intend to redistribute parts of the code without the LICENSE file
  * replace this paragraph with the full contents of the LICENSE file.
  */
-static inline void cgm_wcons(cgm_vec4 *v, float x, float y, float z, float w)
+static CGM_INLINE void cgm_wcons(cgm_vec4 *v, float x, float y, float z, float w)
 {
        v->x = x;
        v->y = y;
@@ -14,7 +14,7 @@ static inline void cgm_wcons(cgm_vec4 *v, float x, float y, float z, float w)
        v->w = w;
 }
 
-static inline void cgm_wadd(cgm_vec4 *a, const cgm_vec4 *b)
+static CGM_INLINE void cgm_wadd(cgm_vec4 *a, const cgm_vec4 *b)
 {
        a->x += b->x;
        a->y += b->y;
@@ -22,7 +22,7 @@ static inline void cgm_wadd(cgm_vec4 *a, const cgm_vec4 *b)
        a->w += b->w;
 }
 
-static inline void cgm_wsub(cgm_vec4 *a, const cgm_vec4 *b)
+static CGM_INLINE void cgm_wsub(cgm_vec4 *a, const cgm_vec4 *b)
 {
        a->x -= b->x;
        a->y -= b->y;
@@ -30,7 +30,7 @@ static inline void cgm_wsub(cgm_vec4 *a, const cgm_vec4 *b)
        a->w -= b->w;
 }
 
-static inline void cgm_wmul(cgm_vec4 *a, const cgm_vec4 *b)
+static CGM_INLINE void cgm_wmul(cgm_vec4 *a, const cgm_vec4 *b)
 {
        a->x *= b->x;
        a->y *= b->y;
@@ -38,7 +38,7 @@ static inline void cgm_wmul(cgm_vec4 *a, const cgm_vec4 *b)
        a->w *= b->w;
 }
 
-static inline void cgm_wscale(cgm_vec4 *v, float s)
+static CGM_INLINE void cgm_wscale(cgm_vec4 *v, float s)
 {
        v->x *= s;
        v->y *= s;
@@ -46,7 +46,7 @@ static inline void cgm_wscale(cgm_vec4 *v, float s)
        v->w *= s;
 }
 
-static inline void cgm_wmul_m4v4(cgm_vec4 *v, const float *m)
+static CGM_INLINE void cgm_wmul_m4v4(cgm_vec4 *v, const float *m)
 {
        float x = v->x * m[0] + v->y * m[4] + v->z * m[8] + v->w * m[12];
        float y = v->x * m[1] + v->y * m[5] + v->z * m[9] + v->w * m[13];
@@ -57,7 +57,7 @@ static inline void cgm_wmul_m4v4(cgm_vec4 *v, const float *m)
        v->z = z;
 }
 
-static inline void cgm_wmul_v4m4(cgm_vec4 *v, const float *m)
+static CGM_INLINE void cgm_wmul_v4m4(cgm_vec4 *v, const float *m)
 {
        float x = v->x * m[0] + v->y * m[1] + v->z * m[2] + v->w * m[3];
        float y = v->x * m[4] + v->y * m[5] + v->z * m[6] + v->w * m[7];
@@ -68,7 +68,7 @@ static inline void cgm_wmul_v4m4(cgm_vec4 *v, const float *m)
        v->z = z;
 }
 
-static inline void cgm_wmul_m34v4(cgm_vec4 *v, const float *m)
+static CGM_INLINE void cgm_wmul_m34v4(cgm_vec4 *v, const float *m)
 {
        float x = v->x * m[0] + v->y * m[4] + v->z * m[8] + v->w * m[12];
        float y = v->x * m[1] + v->y * m[5] + v->z * m[9] + v->w * m[13];
@@ -77,7 +77,7 @@ static inline void cgm_wmul_m34v4(cgm_vec4 *v, const float *m)
        v->y = y;
 }
 
-static inline void cgm_wmul_v4m43(cgm_vec4 *v, const float *m)
+static CGM_INLINE void cgm_wmul_v4m43(cgm_vec4 *v, const float *m)
 {
        float x = v->x * m[0] + v->y * m[1] + v->z * m[2] + v->w * m[3];
        float y = v->x * m[4] + v->y * m[5] + v->z * m[6] + v->w * m[7];
@@ -86,7 +86,7 @@ static inline void cgm_wmul_v4m43(cgm_vec4 *v, const float *m)
        v->y = y;
 }
 
-static inline void cgm_wmul_m3v4(cgm_vec4 *v, const float *m)
+static CGM_INLINE void cgm_wmul_m3v4(cgm_vec4 *v, const float *m)
 {
        float x = v->x * m[0] + v->y * m[4] + v->z * m[8];
        float y = v->x * m[1] + v->y * m[5] + v->z * m[9];
@@ -95,7 +95,7 @@ static inline void cgm_wmul_m3v4(cgm_vec4 *v, const float *m)
        v->y = y;
 }
 
-static inline void cgm_wmul_v4m3(cgm_vec4 *v, const float *m)
+static CGM_INLINE void cgm_wmul_v4m3(cgm_vec4 *v, const float *m)
 {
        float x = v->x * m[0] + v->y * m[1] + v->z * m[2];
        float y = v->x * m[4] + v->y * m[5] + v->z * m[6];
@@ -104,22 +104,22 @@ static inline void cgm_wmul_v4m3(cgm_vec4 *v, const float *m)
        v->y = y;
 }
 
-static inline float cgm_wdot(const cgm_vec4 *a, const cgm_vec4 *b)
+static CGM_INLINE float cgm_wdot(const cgm_vec4 *a, const cgm_vec4 *b)
 {
        return a->x * b->x + a->y * b->y + a->z * b->z + a->w * b->w;
 }
 
-static inline float cgm_wlength(const cgm_vec4 *v)
+static CGM_INLINE float cgm_wlength(const cgm_vec4 *v)
 {
        return sqrt(v->x * v->x + v->y * v->y + v->z * v->z + v->w * v->w);
 }
 
-static inline float cgm_wlength_sq(const cgm_vec4 *v)
+static CGM_INLINE float cgm_wlength_sq(const cgm_vec4 *v)
 {
        return v->x * v->x + v->y * v->y + v->z * v->z + v->w * v->w;
 }
 
-static inline float cgm_wdist(const cgm_vec4 *a, const cgm_vec4 *b)
+static CGM_INLINE float cgm_wdist(const cgm_vec4 *a, const cgm_vec4 *b)
 {
        float dx = a->x - b->x;
        float dy = a->y - b->y;
@@ -128,7 +128,7 @@ static inline float cgm_wdist(const cgm_vec4 *a, const cgm_vec4 *b)
        return sqrt(dx * dx + dy * dy + dz * dz + dw * dw);
 }
 
-static inline float cgm_wdist_sq(const cgm_vec4 *a, const cgm_vec4 *b)
+static CGM_INLINE float cgm_wdist_sq(const cgm_vec4 *a, const cgm_vec4 *b)
 {
        float dx = a->x - b->x;
        float dy = a->y - b->y;
@@ -137,7 +137,7 @@ static inline float cgm_wdist_sq(const cgm_vec4 *a, const cgm_vec4 *b)
        return dx * dx + dy * dy + dz * dz + dw * dw;
 }
 
-static inline void cgm_wnormalize(cgm_vec4 *v)
+static CGM_INLINE void cgm_wnormalize(cgm_vec4 *v)
 {
        float len = cgm_wlength(v);
        if(len != 0.0f) {
@@ -149,7 +149,7 @@ static inline void cgm_wnormalize(cgm_vec4 *v)
        }
 }
 
-static inline void cgm_wlerp(cgm_vec4 *res, const cgm_vec4 *a, const cgm_vec4 *b, float t)
+static CGM_INLINE void cgm_wlerp(cgm_vec4 *res, const cgm_vec4 *a, const cgm_vec4 *b, float t)
 {
        res->x = a->x + (b->x - a->x) * t;
        res->y = a->y + (b->y - a->y) * t;
diff --git a/libs/goat3d/COPYING b/libs/goat3d/COPYING
new file mode 100644 (file)
index 0000000..94a9ed0
--- /dev/null
@@ -0,0 +1,674 @@
+                    GNU GENERAL PUBLIC LICENSE
+                       Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                            Preamble
+
+  The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+  The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works.  By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users.  We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors.  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+  To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights.  Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received.  You must make sure that they, too, receive
+or can get the source code.  And you must show them these terms so they
+know their rights.
+
+  Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+  For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software.  For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+  Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so.  This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software.  The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable.  Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products.  If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+  Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary.  To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+                       TERMS AND CONDITIONS
+
+  0. Definitions.
+
+  "This License" refers to version 3 of the GNU General Public License.
+
+  "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+  "The Program" refers to any copyrightable work licensed under this
+License.  Each licensee is addressed as "you".  "Licensees" and
+"recipients" may be individuals or organizations.
+
+  To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy.  The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+  A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+  To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy.  Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+  To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies.  Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+  An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License.  If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+  1. Source Code.
+
+  The "source code" for a work means the preferred form of the work
+for making modifications to it.  "Object code" means any non-source
+form of a work.
+
+  A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+  The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form.  A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+  The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities.  However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work.  For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+  The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+  The Corresponding Source for a work in source code form is that
+same work.
+
+  2. Basic Permissions.
+
+  All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met.  This License explicitly affirms your unlimited
+permission to run the unmodified Program.  The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work.  This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+  You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force.  You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright.  Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+  Conveying under any other circumstances is permitted solely under
+the conditions stated below.  Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+  3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+  No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+  When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+  4. Conveying Verbatim Copies.
+
+  You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+  You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+  5. Conveying Modified Source Versions.
+
+  You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+    a) The work must carry prominent notices stating that you modified
+    it, and giving a relevant date.
+
+    b) The work must carry prominent notices stating that it is
+    released under this License and any conditions added under section
+    7.  This requirement modifies the requirement in section 4 to
+    "keep intact all notices".
+
+    c) You must license the entire work, as a whole, under this
+    License to anyone who comes into possession of a copy.  This
+    License will therefore apply, along with any applicable section 7
+    additional terms, to the whole of the work, and all its parts,
+    regardless of how they are packaged.  This License gives no
+    permission to license the work in any other way, but it does not
+    invalidate such permission if you have separately received it.
+
+    d) If the work has interactive user interfaces, each must display
+    Appropriate Legal Notices; however, if the Program has interactive
+    interfaces that do not display Appropriate Legal Notices, your
+    work need not make them do so.
+
+  A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit.  Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+  6. Conveying Non-Source Forms.
+
+  You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+    a) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by the
+    Corresponding Source fixed on a durable physical medium
+    customarily used for software interchange.
+
+    b) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by a
+    written offer, valid for at least three years and valid for as
+    long as you offer spare parts or customer support for that product
+    model, to give anyone who possesses the object code either (1) a
+    copy of the Corresponding Source for all the software in the
+    product that is covered by this License, on a durable physical
+    medium customarily used for software interchange, for a price no
+    more than your reasonable cost of physically performing this
+    conveying of source, or (2) access to copy the
+    Corresponding Source from a network server at no charge.
+
+    c) Convey individual copies of the object code with a copy of the
+    written offer to provide the Corresponding Source.  This
+    alternative is allowed only occasionally and noncommercially, and
+    only if you received the object code with such an offer, in accord
+    with subsection 6b.
+
+    d) Convey the object code by offering access from a designated
+    place (gratis or for a charge), and offer equivalent access to the
+    Corresponding Source in the same way through the same place at no
+    further charge.  You need not require recipients to copy the
+    Corresponding Source along with the object code.  If the place to
+    copy the object code is a network server, the Corresponding Source
+    may be on a different server (operated by you or a third party)
+    that supports equivalent copying facilities, provided you maintain
+    clear directions next to the object code saying where to find the
+    Corresponding Source.  Regardless of what server hosts the
+    Corresponding Source, you remain obligated to ensure that it is
+    available for as long as needed to satisfy these requirements.
+
+    e) Convey the object code using peer-to-peer transmission, provided
+    you inform other peers where the object code and Corresponding
+    Source of the work are being offered to the general public at no
+    charge under subsection 6d.
+
+  A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+  A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling.  In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage.  For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product.  A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+  "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source.  The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+  If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information.  But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+  The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed.  Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+  Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+  7. Additional Terms.
+
+  "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law.  If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+  When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it.  (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.)  You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+  Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+    a) Disclaiming warranty or limiting liability differently from the
+    terms of sections 15 and 16 of this License; or
+
+    b) Requiring preservation of specified reasonable legal notices or
+    author attributions in that material or in the Appropriate Legal
+    Notices displayed by works containing it; or
+
+    c) Prohibiting misrepresentation of the origin of that material, or
+    requiring that modified versions of such material be marked in
+    reasonable ways as different from the original version; or
+
+    d) Limiting the use for publicity purposes of names of licensors or
+    authors of the material; or
+
+    e) Declining to grant rights under trademark law for use of some
+    trade names, trademarks, or service marks; or
+
+    f) Requiring indemnification of licensors and authors of that
+    material by anyone who conveys the material (or modified versions of
+    it) with contractual assumptions of liability to the recipient, for
+    any liability that these contractual assumptions directly impose on
+    those licensors and authors.
+
+  All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10.  If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term.  If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+  If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+  Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+  8. Termination.
+
+  You may not propagate or modify a covered work except as expressly
+provided under this License.  Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+  However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+  Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+  Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License.  If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+  9. Acceptance Not Required for Having Copies.
+
+  You are not required to accept this License in order to receive or
+run a copy of the Program.  Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance.  However,
+nothing other than this License grants you permission to propagate or
+modify any covered work.  These actions infringe copyright if you do
+not accept this License.  Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+  10. Automatic Licensing of Downstream Recipients.
+
+  Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License.  You are not responsible
+for enforcing compliance by third parties with this License.
+
+  An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations.  If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+  You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License.  For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+  11. Patents.
+
+  A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based.  The
+work thus licensed is called the contributor's "contributor version".
+
+  A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version.  For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+  Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+  In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement).  To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+  If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients.  "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+  If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+  A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License.  You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+  Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+  12. No Surrender of Others' Freedom.
+
+  If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all.  For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+  13. Use with the GNU Affero General Public License.
+
+  Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work.  The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+  14. Revised Versions of this License.
+
+  The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+  Each version is given a distinguishing version number.  If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation.  If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+  If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+  Later license versions may give you additional or different
+permissions.  However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+  15. Disclaimer of Warranty.
+
+  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+  16. Limitation of Liability.
+
+  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+  17. Interpretation of Sections 15 and 16.
+
+  If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+                     END OF TERMS AND CONDITIONS
+
+            How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU 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 General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+  If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+    <program>  Copyright (C) <year>  <name of author>
+    This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+  You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+<http://www.gnu.org/licenses/>.
+
+  The GNU General Public License does not permit incorporating your program
+into proprietary programs.  If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library.  If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.  But first, please read
+<http://www.gnu.org/philosophy/why-not-lgpl.html>.
diff --git a/libs/goat3d/COPYING.LESSER b/libs/goat3d/COPYING.LESSER
new file mode 100644 (file)
index 0000000..65c5ca8
--- /dev/null
@@ -0,0 +1,165 @@
+                   GNU LESSER GENERAL PUBLIC LICENSE
+                       Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+
+  This version of the GNU Lesser General Public License incorporates
+the terms and conditions of version 3 of the GNU General Public
+License, supplemented by the additional permissions listed below.
+
+  0. Additional Definitions.
+
+  As used herein, "this License" refers to version 3 of the GNU Lesser
+General Public License, and the "GNU GPL" refers to version 3 of the GNU
+General Public License.
+
+  "The Library" refers to a covered work governed by this License,
+other than an Application or a Combined Work as defined below.
+
+  An "Application" is any work that makes use of an interface provided
+by the Library, but which is not otherwise based on the Library.
+Defining a subclass of a class defined by the Library is deemed a mode
+of using an interface provided by the Library.
+
+  A "Combined Work" is a work produced by combining or linking an
+Application with the Library.  The particular version of the Library
+with which the Combined Work was made is also called the "Linked
+Version".
+
+  The "Minimal Corresponding Source" for a Combined Work means the
+Corresponding Source for the Combined Work, excluding any source code
+for portions of the Combined Work that, considered in isolation, are
+based on the Application, and not on the Linked Version.
+
+  The "Corresponding Application Code" for a Combined Work means the
+object code and/or source code for the Application, including any data
+and utility programs needed for reproducing the Combined Work from the
+Application, but excluding the System Libraries of the Combined Work.
+
+  1. Exception to Section 3 of the GNU GPL.
+
+  You may convey a covered work under sections 3 and 4 of this License
+without being bound by section 3 of the GNU GPL.
+
+  2. Conveying Modified Versions.
+
+  If you modify a copy of the Library, and, in your modifications, a
+facility refers to a function or data to be supplied by an Application
+that uses the facility (other than as an argument passed when the
+facility is invoked), then you may convey a copy of the modified
+version:
+
+   a) under this License, provided that you make a good faith effort to
+   ensure that, in the event an Application does not supply the
+   function or data, the facility still operates, and performs
+   whatever part of its purpose remains meaningful, or
+
+   b) under the GNU GPL, with none of the additional permissions of
+   this License applicable to that copy.
+
+  3. Object Code Incorporating Material from Library Header Files.
+
+  The object code form of an Application may incorporate material from
+a header file that is part of the Library.  You may convey such object
+code under terms of your choice, provided that, if the incorporated
+material is not limited to numerical parameters, data structure
+layouts and accessors, or small macros, inline functions and templates
+(ten or fewer lines in length), you do both of the following:
+
+   a) Give prominent notice with each copy of the object code that the
+   Library is used in it and that the Library and its use are
+   covered by this License.
+
+   b) Accompany the object code with a copy of the GNU GPL and this license
+   document.
+
+  4. Combined Works.
+
+  You may convey a Combined Work under terms of your choice that,
+taken together, effectively do not restrict modification of the
+portions of the Library contained in the Combined Work and reverse
+engineering for debugging such modifications, if you also do each of
+the following:
+
+   a) Give prominent notice with each copy of the Combined Work that
+   the Library is used in it and that the Library and its use are
+   covered by this License.
+
+   b) Accompany the Combined Work with a copy of the GNU GPL and this license
+   document.
+
+   c) For a Combined Work that displays copyright notices during
+   execution, include the copyright notice for the Library among
+   these notices, as well as a reference directing the user to the
+   copies of the GNU GPL and this license document.
+
+   d) Do one of the following:
+
+       0) Convey the Minimal Corresponding Source under the terms of this
+       License, and the Corresponding Application Code in a form
+       suitable for, and under terms that permit, the user to
+       recombine or relink the Application with a modified version of
+       the Linked Version to produce a modified Combined Work, in the
+       manner specified by section 6 of the GNU GPL for conveying
+       Corresponding Source.
+
+       1) Use a suitable shared library mechanism for linking with the
+       Library.  A suitable mechanism is one that (a) uses at run time
+       a copy of the Library already present on the user's computer
+       system, and (b) will operate properly with a modified version
+       of the Library that is interface-compatible with the Linked
+       Version.
+
+   e) Provide Installation Information, but only if you would otherwise
+   be required to provide such information under section 6 of the
+   GNU GPL, and only to the extent that such information is
+   necessary to install and execute a modified version of the
+   Combined Work produced by recombining or relinking the
+   Application with a modified version of the Linked Version. (If
+   you use option 4d0, the Installation Information must accompany
+   the Minimal Corresponding Source and Corresponding Application
+   Code. If you use option 4d1, you must provide the Installation
+   Information in the manner specified by section 6 of the GNU GPL
+   for conveying Corresponding Source.)
+
+  5. Combined Libraries.
+
+  You may place library facilities that are a work based on the
+Library side by side in a single library together with other library
+facilities that are not Applications and are not covered by this
+License, and convey such a combined library under terms of your
+choice, if you do both of the following:
+
+   a) Accompany the combined library with a copy of the same work based
+   on the Library, uncombined with any other library facilities,
+   conveyed under the terms of this License.
+
+   b) Give prominent notice with the combined library that part of it
+   is a work based on the Library, and explaining where to find the
+   accompanying uncombined form of the same work.
+
+  6. Revised Versions of the GNU Lesser General Public License.
+
+  The Free Software Foundation may publish revised and/or new versions
+of the GNU Lesser General Public License from time to time. Such new
+versions will be similar in spirit to the present version, but may
+differ in detail to address new problems or concerns.
+
+  Each version is given a distinguishing version number. If the
+Library as you received it specifies that a certain numbered version
+of the GNU Lesser General Public License "or any later version"
+applies to it, you have the option of following the terms and
+conditions either of that published version or of any later version
+published by the Free Software Foundation. If the Library as you
+received it does not specify a version number of the GNU Lesser
+General Public License, you may choose any version of the GNU Lesser
+General Public License ever published by the Free Software Foundation.
+
+  If the Library as you received it specifies that a proxy can decide
+whether future versions of the GNU Lesser General Public License shall
+apply, that proxy's public statement of acceptance of any version is
+permanent authorization for you to choose that version for the
+Library.
diff --git a/libs/goat3d/Makefile b/libs/goat3d/Makefile
new file mode 100644 (file)
index 0000000..6c02b17
--- /dev/null
@@ -0,0 +1,15 @@
+obj = src/aabox.o src/chunk.o src/dynarr.o src/extmesh.o src/g3danm.o \
+         src/g3dscn.o src/goat3d.o src/log.o src/read.o src/track.o src/write.o
+alib = ../unix/goat3d.a
+
+CFLAGS = -O3 -Iinclude -I../treestor/include -I..
+
+$(alib): $(obj)
+       $(AR) rcs $@ $(obj)
+
+.c.o:
+       $(CC) $(CFLAGS) -c $< -o $@
+
+.PHONY: clean
+clean:
+       rm -f $(obj) $(alib)
diff --git a/libs/goat3d/README.md b/libs/goat3d/README.md
new file mode 100644 (file)
index 0000000..951207d
--- /dev/null
@@ -0,0 +1,55 @@
+goat3d
+======
+
+About
+-----
+Goat3D is a hierarchical 3D scene, character, and animation file format, and
+acompanying read/write library, targeting mostly real-time applications.
+
+The specification defines a hierarchical structure (see `doc/goatfmt`, and
+`doc/goatanimfmt` for details), which can be stored in either text or binary
+form. An application using the provided library to read/write goat3d files,
+should be able to handle either variant, with no extra effort (NOTE: currently
+the binary format is not implemented). The animations can be part of the scene
+file, or in separate files.
+
+This project provides the specification of the file format, a simple library
+with a clean C API for reading and writing files in the goat3d scene and
+animation files, as well as a number of tools dealing with such files.
+
+Specifically, at the moment, the goat3d project provides the following:
+ - *libgoat3d*, a library for reading and writing goat3d scene and animation files.
+ - *ass2goat*, a universal 3D asset conversion utility based on the excellent
+   assimp library, from a huge number of 3D file formats to the goat3d file
+   format.
+ - *goatinfo*, a command-line tool for inspecting the contents of goat3d files.
+ - *goatview*, a 3D scene and animation preview tool, based on OpenGL and Qt.
+ - *goatprim*, a procedural 3D model (primitive) generator for quick testing.
+
+License
+-------
+Copyright (C) 2014-2023 John Tsiombikas <nuclear@member.fsf.org>
+
+Goat3D is free software, you may use, modify and/or redistribute it under the
+terms of the GNU Lesser General Public License v3, or at your option any later
+version published by the Free Software Foundation. See COPYING and
+COPYING.LESSER for details.
+
+Build
+-----
+To build and install libgoat3d on UNIX, run the usual:
+
+    ./configure
+    make
+    make install
+
+See `./configure --help` for a complete list of build-time options.
+
+To cross-compile for windows with mingw-w64, try the following incantation:
+
+    ./configure --prefix=/usr/i686-w64-mingw32
+    make CC=i686-w64-mingw32-gcc AR=i686-w64-mingw32-ar sys=mingw
+    make install sys=mingw
+
+The rest of the tools can be built and installed in the exact same way from
+their respective subdirectories.
diff --git a/libs/goat3d/include/goat3d.h b/libs/goat3d/include/goat3d.h
new file mode 100644 (file)
index 0000000..8b9b38d
--- /dev/null
@@ -0,0 +1,383 @@
+/*
+goat3d - 3D scene, and animation file format library.
+Copyright (C) 2013-2023  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/>.
+*/
+#ifndef GOAT3D_H_
+#define GOAT3D_H_
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#ifdef WIN32
+#define GOAT3DAPI      __declspec(dllexport)
+#else
+#ifdef __GNUC__
+#define GOAT3DAPI      __attribute__((visibility("default")))
+#else
+#define GOAT3DAPI
+#endif
+#endif
+
+#define GOAT3D_MAT_ATTR_DIFFUSE                        "diffuse"
+#define GOAT3D_MAT_ATTR_SPECULAR               "specular"
+#define GOAT3D_MAT_ATTR_SHININESS              "shininess"
+#define GOAT3D_MAT_ATTR_NORMAL                 "normal"
+#define GOAT3D_MAT_ATTR_BUMP                   "bump"
+#define GOAT3D_MAT_ATTR_REFLECTION             "reflection"
+#define GOAT3D_MAT_ATTR_TRANSMISSION   "transmission"
+#define GOAT3D_MAT_ATTR_IOR                            "ior"
+#define GOAT3D_MAT_ATTR_ALPHA                  "alpha"
+
+enum goat3d_mesh_attrib {
+       GOAT3D_MESH_ATTR_VERTEX,
+       GOAT3D_MESH_ATTR_NORMAL,
+       GOAT3D_MESH_ATTR_TANGENT,
+       GOAT3D_MESH_ATTR_TEXCOORD,
+       GOAT3D_MESH_ATTR_SKIN_WEIGHT,
+       GOAT3D_MESH_ATTR_SKIN_MATRIX,
+       GOAT3D_MESH_ATTR_COLOR,
+
+       NUM_GOAT3D_MESH_ATTRIBS
+};
+
+enum goat3d_node_type {
+       GOAT3D_NODE_NULL,
+       GOAT3D_NODE_MESH,
+       GOAT3D_NODE_LIGHT,
+       GOAT3D_NODE_CAMERA
+};
+
+/* immediate mode mesh construction primitive type */
+enum goat3d_im_primitive {
+       GOAT3D_TRIANGLES,
+       GOAT3D_QUADS
+};
+
+enum goat3d_track_type {
+       GOAT3D_TRACK_VAL,
+       GOAT3D_TRACK_VEC3,
+       GOAT3D_TRACK_VEC4,
+       GOAT3D_TRACK_QUAT,
+       GOAT3D_TRACK_POS        = GOAT3D_TRACK_VEC3 | 0x100,
+       GOAT3D_TRACK_ROT        = GOAT3D_TRACK_QUAT | 0x200,
+       GOAT3D_TRACK_SCALE      = GOAT3D_TRACK_VEC3 | 0x300
+};
+
+struct goat3d_key {
+       long tm;
+       float val[4];
+};
+
+/* track interpolation modes */
+enum goat3d_interp {
+       GOAT3D_INTERP_STEP,
+       GOAT3D_INTERP_LINEAR,
+       GOAT3D_INTERP_CUBIC
+};
+/* track extrapolation modes */
+enum goat3d_extrap {
+       GOAT3D_EXTRAP_EXTEND,
+       GOAT3D_EXTRAP_CLAMP,
+       GOAT3D_EXTRAP_REPEAT,
+       GOAT3D_EXTRAP_PINGPONG
+};
+
+enum goat3d_option {
+       GOAT3D_OPT_SAVEXML,             /* save in XML format (dropped) */
+       GOAT3D_OPT_SAVETEXT,    /* save in text format */
+       GOAT3D_OPT_SAVEBINDATA, /* save mesh data in text files as binary blobs */
+       GOAT3D_OPT_SAVEBIN,             /* not implemented yet */
+       GOAT3D_OPT_SAVEGLTF,    /* not implemented yet */
+       GOAT3D_OPT_SAVEGLB,             /* not implemented yet */
+
+       NUM_GOAT3D_OPTIONS
+};
+
+struct goat3d;
+struct goat3d_material;
+struct goat3d_mtlattr;
+struct goat3d_mesh;
+struct goat3d_light;
+struct goat3d_camera;
+struct goat3d_node;
+struct goat3d_anim;
+struct goat3d_track;
+
+struct goat3d_io {
+       void *cls;      /* closure data */
+
+       long (*read)(void *buf, size_t bytes, void *uptr);
+       long (*write)(const void *buf, size_t bytes, void *uptr);
+       long (*seek)(long offs, int whence, void *uptr);
+};
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* construction/destruction */
+GOAT3DAPI struct goat3d *goat3d_create(void);
+GOAT3DAPI void goat3d_free(struct goat3d *g);
+
+GOAT3DAPI void goat3d_setopt(struct goat3d *g, enum goat3d_option opt, int val);
+GOAT3DAPI int goat3d_getopt(const struct goat3d *g, enum goat3d_option opt);
+
+/* load/save */
+GOAT3DAPI int goat3d_load(struct goat3d *g, const char *fname);
+GOAT3DAPI int goat3d_save(const struct goat3d *g, const char *fname);
+
+GOAT3DAPI int goat3d_load_file(struct goat3d *g, FILE *fp);
+GOAT3DAPI int goat3d_save_file(const struct goat3d *g, FILE *fp);
+
+GOAT3DAPI int goat3d_load_io(struct goat3d *g, struct goat3d_io *io);
+GOAT3DAPI int goat3d_save_io(const struct goat3d *g, struct goat3d_io *io);
+
+/* load/save animation files (g must already be loaded to load animations) */
+GOAT3DAPI int goat3d_load_anim(struct goat3d *g, const char *fname);
+GOAT3DAPI int goat3d_save_anim(const struct goat3d *g, const char *fname);
+
+GOAT3DAPI int goat3d_load_anim_file(struct goat3d *g, FILE *fp);
+GOAT3DAPI int goat3d_save_anim_file(const struct goat3d *g, FILE *fp);
+
+GOAT3DAPI int goat3d_load_anim_io(struct goat3d *g, struct goat3d_io *io);
+GOAT3DAPI int goat3d_save_anim_io(const struct goat3d *g, struct goat3d_io *io);
+
+/* misc scene properties */
+GOAT3DAPI int goat3d_set_name(struct goat3d *g, const char *name);
+GOAT3DAPI const char *goat3d_get_name(const struct goat3d *g);
+
+GOAT3DAPI void goat3d_set_ambient(struct goat3d *g, const float *ambient);
+GOAT3DAPI void goat3d_set_ambient3f(struct goat3d *g, float ar, float ag, float ab);
+GOAT3DAPI const float *goat3d_get_ambient(const struct goat3d *g);
+
+GOAT3DAPI int goat3d_get_bounds(const struct goat3d *g, float *bmin, float *bmax);
+
+/* materials */
+GOAT3DAPI int goat3d_add_mtl(struct goat3d *g, struct goat3d_material *mtl);
+GOAT3DAPI int goat3d_get_mtl_count(struct goat3d *g);
+GOAT3DAPI struct goat3d_material *goat3d_get_mtl(struct goat3d *g, int idx);
+GOAT3DAPI struct goat3d_material *goat3d_get_mtl_by_name(struct goat3d *g, const char *name);
+
+GOAT3DAPI struct goat3d_material *goat3d_create_mtl(void);
+GOAT3DAPI void goat3d_destroy_mtl(struct goat3d_material *mtl);
+
+GOAT3DAPI int goat3d_set_mtl_name(struct goat3d_material *mtl, const char *name);
+GOAT3DAPI const char *goat3d_get_mtl_name(const struct goat3d_material *mtl);
+
+GOAT3DAPI int goat3d_set_mtl_attrib(struct goat3d_material *mtl, const char *attrib, const float *val);
+GOAT3DAPI int goat3d_set_mtl_attrib1f(struct goat3d_material *mtl, const char *attrib, float val);
+GOAT3DAPI int goat3d_set_mtl_attrib3f(struct goat3d_material *mtl, const char *attrib, float r, float g, float b);
+GOAT3DAPI int goat3d_set_mtl_attrib4f(struct goat3d_material *mtl, const char *attrib, float r, float g, float b, float a);
+GOAT3DAPI const float *goat3d_get_mtl_attrib(struct goat3d_material *mtl, const char *attrib);
+
+GOAT3DAPI int goat3d_set_mtl_attrib_map(struct goat3d_material *mtl, const char *attrib, const char *mapname);
+GOAT3DAPI const char *goat3d_get_mtl_attrib_map(struct goat3d_material *mtl, const char *attrib);
+
+GOAT3DAPI int goat3d_get_mtl_attrib_count(struct goat3d_material *mtl);
+GOAT3DAPI const char *goat3d_get_mtl_attrib_name(struct goat3d_material *mtl, int idx);
+
+
+/* meshes */
+GOAT3DAPI int goat3d_add_mesh(struct goat3d *g, struct goat3d_mesh *mesh);
+GOAT3DAPI int goat3d_get_mesh_count(struct goat3d *g);
+GOAT3DAPI struct goat3d_mesh *goat3d_get_mesh(struct goat3d *g, int idx);
+GOAT3DAPI struct goat3d_mesh *goat3d_get_mesh_by_name(struct goat3d *g, const char *name);
+
+GOAT3DAPI struct goat3d_mesh *goat3d_create_mesh(void);
+GOAT3DAPI void goat3d_destroy_mesh(struct goat3d_mesh *mesh);
+
+GOAT3DAPI int goat3d_set_mesh_name(struct goat3d_mesh *mesh, const char *name);
+GOAT3DAPI const char *goat3d_get_mesh_name(const struct goat3d_mesh *mesh);
+
+GOAT3DAPI void goat3d_set_mesh_mtl(struct goat3d_mesh *mesh, struct goat3d_material *mtl);
+GOAT3DAPI struct goat3d_material *goat3d_get_mesh_mtl(struct goat3d_mesh *mesh);
+
+GOAT3DAPI int goat3d_get_mesh_vertex_count(struct goat3d_mesh *mesh);
+GOAT3DAPI int goat3d_get_mesh_attrib_count(struct goat3d_mesh *mesh, enum goat3d_mesh_attrib attrib);
+GOAT3DAPI int goat3d_get_mesh_face_count(struct goat3d_mesh *mesh);
+
+/* sets all the data for a single vertex attribute array in one go.
+ * vnum is the number of *vertices* to be set, not the number of floats, ints or whatever
+ * data is expected to be something different depending on the attribute:
+ *  - GOAT3D_MESH_ATTR_VERTEX       - 3 floats per vertex
+ *  - GOAT3D_MESH_ATTR_NORMAL       - 3 floats per vertex
+ *  - GOAT3D_MESH_ATTR_TANGENT      - 3 floats per vertex
+ *  - GOAT3D_MESH_ATTR_TEXCOORD     - 2 floats per vertex
+ *  - GOAT3D_MESH_ATTR_SKIN_WEIGHT  - 4 floats per vertex
+ *  - GOAT3D_MESH_ATTR_SKIN_MATRIX  - 4 ints per vertex
+ *  - GOAT3D_MESH_ATTR_COLOR        - 4 floats per vertex
+ */
+GOAT3DAPI int goat3d_set_mesh_attribs(struct goat3d_mesh *mesh, enum goat3d_mesh_attrib attrib,
+               const void *data, int vnum);
+GOAT3DAPI int goat3d_add_mesh_attrib1f(struct goat3d_mesh *mesh, enum goat3d_mesh_attrib attrib, float val);
+GOAT3DAPI int goat3d_add_mesh_attrib2f(struct goat3d_mesh *mesh, enum goat3d_mesh_attrib attrib,
+               float x, float y);
+GOAT3DAPI int goat3d_add_mesh_attrib3f(struct goat3d_mesh *mesh, enum goat3d_mesh_attrib attrib,
+               float x, float y, float z);
+GOAT3DAPI int goat3d_add_mesh_attrib4f(struct goat3d_mesh *mesh, enum goat3d_mesh_attrib attrib,
+               float x, float y, float z, float w);
+/* returns a pointer to the beginning of the requested mesh attribute array */
+GOAT3DAPI void *goat3d_get_mesh_attribs(struct goat3d_mesh *mesh, enum goat3d_mesh_attrib attrib);
+/* returns a pointer to the requested mesh attribute */
+GOAT3DAPI void *goat3d_get_mesh_attrib(struct goat3d_mesh *mesh, enum goat3d_mesh_attrib attrib, int idx);
+
+/* sets all the faces in one go. data is an array of 3 int vertex indices per face */
+GOAT3DAPI int goat3d_set_mesh_faces(struct goat3d_mesh *mesh, const int *data, int fnum);
+GOAT3DAPI int goat3d_add_mesh_face(struct goat3d_mesh *mesh, int a, int b, int c);
+/* returns a pointer to the beginning of the face index array */
+GOAT3DAPI int *goat3d_get_mesh_faces(struct goat3d_mesh *mesh);
+/* returns a pointer to a face index */
+GOAT3DAPI int *goat3d_get_mesh_face(struct goat3d_mesh *mesh, int idx);
+
+/* immediate mode OpenGL-like interface for setting mesh data
+ *  NOTE: using this interface will result in no vertex sharing between faces
+ * NOTE2: the immedate mode interface is not thread-safe, either use locks, or don't
+ *        use it at all in multithreaded situations.
+ */
+GOAT3DAPI void goat3d_begin(struct goat3d_mesh *mesh, enum goat3d_im_primitive prim);
+GOAT3DAPI void goat3d_end(void);
+GOAT3DAPI void goat3d_vertex3f(float x, float y, float z);
+GOAT3DAPI void goat3d_normal3f(float x, float y, float z);
+GOAT3DAPI void goat3d_tangent3f(float x, float y, float z);
+GOAT3DAPI void goat3d_texcoord2f(float x, float y);
+GOAT3DAPI void goat3d_skin_weight4f(float x, float y, float z, float w);
+GOAT3DAPI void goat3d_skin_matrix4i(int x, int y, int z, int w);
+GOAT3DAPI void goat3d_color3f(float x, float y, float z);
+GOAT3DAPI void goat3d_color4f(float x, float y, float z, float w);
+
+GOAT3DAPI void goat3d_get_mesh_bounds(const struct goat3d_mesh *mesh, float *bmin, float *bmax);
+
+/* lights (TODO) */
+GOAT3DAPI int goat3d_add_light(struct goat3d *g, struct goat3d_light *lt);
+GOAT3DAPI int goat3d_get_light_count(struct goat3d *g);
+GOAT3DAPI struct goat3d_light *goat3d_get_light(struct goat3d *g, int idx);
+GOAT3DAPI struct goat3d_light *goat3d_get_light_by_name(struct goat3d *g, const char *name);
+
+GOAT3DAPI struct goat3d_light *goat3d_create_light(void);
+GOAT3DAPI void goat3d_destroy_light(struct goat3d_light *lt);
+
+GOAT3DAPI int goat3d_set_light_name(struct goat3d_light *lt, const char *name);
+GOAT3DAPI const char *goat3d_get_light_name(const struct goat3d_light *lt);
+
+/* cameras (TODO) */
+GOAT3DAPI int goat3d_add_camera(struct goat3d *g, struct goat3d_camera *cam);
+GOAT3DAPI int goat3d_get_camera_count(struct goat3d *g);
+GOAT3DAPI struct goat3d_camera *goat3d_get_camera(struct goat3d *g, int idx);
+GOAT3DAPI struct goat3d_camera *goat3d_get_camera_by_name(struct goat3d *g, const char *name);
+
+GOAT3DAPI struct goat3d_camera *goat3d_create_camera(void);
+GOAT3DAPI void goat3d_destroy_camera(struct goat3d_camera *cam);
+
+GOAT3DAPI int goat3d_set_camera_name(struct goat3d_camera *cam, const char *name);
+GOAT3DAPI const char *goat3d_get_camera_name(const struct goat3d_camera *cam);
+
+/* nodes */
+GOAT3DAPI int goat3d_add_node(struct goat3d *g, struct goat3d_node *node);
+GOAT3DAPI int goat3d_get_node_count(struct goat3d *g);
+GOAT3DAPI struct goat3d_node *goat3d_get_node(struct goat3d *g, int idx);
+GOAT3DAPI struct goat3d_node *goat3d_get_node_by_name(struct goat3d *g, const char *name);
+
+GOAT3DAPI struct goat3d_node *goat3d_create_node(void);
+GOAT3DAPI void goat3d_destroy_node(struct goat3d_node *node);
+
+GOAT3DAPI int goat3d_set_node_name(struct goat3d_node *node, const char *name);
+GOAT3DAPI const char *goat3d_get_node_name(const struct goat3d_node *node);
+
+GOAT3DAPI void goat3d_set_node_object(struct goat3d_node *node, enum goat3d_node_type type, void *obj);
+GOAT3DAPI void *goat3d_get_node_object(const struct goat3d_node *node);
+GOAT3DAPI enum goat3d_node_type goat3d_get_node_type(const struct goat3d_node *node);
+
+GOAT3DAPI void goat3d_add_node_child(struct goat3d_node *node, struct goat3d_node *child);
+GOAT3DAPI int goat3d_get_node_child_count(const struct goat3d_node *node);
+GOAT3DAPI struct goat3d_node *goat3d_get_node_child(const struct goat3d_node *node, int idx);
+GOAT3DAPI struct goat3d_node *goat3d_get_node_parent(const struct goat3d_node *node);
+
+GOAT3DAPI void goat3d_set_node_position(struct goat3d_node *node, float x, float y, float z);
+GOAT3DAPI void goat3d_set_node_rotation(struct goat3d_node *node, float qx, float qy, float qz, float qw);
+GOAT3DAPI void goat3d_set_node_scaling(struct goat3d_node *node, float sx, float sy, float sz);
+
+GOAT3DAPI void goat3d_get_node_position(const struct goat3d_node *node, float *xptr, float *yptr, float *zptr);
+GOAT3DAPI void goat3d_get_node_rotation(const struct goat3d_node *node, float *xptr, float *yptr, float *zptr, float *wptr);
+GOAT3DAPI void goat3d_get_node_scaling(const struct goat3d_node *node, float *xptr, float *yptr, float *zptr);
+
+GOAT3DAPI void goat3d_set_node_pivot(struct goat3d_node *node, float x, float y, float z);
+GOAT3DAPI void goat3d_get_node_pivot(const struct goat3d_node *node, float *xptr, float *yptr, float *zptr);
+
+GOAT3DAPI void goat3d_get_node_matrix(const struct goat3d_node *node, float *matrix);
+/* same as above, but also takes hierarchy into account */
+GOAT3DAPI void goat3d_get_matrix(const struct goat3d_node *node, float *matrix);
+
+GOAT3DAPI void goat3d_get_node_bounds(const struct goat3d_node *node, float *bmin, float *bmax);
+
+/* keyframe track */
+GOAT3DAPI struct goat3d_track *goat3d_create_track(void);
+GOAT3DAPI void goat3d_destroy_track(struct goat3d_track *trk);
+
+GOAT3DAPI int goat3d_set_track_name(struct goat3d_track *trk, const char *name);
+GOAT3DAPI const char *goat3d_get_track_name(const struct goat3d_track *trk);
+
+GOAT3DAPI void goat3d_set_track_type(struct goat3d_track *trk, enum goat3d_track_type type);
+GOAT3DAPI enum goat3d_track_type goat3d_get_track_type(const struct goat3d_track *trk);
+
+GOAT3DAPI void goat3d_set_track_node(struct goat3d_track *trk, struct goat3d_node *node);
+GOAT3DAPI struct goat3d_node *goat3d_get_track_node(const struct goat3d_track *trk);
+
+GOAT3DAPI void goat3d_set_track_interp(struct goat3d_track *trk, enum goat3d_interp in);
+GOAT3DAPI enum goat3d_interp goat3d_get_track_interp(const struct goat3d_track *trk);
+GOAT3DAPI void goat3d_set_track_extrap(struct goat3d_track *trk, enum goat3d_extrap ex);
+GOAT3DAPI enum goat3d_extrap goat3d_get_track_extrap(const struct goat3d_track *trk);
+
+GOAT3DAPI int goat3d_set_track_key(struct goat3d_track *trk, const struct goat3d_key *key);
+GOAT3DAPI int goat3d_get_track_key(const struct goat3d_track *trk, int keyidx, struct goat3d_key *key);
+GOAT3DAPI int goat3d_get_track_key_count(const struct goat3d_track *trk);
+
+GOAT3DAPI int goat3d_set_track_val(struct goat3d_track *trk, long msec, float val);
+GOAT3DAPI int goat3d_set_track_vec3(struct goat3d_track *trk, long msec, float x, float y, float z);
+GOAT3DAPI int goat3d_set_track_vec4(struct goat3d_track *trk, long msec, float x, float y, float z, float w);
+GOAT3DAPI int goat3d_set_track_quat(struct goat3d_track *trk, long msec, float x, float y, float z, float w);
+
+GOAT3DAPI void goat3d_get_track_val(const struct goat3d_track *trk, long msec, float *valp);
+GOAT3DAPI void goat3d_get_track_vec3(const struct goat3d_track *trk, long msec, float *xp, float *yp, float *zp);
+GOAT3DAPI void goat3d_get_track_vec4(const struct goat3d_track *trk, long msec, float *xp, float *yp, float *zp, float *wp);
+GOAT3DAPI void goat3d_get_track_quat(const struct goat3d_track *trk, long msec, float *xp, float *yp, float *zp, float *wp);
+
+GOAT3DAPI long goat3d_get_track_timeline(const struct goat3d_track *trk, long *tstart, long *tend);
+
+/* animation */
+GOAT3DAPI int goat3d_add_anim(struct goat3d *g, struct goat3d_anim *anim);
+GOAT3DAPI int goat3d_get_anim_count(const struct goat3d *g);
+GOAT3DAPI struct goat3d_anim *goat3d_get_anim(const struct goat3d *g, int idx);
+GOAT3DAPI struct goat3d_anim *goat3d_get_anim_by_name(const struct goat3d *g, const char *name);
+
+GOAT3DAPI struct goat3d_anim *goat3d_create_anim(void);
+GOAT3DAPI void goat3d_destroy_anim(struct goat3d_anim *anim);
+
+GOAT3DAPI int goat3d_set_anim_name(struct goat3d_anim *anim, const char *name);
+GOAT3DAPI const char *goat3d_get_anim_name(const struct goat3d_anim *anim);
+
+GOAT3DAPI int goat3d_add_anim_track(struct goat3d_anim *anim, struct goat3d_track *trk);
+GOAT3DAPI struct goat3d_track *goat3d_get_anim_track(const struct goat3d_anim *anim, int idx);
+GOAT3DAPI struct goat3d_track *goat3d_get_anim_track_by_name(const struct goat3d_anim *anim, const char *name);
+GOAT3DAPI struct goat3d_track *goat3d_get_anim_track_by_type(const struct goat3d_anim *anim, enum goat3d_track_type type);
+GOAT3DAPI int goat3d_get_anim_track_count(const struct goat3d_anim *anim);
+
+GOAT3DAPI long goat3d_get_anim_timeline(const struct goat3d_anim *anim, long *tstart, long *tend);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* GOAT3D_H_ */
diff --git a/libs/goat3d/src/aabox.c b/libs/goat3d/src/aabox.c
new file mode 100644 (file)
index 0000000..2bbfb3b
--- /dev/null
@@ -0,0 +1,56 @@
+/*
+goat3d - 3D scene, and animation file format library.
+Copyright (C) 2013-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/>.
+*/
+#include <float.h>
+#include "aabox.h"
+
+void g3dimpl_aabox_init(struct aabox *box)
+{
+       cgm_vcons(&box->bmin, FLT_MAX, FLT_MAX, FLT_MAX);
+       cgm_vcons(&box->bmax, -FLT_MAX, -FLT_MAX, -FLT_MAX);
+}
+
+void g3dimpl_aabox_cons(struct aabox *box, float x0, float y0, float z0,
+               float x1, float y1, float z1)
+{
+       cgm_vcons(&box->bmin, x0, y0, z0);
+       cgm_vcons(&box->bmax, x1, y1, z1);
+}
+
+
+int g3dimpl_aabox_equal(const struct aabox *a, const struct aabox *b)
+{
+       if(a->bmin.x != b->bmin.x || a->bmin.y != b->bmin.y || a->bmin.z != b->bmin.z) {
+               return 0;
+       }
+       if(a->bmax.x != b->bmax.x || a->bmax.y != b->bmax.y || a->bmax.z != b->bmax.z) {
+               return 0;
+       }
+       return 1;
+}
+
+#define MIN(a, b)      ((a) < (b) ? (a) : (b))
+#define MAX(a, b)      ((a) > (b) ? (a) : (b))
+void g3dimpl_aabox_union(struct aabox *res, const struct aabox *a, const struct aabox *b)
+{
+       res->bmin.x = MIN(a->bmin.x, b->bmin.x);
+       res->bmin.y = MIN(a->bmin.y, b->bmin.y);
+       res->bmin.z = MIN(a->bmin.z, b->bmin.z);
+       res->bmax.x = MAX(a->bmax.x, b->bmax.x);
+       res->bmax.y = MAX(a->bmax.y, b->bmax.y);
+       res->bmax.z = MAX(a->bmax.z, b->bmax.z);
+}
diff --git a/libs/goat3d/src/aabox.h b/libs/goat3d/src/aabox.h
new file mode 100644 (file)
index 0000000..d9cb818
--- /dev/null
@@ -0,0 +1,36 @@
+/*
+goat3d - 3D scene, and animation file format library.
+Copyright (C) 2013-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/>.
+*/
+#ifndef AABOX_H_
+#define AABOX_H_
+
+#include "cgmath/cgmath.h"
+
+struct aabox {
+       cgm_vec3 bmin, bmax;
+};
+
+void g3dimpl_aabox_init(struct aabox *box);
+void g3dimpl_aabox_cons(struct aabox *box, float x0, float y0, float z0,
+               float x1, float y1, float z1);
+
+int g3dimpl_aabox_equal(const struct aabox *a, const struct aabox *b);
+
+void g3dimpl_aabox_union(struct aabox *res, const struct aabox *a,
+               const struct aabox *b);
+
+#endif /* AABOX_H_ */
diff --git a/libs/goat3d/src/chunk.c b/libs/goat3d/src/chunk.c
new file mode 100644 (file)
index 0000000..9043357
--- /dev/null
@@ -0,0 +1,47 @@
+/*
+goat3d - 3D scene, and animation file format library.
+Copyright (C) 2013-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/>.
+*/
+#include "goat3d.h"
+#include "chunk.h"
+
+void g3dimpl_chunk_header(struct chunk_header *hdr, int id)
+{
+       hdr->id = id;
+       hdr->size = sizeof *hdr;
+}
+
+int g3dimpl_write_chunk_header(const struct chunk_header *hdr, struct goat3d_io *io)
+{
+       io->seek(-(long)hdr->size, SEEK_CUR, io->cls);
+       if(io->write(hdr, sizeof *hdr, io->cls) < (long)sizeof *hdr) {
+               return -1;
+       }
+       return 0;
+}
+
+int g3dimpl_read_chunk_header(struct chunk_header *hdr, struct goat3d_io *io)
+{
+       if(io->read(hdr, sizeof *hdr, io->cls) < (long)sizeof *hdr) {
+               return -1;
+       }
+       return 0;
+}
+
+void g3dimpl_skip_chunk(const struct chunk_header *hdr, struct goat3d_io *io)
+{
+       io->seek(hdr->size - sizeof *hdr, SEEK_CUR, io->cls);
+}
diff --git a/libs/goat3d/src/chunk.h b/libs/goat3d/src/chunk.h
new file mode 100644 (file)
index 0000000..505c150
--- /dev/null
@@ -0,0 +1,146 @@
+/*
+goat3d - 3D scene, and animation file format library.
+Copyright (C) 2013-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/>.
+*/
+#ifndef CHUNK_H_
+#define CHUNK_H_
+
+#ifndef _MSC_VER
+#ifdef __sgi
+#include <inttypes.h>
+#else
+#include <stdint.h>
+#endif
+#else
+typedef unsigned __int32 uint32_t;
+#endif
+
+enum {
+       CNK_INVALID,            /* this shouldn't appear in files */
+       CNK_SCENE,                      /* the root chunk */
+
+       /* general purpose chunks */
+       CNK_INT,
+       CNK_INT4,
+       CNK_FLOAT,
+       CNK_FLOAT3,
+       CNK_FLOAT4,
+       CNK_STRING,
+
+       /* --- first level chunks --- */
+       /* children of CNK_SCENE */
+       CNK_ENV,                        /* environmental parameters */
+       CNK_MTL,                        /* material */
+       CNK_MESH,
+       CNK_LIGHT,
+       CNK_CAMERA,
+       CNK_NODE,
+
+       /* --- second level chunks --- */
+       /* children of CNK_ENV */
+       CNK_ENV_AMBIENT,        /* ambient color, contains a single CNK_FLOAT3 */
+       CNK_ENV_FOG,
+
+       /* --- third level chunks --- */
+       /* children of CNK_FOG */
+       CNK_FOG_COLOR,          /* fog color, contains a single CNK_FLOAT3 */
+       CNK_FOG_EXP,            /* fog exponent, contains a single CNK_FLOAT */
+
+       /* children of CNK_MTL */
+       CNK_MTL_NAME,           /* has a single CNK_STRING */
+       CNK_MTL_ATTR,           /* material attribute, has a CNK_STRING for its name, */
+                                               /* a CNK_MTL_ATTR_VAL, and optionally a CNK_MTL_ATTR_MAP */
+       /* children of CNK_MTL_ATTR */
+       CNK_MTL_ATTR_NAME,      /* has a single CNK_STRING */
+       CNK_MTL_ATTR_VAL,       /* can have a single CNK_FLOAT, CNK_FLOAT3, or CNK_FLOAT4 */
+       CNK_MTL_ATTR_MAP,       /* has a single CNK_STRING */
+
+       /* children of CNK_MESH */
+       CNK_MESH_NAME,                  /* has a single CNK_STRING */
+       CNK_MESH_MATERIAL,              /* has one of CNK_STRING or CNK_INT to identify the material */
+       CNK_MESH_VERTEX_LIST,   /* has a series of CNK_FLOAT3 chunks */
+       CNK_MESH_NORMAL_LIST,   /* has a series of CNK_FLOAT3 chunks */
+       CNK_MESH_TANGENT_LIST,  /* has a series of CNK_FLOAT3 chunks */
+       CNK_MESH_TEXCOORD_LIST, /* has a series of CNK_FLOAT3 chunks */
+       CNK_MESH_SKINWEIGHT_LIST,       /* has a series of CNK_FLOAT4 chunks (4 skin weights) */
+       CNK_MESH_SKINMATRIX_LIST,       /* has a series of CNK_INT4 chunks (4 matrix indices) */
+       CNK_MESH_COLOR_LIST,    /* has a series of CNK_FLOAT4 chunks */
+       CNK_MESH_BONES_LIST,    /* has a series of CNK_INT or CNK_STRING chunks identifying the bone nodes */
+       CNK_MESH_FACE_LIST,             /* has a series of CNK_FACE chunks */
+       CNK_MESH_FILE,                  /* optionally mesh data may be in another file, has a CNK_STRING filename */
+
+       /* child of CNK_MESH_FACE_LIST */
+       CNK_MESH_FACE,                  /* has three CNK_INT chunks */
+
+       /* children of CNK_LIGHT */
+       CNK_LIGHT_NAME,                 /* has a single CNK_STRING */
+       CNK_LIGHT_POS,                  /* has a single CNK_FLOAT3 */
+       CNK_LIGHT_COLOR,                /* has a single CNK_FLOAT3 */
+       CNK_LIGHT_ATTEN,                /* has a single CNK_FLOAT3 (constant, linear, squared attenuation) */
+       CNK_LIGHT_DISTANCE,             /* has a single CNK_FLOAT */
+       CNK_LIGHT_DIR,                  /* a single CNK_FLOAT3 (for spotlights and dir-lights) */
+       CNK_LIGHT_CONE_INNER,   /* single CNK_FLOAT, inner cone angle (for spotlights) */
+       CNK_LIGHT_CONE_OUTER,   /* single CNK_FLOAT, outer cone angle (for spotlights) */
+
+       /* children of CNK_CAMERA */
+       CNK_CAMERA_NAME,                /* has a single CNK_STRING */
+       CNK_CAMERA_POS,                 /* single CNK_FLOAT3 */
+       CNK_CAMERA_TARGET,              /* single CNK_FLOAT3 */
+       CNK_CAMERA_FOV,                 /* single CNK_FLOAT (field of view in radians) */
+       CNK_CAMERA_NEARCLIP,    /* single CNK_FLOAT (near clipping plane distance) */
+       CNK_CAMERA_FARCLIP,             /* signle CNK_FLOAT (far clipping plane distance) */
+
+       /* children of CNK_NODE */
+       CNK_NODE_NAME,                  /* node name, a single CNK_STRING */
+       CNK_NODE_PARENT,                /* it can have a CNK_INT or a CNK_STRING to identify the parent node */
+
+       CNK_NODE_MESH,                  /* it can have a CNK_INT or a CNK_STRING to identify this node's mesh */
+       CNK_NODE_LIGHT,                 /* same as CNK_NODE_MESH */
+       CNK_NODE_CAMERA,                /* same as CNK_NODE_MESH */
+
+       CNK_NODE_POS,                   /* has a CNK_FLOAT3, position vector */
+       CNK_NODE_ROT,                   /* has a CNK_FLOAT4, rotation quaternion (x, y, z imaginary, w real) */
+       CNK_NODE_SCALE,                 /* has a CNK_FLOAT3, scaling */
+       CNK_NODE_PIVOT,                 /* has a CNK_FLOAT3, pivot point */
+
+       CNK_NODE_MATRIX0,               /* has a CNK_FLOAT4, first matrix row (4x3) */
+       CNK_NODE_MATRXI1,               /* has a CNK_FLOAT4, second matrix row (4x3) */
+       CNK_NODE_MATRIX2,               /* has a CNK_FLOAT4, third matrix row (4x3) */
+
+       CNK_ANIM,               /* the animation root chunk */
+
+       MAX_NUM_CHUNKS
+};
+
+#define UNKNOWN_SIZE   ((uint32_t)0xbaadf00d)
+
+struct chunk_header {
+       uint32_t id;
+       uint32_t size;
+};
+
+struct chunk {
+       struct chunk_header hdr;
+       char data[1];
+};
+
+
+void g3dimpl_chunk_header(struct chunk_header *hdr, int id);
+int g3dimpl_write_chunk_header(const struct chunk_header *hdr, struct goat3d_io *io);
+int g3dimpl_read_chunk_header(struct chunk_header *hdr, struct goat3d_io *io);
+void g3dimpl_skip_chunk(const struct chunk_header *hdr, struct goat3d_io *io);
+
+#endif /* CHUNK_H_ */
diff --git a/libs/goat3d/src/dynarr.c b/libs/goat3d/src/dynarr.c
new file mode 100644 (file)
index 0000000..59bbf8c
--- /dev/null
@@ -0,0 +1,141 @@
+/* dynarr - dynamic resizable C array data structure
+ * author: John Tsiombikas <nuclear@member.fsf.org>
+ * license: public domain
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "dynarr.h"
+
+/* The array descriptor keeps auxilliary information needed to manipulate
+ * the dynamic array. It's allocated adjacent to the array buffer.
+ */
+struct arrdesc {
+       int nelem, szelem;
+       int max_elem;
+       int bufsz;      /* not including the descriptor */
+};
+
+#define DESC(x)                ((struct arrdesc*)((char*)(x) - sizeof(struct arrdesc)))
+
+void *dynarr_alloc(int elem, int szelem)
+{
+       struct arrdesc *desc;
+
+       if(!(desc = malloc(elem * szelem + sizeof *desc))) {
+               return 0;
+       }
+       desc->nelem = desc->max_elem = elem;
+       desc->szelem = szelem;
+       desc->bufsz = elem * szelem;
+       return (char*)desc + sizeof *desc;
+}
+
+void dynarr_free(void *da)
+{
+       if(da) {
+               free(DESC(da));
+       }
+}
+
+void *dynarr_resize(void *da, int elem)
+{
+       int newsz;
+       void *tmp;
+       struct arrdesc *desc;
+
+       if(!da) return 0;
+       desc = DESC(da);
+
+       newsz = desc->szelem * elem;
+
+       if(!(tmp = realloc(desc, newsz + sizeof *desc))) {
+               return 0;
+       }
+       desc = tmp;
+
+       desc->nelem = desc->max_elem = elem;
+       desc->bufsz = newsz;
+       return (char*)desc + sizeof *desc;
+}
+
+int dynarr_empty(void *da)
+{
+       return DESC(da)->nelem ? 0 : 1;
+}
+
+int dynarr_size(void *da)
+{
+       return DESC(da)->nelem;
+}
+
+
+void *dynarr_clear(void *da)
+{
+       return dynarr_resize(da, 0);
+}
+
+/* stack semantics */
+void *dynarr_push(void *da, void *item)
+{
+       struct arrdesc *desc;
+       int nelem;
+
+       desc = DESC(da);
+       nelem = desc->nelem;
+
+       if(nelem >= desc->max_elem) {
+               /* need to resize */
+               struct arrdesc *tmp;
+               int newsz = desc->max_elem ? desc->max_elem * 2 : 1;
+
+               if(!(tmp = dynarr_resize(da, newsz))) {
+                       fprintf(stderr, "failed to resize\n");
+                       return da;
+               }
+               da = tmp;
+               desc = DESC(da);
+               desc->nelem = nelem;
+       }
+
+       if(item) {
+               memcpy((char*)da + desc->nelem * desc->szelem, item, desc->szelem);
+       }
+       desc->nelem++;
+       return da;
+}
+
+void *dynarr_pop(void *da)
+{
+       struct arrdesc *desc;
+       int nelem;
+
+       desc = DESC(da);
+       nelem = desc->nelem;
+
+       if(!nelem) return da;
+
+       if(nelem <= desc->max_elem / 3) {
+               /* reclaim space */
+               struct arrdesc *tmp;
+               int newsz = desc->max_elem / 2;
+
+               if(!(tmp = dynarr_resize(da, newsz))) {
+                       fprintf(stderr, "failed to resize\n");
+                       return da;
+               }
+               da = tmp;
+               desc = DESC(da);
+               desc->nelem = nelem;
+       }
+       desc->nelem--;
+
+       return da;
+}
+
+void *dynarr_finalize(void *da)
+{
+       struct arrdesc *desc = DESC(da);
+       memmove(desc, da, desc->bufsz);
+       return desc;
+}
diff --git a/libs/goat3d/src/dynarr.h b/libs/goat3d/src/dynarr.h
new file mode 100644 (file)
index 0000000..619feeb
--- /dev/null
@@ -0,0 +1,90 @@
+/* dynarr - dynamic resizable C array data structure
+ * author: John Tsiombikas <nuclear@member.fsf.org>
+ * license: public domain
+ */
+#ifndef DYNARR_H_
+#define DYNARR_H_
+
+#define dynarr_alloc   g3dimpl_dynarr_alloc
+#define dynarr_free            g3dimpl_dynarr_free
+#define dynarr_resize  g3dimpl_dynarr_resize
+#define dynarr_empty   g3dimpl_dynarr_empty
+#define dynarr_size            g3dimpl_dynarr_size
+#define dynarr_clear   g3dimpl_dynarr_clear
+#define dynarr_push            g3dimpl_dynarr_push
+#define dynarr_pop             g3dimpl_dynarr_pop
+#define dynarr_finalize        g3dimpl_dynarr_finalize
+
+/* usage example:
+ * -------------
+ * int *arr = dynarr_alloc(0, sizeof *arr);
+ *
+ * int x = 10;
+ * arr = dynarr_push(arr, &x);
+ * x = 5;
+ * arr = dynarr_push(arr, &x);
+ * x = 42;
+ * arr = dynarr_push(arr, &x);
+ *
+ * for(i=0; i<dynarr_size(arr); i++) {
+ *     printf("%d\n", arr[i]);
+ *  }
+ *  dynarr_free(arr);
+ */
+
+void *dynarr_alloc(int elem, int szelem);
+void dynarr_free(void *da);
+void *dynarr_resize(void *da, int elem);
+
+/* dynarr_empty returns non-zero if the array is empty
+ * Complexity: O(1) */
+int dynarr_empty(void *da);
+/* dynarr_size returns the number of elements in the array
+ * Complexity: O(1) */
+int dynarr_size(void *da);
+
+void *dynarr_clear(void *da);
+
+/* stack semantics */
+void *dynarr_push(void *da, void *item);
+void *dynarr_pop(void *da);
+
+/* Finalize the array. No more resizing is possible after this call.
+ * Use free() instead of dynarr_free() to deallocate a finalized array.
+ * Returns pointer to the finalized array.
+ * dynarr_finalize can't fail.
+ * Complexity: O(n)
+ */
+void *dynarr_finalize(void *da);
+
+/* helper macros */
+#define DYNARR_RESIZE(da, n) \
+       do { (da) = dynarr_resize((da), (n)); } while(0)
+#define DYNARR_CLEAR(da) \
+       do { (da) = dynarr_clear(da); } while(0)
+#define DYNARR_PUSH(da, item) \
+       do { (da) = dynarr_push((da), (item)); } while(0)
+#define DYNARR_POP(da) \
+       do { (da) = dynarr_pop(da); } while(0)
+
+/* utility macros to push characters to a string. assumes and maintains
+ * the invariant that the last element is always a zero
+ */
+#define DYNARR_STRPUSH(da, c) \
+       do { \
+               char cnull = 0, ch = (char)(c); \
+               (da) = dynarr_pop(da); \
+               (da) = dynarr_push((da), &ch); \
+               (da) = dynarr_push((da), &cnull); \
+       } while(0)
+
+#define DYNARR_STRPOP(da) \
+       do { \
+               char cnull = 0; \
+               (da) = dynarr_pop(da); \
+               (da) = dynarr_pop(da); \
+               (da) = dynarr_push((da), &cnull); \
+       } while(0)
+
+
+#endif /* DYNARR_H_ */
diff --git a/libs/goat3d/src/extmesh.c b/libs/goat3d/src/extmesh.c
new file mode 100644 (file)
index 0000000..6c13be8
--- /dev/null
@@ -0,0 +1,25 @@
+/*
+goat3d - 3D scene, and animation file format library.
+Copyright (C) 2013-2023  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/>.
+*/
+#include "g3dscn.h"
+#include "log.h"
+
+int g3dimpl_loadmesh(struct goat3d_mesh *mesh, const char *fname)
+{
+       goat3d_logmsg(LOG_ERROR, "loading external mesh files not implemented yet!\n");
+       return -1;
+}
diff --git a/libs/goat3d/src/g3danm.c b/libs/goat3d/src/g3danm.c
new file mode 100644 (file)
index 0000000..8fdc41a
--- /dev/null
@@ -0,0 +1,68 @@
+/*
+goat3d - 3D scene, and animation file format library.
+Copyright (C) 2013-2023  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/>.
+*/
+#include <stdlib.h>
+#include "g3danm.h"
+#include "dynarr.h"
+#include "log.h"
+
+int g3dimpl_anim_init(struct goat3d_anim *anim)
+{
+       anim->name = 0;
+       if(!(anim->tracks = dynarr_alloc(0, sizeof *anim->tracks))) {
+               goat3d_logmsg(LOG_ERROR, "g3dimpl_anim_init: failed to allocate tracks array\n");
+               return -1;
+       }
+       return 0;
+}
+
+void g3dimpl_anim_destroy(struct goat3d_anim *anim)
+{
+       int i, num;
+
+       if(!anim) return;
+
+       free(anim->name);
+       num = dynarr_size(anim->tracks);
+       for(i=0; i<num; i++) {
+               goat3d_destroy_track(anim->tracks[i]);
+       }
+       dynarr_free(anim->tracks);
+}
+
+
+const char *g3dimpl_trktypestr(enum goat3d_track_type type)
+{
+       switch(type) {
+       case GOAT3D_TRACK_VAL:
+               return "val";
+       case GOAT3D_TRACK_VEC3:
+               return "vec3";
+       case GOAT3D_TRACK_VEC4:
+               return "vec4";
+       case GOAT3D_TRACK_QUAT:
+               return "quat";
+       case GOAT3D_TRACK_POS:
+               return "pos";
+       case GOAT3D_TRACK_ROT:
+               return "rot";
+       case GOAT3D_TRACK_SCALE:
+               return "scale";
+       }
+       return 0;
+}
+
diff --git a/libs/goat3d/src/g3danm.h b/libs/goat3d/src/g3danm.h
new file mode 100644 (file)
index 0000000..36da018
--- /dev/null
@@ -0,0 +1,41 @@
+/*
+goat3d - 3D scene, and animation file format library.
+Copyright (C) 2013-2023  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/>.
+*/
+#ifndef G3DANM_H_
+#define G3DANM_H_
+
+#include "goat3d.h"
+#include "track.h"
+
+struct goat3d_track {
+       char *name;
+       enum goat3d_track_type type;
+       struct anm_track trk[4];
+       struct goat3d_node *node;       /* node associated with this track */
+};
+
+struct goat3d_anim {
+       char *name;
+       struct goat3d_track **tracks;   /* dynarr */
+};
+
+int g3dimpl_anim_init(struct goat3d_anim *anim);
+void g3dimpl_anim_destroy(struct goat3d_anim *anim);
+
+const char *g3dimpl_trktypestr(enum goat3d_track_type type);
+
+#endif /* G3DANM_H_ */
diff --git a/libs/goat3d/src/g3dscn.c b/libs/goat3d/src/g3dscn.c
new file mode 100644 (file)
index 0000000..f58bc19
--- /dev/null
@@ -0,0 +1,212 @@
+/*
+goat3d - 3D scene, and animation file format library.
+Copyright (C) 2013-2023  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/>.
+*/
+#include <string.h>
+#include "g3dscn.h"
+#include "dynarr.h"
+
+int g3dimpl_obj_init(struct object *o, int type)
+{
+       static int last_mesh, last_light, last_camera;
+       struct goat3d_mesh *m;
+       struct goat3d_light *lt;
+       struct goat3d_camera *cam;
+       char *name;
+
+       if(!(name = malloc(64))) {
+               return -1;
+       }
+
+       switch(type) {
+       case OBJTYPE_MESH:
+               m = (struct goat3d_mesh*)o;
+               memset(m, 0, sizeof *m);
+               if(!(m->vertices = dynarr_alloc(0, sizeof *m->vertices))) goto err;
+               if(!(m->normals = dynarr_alloc(0, sizeof *m->normals))) goto err;
+               if(!(m->tangents = dynarr_alloc(0, sizeof *m->tangents))) goto err;
+               if(!(m->texcoords = dynarr_alloc(0, sizeof *m->texcoords))) goto err;
+               if(!(m->skin_weights = dynarr_alloc(0, sizeof *m->skin_weights))) goto err;
+               if(!(m->skin_matrices = dynarr_alloc(0, sizeof *m->skin_matrices))) goto err;
+               if(!(m->colors = dynarr_alloc(0, sizeof *m->colors))) goto err;
+               if(!(m->faces = dynarr_alloc(0, sizeof *m->faces))) goto err;
+               if(!(m->bones = dynarr_alloc(0, sizeof *m->bones))) goto err;
+               sprintf(name, "mesh%d", last_mesh++);
+               break;
+
+       case OBJTYPE_LIGHT:
+               lt = (struct goat3d_light*)o;
+               memset(lt, 0, sizeof *lt);
+               cgm_vcons(&lt->color, 1, 1, 1);
+               cgm_vcons(&lt->attenuation, 1, 0, 0);
+               cgm_vcons(&lt->dir, 0, 0, 1);
+               lt->inner_cone = cgm_deg_to_rad(30);
+               lt->outer_cone = cgm_deg_to_rad(45);
+               sprintf(name, "light%d", last_light++);
+               break;
+
+       case OBJTYPE_CAMERA:
+               cam = (struct goat3d_camera*)o;
+               memset(cam, 0, sizeof *cam);
+               cam->near_clip = 0.5f;
+               cam->far_clip = 500.0f;
+               cgm_vcons(&cam->up, 0, 1, 0);
+               sprintf(name, "camera%d", last_camera++);
+               break;
+
+       default:
+               return -1;
+       }
+
+       o->name = name;
+       o->type = type;
+       cgm_qcons(&o->rot, 0, 0, 0, 1);
+       cgm_vcons(&o->scale, 1, 1, 1);
+       return 0;
+
+err:
+       free(name);
+       g3dimpl_obj_destroy(o);
+       return -1;
+}
+
+void g3dimpl_obj_destroy(struct object *o)
+{
+       struct goat3d_mesh *m;
+
+       switch(o->type) {
+       case OBJTYPE_MESH:
+               m = (struct goat3d_mesh*)o;
+               dynarr_free(m->vertices);
+               dynarr_free(m->normals);
+               dynarr_free(m->tangents);
+               dynarr_free(m->texcoords);
+               dynarr_free(m->skin_weights);
+               dynarr_free(m->skin_matrices);
+               dynarr_free(m->colors);
+               dynarr_free(m->faces);
+               dynarr_free(m->bones);
+               break;
+
+       default:
+               break;
+       }
+}
+
+void g3dimpl_mesh_bounds(struct aabox *bb, struct goat3d_mesh *m, float *xform)
+{
+       int i, nverts;
+
+       g3dimpl_aabox_init(bb);
+
+       nverts = dynarr_size(m->vertices);
+       for(i=0; i<nverts; i++) {
+               cgm_vec3 v = m->vertices[i];
+               if(xform) cgm_vmul_m4v3(&v, xform);
+
+               if(v.x < bb->bmin.x) bb->bmin.x = v.x;
+               if(v.y < bb->bmin.y) bb->bmin.y = v.y;
+               if(v.z < bb->bmin.z) bb->bmin.z = v.z;
+
+               if(v.x > bb->bmax.x) bb->bmax.x = v.x;
+               if(v.y > bb->bmax.y) bb->bmax.y = v.y;
+               if(v.z > bb->bmax.z) bb->bmax.z = v.z;
+       }
+}
+
+int g3dimpl_mtl_init(struct goat3d_material *mtl)
+{
+       memset(mtl, 0, sizeof *mtl);
+       if(!(mtl->attrib = dynarr_alloc(0, sizeof *mtl->attrib))) {
+               return -1;
+       }
+       return 0;
+}
+
+void g3dimpl_mtl_destroy(struct goat3d_material *mtl)
+{
+       int i, num = dynarr_size(mtl->attrib);
+       for(i=0; i<num; i++) {
+               free(mtl->attrib[i].name);
+               free(mtl->attrib[i].map);
+       }
+       dynarr_free(mtl->attrib);
+}
+
+struct material_attrib *g3dimpl_mtl_findattr(struct goat3d_material *mtl, const char *name)
+{
+       int i, num = dynarr_size(mtl->attrib);
+
+       for(i=0; i<num; i++) {
+               if(strcmp(mtl->attrib[i].name, name) == 0) {
+                       return mtl->attrib + i;
+               }
+       }
+       return 0;
+}
+
+struct material_attrib *g3dimpl_mtl_getattr(struct goat3d_material *mtl, const char *name)
+{
+       int idx, len;
+       char *tmpname;
+       struct material_attrib *tmpattr, *ma;
+
+       if((ma = g3dimpl_mtl_findattr(mtl, name))) {
+               return ma;
+       }
+
+       len = strlen(name);
+       if(!(tmpname = malloc(len + 1))) {
+               return 0;
+       }
+       memcpy(tmpname, name, len + 1);
+
+       idx = dynarr_size(mtl->attrib);
+       if(!(tmpattr = dynarr_push(mtl->attrib, 0))) {
+               free(tmpname);
+               return 0;
+       }
+       mtl->attrib = tmpattr;
+
+       ma = mtl->attrib + idx;
+       ma->name = tmpname;
+       ma->map = 0;
+       cgm_wcons(&ma->value, 1, 1, 1, 1);
+
+       return ma;
+}
+
+void g3dimpl_node_bounds(struct aabox *bb, struct goat3d_node *n)
+{
+       struct object *obj = n->obj;
+       struct goat3d_node *cn = n->child;
+       float xform[16];
+
+       goat3d_get_matrix(n, xform);
+
+       if(obj && obj->type == OBJTYPE_MESH) {
+               g3dimpl_mesh_bounds(bb, (struct goat3d_mesh*)obj, xform);
+       } else {
+               g3dimpl_aabox_init(bb);
+       }
+
+       while(cn) {
+               struct aabox cbox;
+               g3dimpl_node_bounds(&cbox, cn);
+               g3dimpl_aabox_union(bb, bb, &cbox);
+               cn = cn->next;
+       }
+}
diff --git a/libs/goat3d/src/g3dscn.h b/libs/goat3d/src/g3dscn.h
new file mode 100644 (file)
index 0000000..468f9d3
--- /dev/null
@@ -0,0 +1,198 @@
+/*
+goat3d - 3D scene, and animation file format library.
+Copyright (C) 2013-2023  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/>.
+*/
+#ifndef GOAT3D_SCENE_H_
+#define GOAT3D_SCENE_H_
+
+#include "cgmath/cgmath.h"
+#include "goat3d.h"
+#include "g3danm.h"
+#include "aabox.h"
+
+enum {
+       OBJTYPE_UNKNOWN,
+       OBJTYPE_MESH,
+       OBJTYPE_LIGHT,
+       OBJTYPE_CAMERA
+};
+
+enum {
+       LTYPE_POINT,
+       LTYPE_DIR,
+       LTYPE_SPOT
+};
+
+enum {
+       CAMTYPE_PRS,
+       CAMTYPE_TARGET
+};
+
+
+struct face {
+       int v[3];
+};
+
+typedef struct int4 {
+       int x, y, z, w;
+} int4;
+
+struct material_attrib {
+       char *name;
+       cgm_vec4 value;
+       char *map;
+};
+
+struct goat3d_material {
+       char *name;
+       int idx;
+       struct material_attrib *attrib; /* dynarr */
+};
+
+
+#define OBJECT_COMMON  \
+       int type; \
+       char *name; \
+       cgm_vec3 pos; \
+       cgm_quat rot; \
+       cgm_vec3 scale; \
+       void *next
+
+struct object {
+       OBJECT_COMMON;
+};
+
+struct goat3d_mesh {
+       OBJECT_COMMON;
+       struct goat3d_material *mtl;
+
+       /* dynamic arrays */
+       cgm_vec3 *vertices;
+       cgm_vec3 *normals;
+       cgm_vec3 *tangents;
+       cgm_vec2 *texcoords;
+       cgm_vec4 *skin_weights;
+       int4 *skin_matrices;
+       cgm_vec4 *colors;
+       struct face *faces;
+       struct goat3d_node **bones;
+};
+
+struct goat3d_light {
+       OBJECT_COMMON;
+       int ltype;
+       cgm_vec3 color;
+       cgm_vec3 attenuation;
+       float max_dist;
+       cgm_vec3 dir;                                   /* for LTYPE_DIR */
+       float inner_cone, outer_cone;   /* for LTYPE_SPOT */
+};
+
+struct goat3d_camera {
+       OBJECT_COMMON;
+       int camtype;
+       float fov;
+       float near_clip, far_clip;
+       cgm_vec3 target, up;
+};
+
+struct goat3d_node {
+       char *name;
+       enum goat3d_node_type type;
+       void *obj;
+       int child_count;
+
+       cgm_vec3 pivot;
+       /* local transformation */
+       cgm_vec3 pos, scale;
+       cgm_quat rot;
+       /* values from animation evaluation, take precedence over the above if
+        * has_anim is true
+        */
+       cgm_vec3 apos, ascale;
+       cgm_quat arot;
+       int has_anim;
+       /* matrix computed from the above*/
+       float matrix[16];
+       int matrix_valid;
+
+       struct goat3d_node *parent;
+       struct goat3d_node *child;
+       struct goat3d_node *next;
+};
+
+
+struct goat3d {
+       unsigned int flags;
+       char *search_path;
+
+       char *name;
+       cgm_vec3 ambient;
+
+       /* dynamic arrays */
+       struct goat3d_material **materials;
+       struct goat3d_mesh **meshes;
+       struct goat3d_light **lights;
+       struct goat3d_camera **cameras;
+       struct goat3d_node **nodes;
+       struct goat3d_anim **anims;
+
+       struct aabox bbox;
+       int bbox_valid;
+
+       /* namegen */
+       unsigned int namecnt[7];
+       char namebuf[64];
+};
+
+extern int goat3d_log_level;
+
+/* defined in goat3d.c, declared here to keep them out of the public API */
+int goat3d_init(struct goat3d *g);
+void goat3d_destroy(struct goat3d *g);
+
+void goat3d_clear(struct goat3d *g);
+
+/* defined in g3dscn.c */
+int g3dimpl_obj_init(struct object *o, int type);
+void g3dimpl_obj_destroy(struct object *o);
+
+void g3dimpl_mesh_bounds(struct aabox *bb, struct goat3d_mesh *m, float *xform);
+
+int g3dimpl_mtl_init(struct goat3d_material *mtl);
+void g3dimpl_mtl_destroy(struct goat3d_material *mtl);
+struct material_attrib *g3dimpl_mtl_findattr(struct goat3d_material *mtl, const char *name);
+struct material_attrib *g3dimpl_mtl_getattr(struct goat3d_material *mtl, const char *name);
+
+void g3dimpl_node_bounds(struct aabox *bb, struct goat3d_node *n);
+
+/*
+void io_fprintf(goat3d_io *io, const char *fmt, ...);
+void io_vfprintf(goat3d_io *io, const char *fmt, va_list ap);
+*/
+
+/* defined in read.c */
+int g3dimpl_scnload(struct goat3d *g, struct goat3d_io *io);
+int g3dimpl_anmload(struct goat3d *g, struct goat3d_io *io);
+
+/* defined in write.c */
+int g3dimpl_scnsave(const struct goat3d *g, struct goat3d_io *io);
+int g3dimpl_anmsave(const struct goat3d *g, struct goat3d_io *io);
+
+/* defined in extmesh.c */
+int g3dimpl_loadmesh(struct goat3d_mesh *mesh, const char *fname);
+
+#endif /* GOAT3D_SCENE_H_ */
diff --git a/libs/goat3d/src/goat3d.c b/libs/goat3d/src/goat3d.c
new file mode 100644 (file)
index 0000000..9250b3e
--- /dev/null
@@ -0,0 +1,1914 @@
+/*
+goat3d - 3D scene, and animation file format library.
+Copyright (C) 2013-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/>.
+*/
+#include <string.h>
+#include <errno.h>
+#include <ctype.h>
+#include "goat3d.h"
+#include "g3dscn.h"
+#include "log.h"
+#include "dynarr.h"
+
+static long read_file(void *buf, size_t bytes, void *uptr);
+static long write_file(const void *buf, size_t bytes, void *uptr);
+static long seek_file(long offs, int whence, void *uptr);
+static char *clean_filename(char *str);
+
+static const char *def_scn_name = "unnamed";
+
+#define SETNAME(dest, str)     \
+       do { \
+               char *tmpname; \
+               int len = strlen(str); \
+               if(!(tmpname = malloc(len + 1))) { \
+                       return -1; \
+               } \
+               memcpy(tmpname, str, len + 1); \
+               free(dest); \
+               dest = tmpname; \
+               return 0; \
+       } while(0)
+
+
+GOAT3DAPI struct goat3d *goat3d_create(void)
+{
+       struct goat3d *g;
+
+       if(!(g = malloc(sizeof *g))) {
+               return 0;
+       }
+       if(goat3d_init(g) == -1) {
+               free(g);
+               return 0;
+       }
+       return g;
+}
+
+GOAT3DAPI void goat3d_free(struct goat3d *g)
+{
+       goat3d_destroy(g);
+       free(g);
+}
+
+int goat3d_init(struct goat3d *g)
+{
+       memset(g, 0, sizeof *g);
+
+       cgm_vcons(&g->ambient, 0.05, 0.05, 0.05);
+
+       if(!(g->materials = dynarr_alloc(0, sizeof *g->materials))) goto err;
+       if(!(g->meshes = dynarr_alloc(0, sizeof *g->meshes))) goto err;
+       if(!(g->lights = dynarr_alloc(0, sizeof *g->lights))) goto err;
+       if(!(g->cameras = dynarr_alloc(0, sizeof *g->cameras))) goto err;
+       if(!(g->nodes = dynarr_alloc(0, sizeof *g->nodes))) goto err;
+       if(!(g->anims = dynarr_alloc(0, sizeof *g->anims))) goto err;
+
+       return 0;
+
+err:
+       goat3d_destroy(g);
+       return -1;
+}
+
+void goat3d_destroy(struct goat3d *g)
+{
+       goat3d_clear(g);
+
+       dynarr_free(g->materials);
+       dynarr_free(g->meshes);
+       dynarr_free(g->lights);
+       dynarr_free(g->cameras);
+       dynarr_free(g->nodes);
+       dynarr_free(g->anims);
+}
+
+void goat3d_clear(struct goat3d *g)
+{
+       int i, num;
+
+       num = dynarr_size(g->materials);
+       for(i=0; i<num; i++) {
+               g3dimpl_mtl_destroy(g->materials[i]);
+               free(g->materials[i]);
+       }
+       DYNARR_CLEAR(g->materials);
+
+       num = dynarr_size(g->meshes);
+       for(i=0; i<num; i++) {
+               g3dimpl_obj_destroy((struct object*)g->meshes[i]);
+               free(g->meshes[i]);
+       }
+       DYNARR_CLEAR(g->meshes);
+
+       num = dynarr_size(g->lights);
+       for(i=0; i<num; i++) {
+               g3dimpl_obj_destroy((struct object*)g->lights[i]);
+               free(g->lights[i]);
+       }
+       DYNARR_CLEAR(g->lights);
+
+       num = dynarr_size(g->cameras);
+       for(i=0; i<num; i++) {
+               g3dimpl_obj_destroy((struct object*)g->cameras[i]);
+               free(g->cameras[i]);
+       }
+       DYNARR_CLEAR(g->cameras);
+
+       num = dynarr_size(g->nodes);
+       for(i=0; i<num; i++) {
+               goat3d_destroy_node(g->nodes[i]);
+       }
+       DYNARR_CLEAR(g->nodes);
+
+       num = dynarr_size(g->anims);
+       for(i=0; i<num; i++) {
+               g3dimpl_anim_destroy(g->anims[i]);
+       }
+
+       g->name = 0;
+       g->bbox_valid = 0;
+}
+
+GOAT3DAPI void goat3d_setopt(struct goat3d *g, enum goat3d_option opt, int val)
+{
+       if(val) {
+               g->flags |= (1 << (int)opt);
+       } else {
+               g->flags &= ~(1 << (int)opt);
+       }
+}
+
+GOAT3DAPI int goat3d_getopt(const struct goat3d *g, enum goat3d_option opt)
+{
+       return (g->flags >> (int)opt) & 1;
+}
+
+GOAT3DAPI int goat3d_load(struct goat3d *g, const char *fname)
+{
+       int len, res;
+       char *slash;
+       FILE *fp = fopen(fname, "rb");
+       if(!fp) {
+               goat3d_logmsg(LOG_ERROR, "failed to open file \"%s\" for reading: %s\n", fname, strerror(errno));
+               return -1;
+       }
+
+       /* if the filename contained any directory components, keep the prefix
+        * to use it as a search path for external mesh file loading
+        */
+       len = strlen(fname);
+       if(!(g->search_path = malloc(len + 1))) {
+               fclose(fp);
+               return -1;
+       }
+       memcpy(g->search_path, fname, len + 1);
+
+       if((slash = strrchr(g->search_path, '/'))) {
+               *slash = 0;
+       } else {
+               if((slash = strrchr(g->search_path, '\\'))) {
+                       *slash = 0;
+               } else {
+                       free(g->search_path);
+                       g->search_path = 0;
+               }
+       }
+
+       if((res = goat3d_load_file(g, fp)) == 0) {
+               const char *name;
+               if((name = goat3d_get_name(g)) == def_scn_name) {
+                       goat3d_set_name(g, slash ? slash + 1 : fname);
+               }
+       }
+       fclose(fp);
+       return res;
+}
+
+GOAT3DAPI int goat3d_save(const struct goat3d *g, const char *fname)
+{
+       int res;
+       FILE *fp = fopen(fname, "wb");
+       if(!fp) {
+               goat3d_logmsg(LOG_ERROR, "failed to open file \"%s\" for writing: %s\n", fname, strerror(errno));
+               return -1;
+       }
+
+       res = goat3d_save_file(g, fp);
+       fclose(fp);
+       return res;
+}
+
+GOAT3DAPI int goat3d_load_file(struct goat3d *g, FILE *fp)
+{
+       struct goat3d_io io;
+       io.cls = fp;
+       io.read = read_file;
+       io.write = write_file;
+       io.seek = seek_file;
+
+       return goat3d_load_io(g, &io);
+}
+
+GOAT3DAPI int goat3d_save_file(const struct goat3d *g, FILE *fp)
+{
+       struct goat3d_io io;
+       io.cls = fp;
+       io.read = read_file;
+       io.write = write_file;
+       io.seek = seek_file;
+
+       return goat3d_save_io(g, &io);
+}
+
+GOAT3DAPI int goat3d_load_io(struct goat3d *g, struct goat3d_io *io)
+{
+       return g3dimpl_scnload(g, io);
+}
+
+GOAT3DAPI int goat3d_save_io(const struct goat3d *g, struct goat3d_io *io)
+{
+       if(goat3d_getopt(g, GOAT3D_OPT_SAVEXML)) {
+               goat3d_logmsg(LOG_ERROR, "saving in the original xml format is no longer supported\n");
+               return -1;
+       } else if(goat3d_getopt(g, GOAT3D_OPT_SAVETEXT)) {
+               /* TODO set treestore output format as text */
+       }
+       return g3dimpl_scnsave(g, io);
+}
+
+/* save/load animations */
+GOAT3DAPI int goat3d_load_anim(struct goat3d *g, const char *fname)
+{
+       int res;
+       FILE *fp;
+
+       if(!(fp = fopen(fname, "rb"))) {
+               return -1;
+       }
+
+       res = goat3d_load_anim_file(g, fp);
+       fclose(fp);
+       return res;
+}
+
+GOAT3DAPI int goat3d_save_anim(const struct goat3d *g, const char *fname)
+{
+       int res;
+       FILE *fp;
+
+       if(!(fp = fopen(fname, "wb"))) {
+               return -1;
+       }
+
+       res = goat3d_save_anim_file(g, fp);
+       fclose(fp);
+       return res;
+}
+
+GOAT3DAPI int goat3d_load_anim_file(struct goat3d *g, FILE *fp)
+{
+       struct goat3d_io io;
+       io.cls = fp;
+       io.read = read_file;
+       io.write = write_file;
+       io.seek = seek_file;
+
+       return goat3d_load_anim_io(g, &io);
+}
+
+GOAT3DAPI int goat3d_save_anim_file(const struct goat3d *g, FILE *fp)
+{
+       struct goat3d_io io;
+       io.cls = fp;
+       io.read = read_file;
+       io.write = write_file;
+       io.seek = seek_file;
+
+       return goat3d_save_anim_io(g, &io);
+}
+
+GOAT3DAPI int goat3d_load_anim_io(struct goat3d *g, struct goat3d_io *io)
+{
+       return g3dimpl_anmload(g, io);
+}
+
+GOAT3DAPI int goat3d_save_anim_io(const struct goat3d *g, struct goat3d_io *io)
+{
+       if(goat3d_getopt(g, GOAT3D_OPT_SAVEXML)) {
+               goat3d_logmsg(LOG_ERROR, "saving in the original xml format is no longer supported\n");
+               return -1;
+       } else if(goat3d_getopt(g, GOAT3D_OPT_SAVETEXT)) {
+               /* TODO set treestore save format as text */
+       }
+       return g3dimpl_anmsave(g, io);
+}
+
+
+GOAT3DAPI int goat3d_set_name(struct goat3d *g, const char *name)
+{
+       int len = strlen(name);
+
+       free(g->name);
+       if(!(g->name = malloc(len + 1))) {
+               return -1;
+       }
+       memcpy(g->name, name, len + 1);
+       return 0;
+}
+
+GOAT3DAPI const char *goat3d_get_name(const struct goat3d *g)
+{
+       return g->name ? g->name : def_scn_name;
+}
+
+GOAT3DAPI void goat3d_set_ambient(struct goat3d *g, const float *amb)
+{
+       cgm_vcons(&g->ambient, amb[0], amb[1], amb[2]);
+}
+
+GOAT3DAPI void goat3d_set_ambient3f(struct goat3d *g, float ar, float ag, float ab)
+{
+       cgm_vcons(&g->ambient, ar, ag, ab);
+}
+
+GOAT3DAPI const float *goat3d_get_ambient(const struct goat3d *g)
+{
+       return &g->ambient.x;
+}
+
+GOAT3DAPI int goat3d_get_bounds(const struct goat3d *g, float *bmin, float *bmax)
+{
+       int i, num_nodes, num_meshes;
+       struct aabox bbox;
+
+       if(!g->bbox_valid) {
+               g3dimpl_aabox_init((struct aabox*)&g->bbox);
+
+               if(dynarr_empty(g->nodes)) {
+use_mesh_bounds:
+                       num_meshes = dynarr_size(g->meshes);
+                       for(i=0; i<num_meshes; i++) {
+                               g3dimpl_mesh_bounds(&bbox, g->meshes[i], 0);
+                               g3dimpl_aabox_union((struct aabox*)&g->bbox, &g->bbox, &bbox);
+                       }
+               } else {
+                       num_nodes = dynarr_size(g->nodes);
+                       for(i=0; i<num_nodes; i++) {
+                               if(g->nodes[i]->parent) {
+                                       continue;
+                               }
+                               g3dimpl_node_bounds(&bbox, g->nodes[i]);
+                               g3dimpl_aabox_union((struct aabox*)&g->bbox, &g->bbox, &bbox);
+                       }
+
+                       /* in case the nodes are junk */
+                       if(g->bbox.bmin.x > g->bbox.bmax.x) {
+                               goto use_mesh_bounds;
+                       }
+               }
+               ((struct goat3d*)g)->bbox_valid = 1;
+       }
+
+       if(g->bbox.bmin.x > g->bbox.bmax.x) {
+               return -1;
+       }
+
+       bmin[0] = g->bbox.bmin.x;
+       bmin[1] = g->bbox.bmin.y;
+       bmin[2] = g->bbox.bmin.z;
+       bmax[0] = g->bbox.bmax.x;
+       bmax[1] = g->bbox.bmax.y;
+       bmax[2] = g->bbox.bmax.z;
+       return 0;
+}
+
+// ---- materials ----
+GOAT3DAPI int goat3d_add_mtl(struct goat3d *g, struct goat3d_material *mtl)
+{
+       struct goat3d_material **newarr;
+       mtl->idx = dynarr_size(g->materials);
+       if(!(newarr = dynarr_push(g->materials, &mtl))) {
+               return -1;
+       }
+       g->materials = newarr;
+       return 0;
+}
+
+GOAT3DAPI int goat3d_get_mtl_count(struct goat3d *g)
+{
+       return dynarr_size(g->materials);
+}
+
+GOAT3DAPI struct goat3d_material *goat3d_get_mtl(struct goat3d *g, int idx)
+{
+       return g->materials[idx];
+}
+
+GOAT3DAPI struct goat3d_material *goat3d_get_mtl_by_name(struct goat3d *g, const char *name)
+{
+       int i, num = dynarr_size(g->materials);
+       for(i=0; i<num; i++) {
+               if(strcmp(g->materials[i]->name, name) == 0) {
+                       return g->materials[i];
+               }
+       }
+       return 0;
+}
+
+GOAT3DAPI struct goat3d_material *goat3d_create_mtl(void)
+{
+       struct goat3d_material *mtl;
+       if(!(mtl = malloc(sizeof *mtl))) {
+               return 0;
+       }
+       g3dimpl_mtl_init(mtl);
+       return mtl;
+}
+
+GOAT3DAPI void goat3d_destroy_mtl(struct goat3d_material *mtl)
+{
+       g3dimpl_mtl_destroy(mtl);
+       free(mtl);
+}
+
+GOAT3DAPI int goat3d_set_mtl_name(struct goat3d_material *mtl, const char *name)
+{
+       SETNAME(mtl->name, name);
+}
+
+GOAT3DAPI const char *goat3d_get_mtl_name(const struct goat3d_material *mtl)
+{
+       return mtl->name;
+}
+
+GOAT3DAPI int goat3d_set_mtl_attrib(struct goat3d_material *mtl, const char *attrib, const float *val)
+{
+       struct material_attrib *ma = g3dimpl_mtl_getattr(mtl, attrib);
+       if(!ma) return -1;
+       cgm_wcons(&ma->value, val[0], val[1], val[2], val[3]);
+       return 0;
+}
+
+GOAT3DAPI int goat3d_set_mtl_attrib1f(struct goat3d_material *mtl, const char *attrib, float val)
+{
+       return goat3d_set_mtl_attrib4f(mtl, attrib, val, 0, 0, 1);
+}
+
+GOAT3DAPI int goat3d_set_mtl_attrib3f(struct goat3d_material *mtl, const char *attrib, float r, float g, float b)
+{
+       return goat3d_set_mtl_attrib4f(mtl, attrib, r, g, b, 1);
+}
+
+GOAT3DAPI int goat3d_set_mtl_attrib4f(struct goat3d_material *mtl, const char *attrib, float r, float g, float b, float a)
+{
+       struct material_attrib *ma = g3dimpl_mtl_getattr(mtl, attrib);
+       if(!ma) return -1;
+       cgm_wcons(&ma->value, r, g, b, a);
+       return 0;
+}
+
+GOAT3DAPI const float *goat3d_get_mtl_attrib(struct goat3d_material *mtl, const char *attrib)
+{
+       struct material_attrib *ma = g3dimpl_mtl_findattr(mtl, attrib);
+       return ma ? &ma->value.x : 0;
+}
+
+GOAT3DAPI int goat3d_set_mtl_attrib_map(struct goat3d_material *mtl, const char *attrib, const char *mapname)
+{
+       int len;
+       char *tmp;
+       struct material_attrib *ma;
+
+       len = strlen(mapname);
+       if(!(tmp = malloc(len + 1))) {
+               return -1;
+       }
+       memcpy(tmp, mapname, len + 1);
+
+       if(!(ma = g3dimpl_mtl_getattr(mtl, attrib))) {
+               free(tmp);
+               return -1;
+       }
+       free(ma->map);
+       ma->map = tmp;
+       tmp = clean_filename(ma->map);
+       if(tmp != ma->map) {
+               memmove(ma->map, tmp, len - (tmp - ma->map) + 1);
+       }
+       return 0;
+}
+
+GOAT3DAPI const char *goat3d_get_mtl_attrib_map(struct goat3d_material *mtl, const char *attrib)
+{
+       struct material_attrib *ma = g3dimpl_mtl_findattr(mtl, attrib);
+       return ma->map;
+}
+
+GOAT3DAPI const char *goat3d_get_mtl_attrib_name(struct goat3d_material *mtl, int idx)
+{
+       return mtl->attrib[idx].name;
+}
+
+GOAT3DAPI int goat3d_get_mtl_attrib_count(struct goat3d_material *mtl)
+{
+       return dynarr_size(mtl->attrib);
+}
+
+// ---- meshes ----
+GOAT3DAPI int goat3d_add_mesh(struct goat3d *g, struct goat3d_mesh *mesh)
+{
+       struct goat3d_mesh **arr;
+       if(!(arr = dynarr_push(g->meshes, &mesh))) {
+               return -1;
+       }
+       g->meshes = arr;
+       return 0;
+}
+
+GOAT3DAPI int goat3d_get_mesh_count(struct goat3d *g)
+{
+       return dynarr_size(g->meshes);
+}
+
+GOAT3DAPI struct goat3d_mesh *goat3d_get_mesh(struct goat3d *g, int idx)
+{
+       return g->meshes[idx];
+}
+
+GOAT3DAPI struct goat3d_mesh *goat3d_get_mesh_by_name(struct goat3d *g, const char *name)
+{
+       int i, num = dynarr_size(g->meshes);
+       for(i=0; i<num; i++) {
+               if(strcmp(g->meshes[i]->name, name) == 0) {
+                       return g->meshes[i];
+               }
+       }
+       return 0;
+}
+
+GOAT3DAPI struct goat3d_mesh *goat3d_create_mesh(void)
+{
+       struct goat3d_mesh *m;
+
+       if(!(m = malloc(sizeof *m))) {
+               return 0;
+       }
+       if(g3dimpl_obj_init((struct object*)m, OBJTYPE_MESH) == -1) {
+               free(m);
+               return 0;
+       }
+       return m;
+}
+
+GOAT3DAPI void goat3d_destroy_mesh(struct goat3d_mesh *mesh)
+{
+       g3dimpl_obj_destroy((struct object*)mesh);
+       free(mesh);
+}
+
+GOAT3DAPI int goat3d_set_mesh_name(struct goat3d_mesh *mesh, const char *name)
+{
+       SETNAME(mesh->name, name);
+}
+
+GOAT3DAPI const char *goat3d_get_mesh_name(const struct goat3d_mesh *mesh)
+{
+       return mesh->name;
+}
+
+GOAT3DAPI void goat3d_set_mesh_mtl(struct goat3d_mesh *mesh, struct goat3d_material *mtl)
+{
+       mesh->mtl = mtl;
+}
+
+GOAT3DAPI struct goat3d_material *goat3d_get_mesh_mtl(struct goat3d_mesh *mesh)
+{
+       return mesh->mtl;
+}
+
+GOAT3DAPI int goat3d_get_mesh_vertex_count(struct goat3d_mesh *mesh)
+{
+       return dynarr_size(mesh->vertices);
+}
+
+GOAT3DAPI int goat3d_get_mesh_attrib_count(struct goat3d_mesh *mesh, enum goat3d_mesh_attrib attrib)
+{
+       switch(attrib) {
+       case GOAT3D_MESH_ATTR_VERTEX:
+               return dynarr_size(mesh->vertices);
+       case GOAT3D_MESH_ATTR_NORMAL:
+               return dynarr_size(mesh->normals);
+       case GOAT3D_MESH_ATTR_TANGENT:
+               return dynarr_size(mesh->tangents);
+       case GOAT3D_MESH_ATTR_TEXCOORD:
+               return dynarr_size(mesh->texcoords);
+       case GOAT3D_MESH_ATTR_SKIN_WEIGHT:
+               return dynarr_size(mesh->skin_weights);
+       case GOAT3D_MESH_ATTR_SKIN_MATRIX:
+               return dynarr_size(mesh->skin_matrices);
+       case GOAT3D_MESH_ATTR_COLOR:
+               return dynarr_size(mesh->colors);
+       default:
+               break;
+       }
+       return 0;
+}
+
+GOAT3DAPI int goat3d_get_mesh_face_count(struct goat3d_mesh *mesh)
+{
+       return dynarr_size(mesh->faces);
+}
+
+#define SET_VERTEX_DATA(arr, p, n) \
+       do { \
+               void *tmp = dynarr_resize(arr, n); \
+               if(!tmp) { \
+                       goat3d_logmsg(LOG_ERROR, "failed to resize vertex array (%d)\n", n); \
+                       return -1; \
+               } \
+               arr = tmp; \
+               memcpy(arr, p, n * sizeof *arr); \
+       } while(0)
+
+GOAT3DAPI int goat3d_set_mesh_attribs(struct goat3d_mesh *mesh, enum goat3d_mesh_attrib attrib, const void *data, int vnum)
+{
+       if(attrib == GOAT3D_MESH_ATTR_VERTEX) {
+               SET_VERTEX_DATA(mesh->vertices, data, vnum);
+               return 0;
+       }
+
+       if(vnum != dynarr_size(mesh->vertices)) {
+               goat3d_logmsg(LOG_ERROR, "trying to set mesh attrib data with number of elements different than the vertex array\n");
+               return -1;
+       }
+
+       switch(attrib) {
+       case GOAT3D_MESH_ATTR_NORMAL:
+               SET_VERTEX_DATA(mesh->normals, data, vnum);
+               break;
+       case GOAT3D_MESH_ATTR_TANGENT:
+               SET_VERTEX_DATA(mesh->tangents, data, vnum);
+               break;
+       case GOAT3D_MESH_ATTR_TEXCOORD:
+               SET_VERTEX_DATA(mesh->texcoords, data, vnum);
+               break;
+       case GOAT3D_MESH_ATTR_SKIN_WEIGHT:
+               SET_VERTEX_DATA(mesh->skin_weights, data, vnum);
+               break;
+       case GOAT3D_MESH_ATTR_SKIN_MATRIX:
+               SET_VERTEX_DATA(mesh->skin_matrices, data, vnum);
+               break;
+       case GOAT3D_MESH_ATTR_COLOR:
+               SET_VERTEX_DATA(mesh->colors, data, vnum);
+       default:
+               goat3d_logmsg(LOG_ERROR, "trying to set unknown vertex attrib: %d\n", attrib);
+               return -1;
+       }
+       return 0;
+}
+
+GOAT3DAPI int goat3d_add_mesh_attrib1f(struct goat3d_mesh *mesh, enum goat3d_mesh_attrib attrib,
+               float val)
+{
+       return goat3d_add_mesh_attrib4f(mesh, attrib, val, 0, 0, 1);
+}
+
+GOAT3DAPI int goat3d_add_mesh_attrib2f(struct goat3d_mesh *mesh, enum goat3d_mesh_attrib attrib,
+               float x, float y)
+{
+       return goat3d_add_mesh_attrib4f(mesh, attrib, x, y, 0, 1);
+}
+
+GOAT3DAPI int goat3d_add_mesh_attrib3f(struct goat3d_mesh *mesh, enum goat3d_mesh_attrib attrib,
+               float x, float y, float z)
+{
+       return goat3d_add_mesh_attrib4f(mesh, attrib, x, y, z, 1);
+}
+
+GOAT3DAPI int goat3d_add_mesh_attrib4f(struct goat3d_mesh *mesh, enum goat3d_mesh_attrib attrib,
+               float x, float y, float z, float w)
+{
+       float vec[4];
+       int4 intvec;
+       void *tmp;
+
+       switch(attrib) {
+       case GOAT3D_MESH_ATTR_VERTEX:
+               cgm_vcons((cgm_vec3*)vec, x, y, z);
+               if(!(tmp = dynarr_push(mesh->vertices, vec))) {
+                       goto err;
+               }
+               mesh->vertices = tmp;
+               break;
+
+       case GOAT3D_MESH_ATTR_NORMAL:
+               cgm_vcons((cgm_vec3*)vec, x, y, z);
+               if(!(tmp = dynarr_push(mesh->normals, vec))) {
+                       goto err;
+               }
+               mesh->normals = tmp;
+               break;
+
+       case GOAT3D_MESH_ATTR_TANGENT:
+               cgm_vcons((cgm_vec3*)vec, x, y, z);
+               if(!(tmp = dynarr_push(mesh->tangents, vec))) {
+                       goto err;
+               }
+               mesh->tangents = tmp;
+               break;
+
+       case GOAT3D_MESH_ATTR_TEXCOORD:
+               cgm_vcons((cgm_vec3*)vec, x, y, 0);
+               if(!(tmp = dynarr_push(mesh->texcoords, vec))) {
+                       goto err;
+               }
+               mesh->texcoords = tmp;
+               break;
+
+       case GOAT3D_MESH_ATTR_SKIN_WEIGHT:
+               cgm_wcons((cgm_vec4*)vec, x, y, z, w);
+               if(!(tmp = dynarr_push(mesh->skin_weights, vec))) {
+                       goto err;
+               }
+               mesh->skin_weights = tmp;
+               break;
+
+       case GOAT3D_MESH_ATTR_SKIN_MATRIX:
+               intvec.x = x;
+               intvec.y = y;
+               intvec.z = z;
+               intvec.w = w;
+               if(!(tmp = dynarr_push(mesh->skin_matrices, &intvec))) {
+                       goto err;
+               }
+               mesh->skin_matrices = tmp;
+               break;
+
+       case GOAT3D_MESH_ATTR_COLOR:
+               cgm_wcons((cgm_vec4*)vec, x, y, z, w);
+               if(!(tmp = dynarr_push(mesh->colors, vec))) {
+                       goto err;
+               }
+               mesh->colors = tmp;
+
+       default:
+               goat3d_logmsg(LOG_ERROR, "trying to add unknown vertex attrib: %d\n", attrib);
+               return -1;
+       }
+       return 0;
+
+err:
+       goat3d_logmsg(LOG_ERROR, "failed to push vertex attrib\n");
+       return -1;
+}
+
+GOAT3DAPI void *goat3d_get_mesh_attribs(struct goat3d_mesh *mesh, enum goat3d_mesh_attrib attrib)
+{
+       return goat3d_get_mesh_attrib(mesh, attrib, 0);
+}
+
+GOAT3DAPI void *goat3d_get_mesh_attrib(struct goat3d_mesh *mesh, enum goat3d_mesh_attrib attrib, int idx)
+{
+       switch(attrib) {
+       case GOAT3D_MESH_ATTR_VERTEX:
+               return dynarr_empty(mesh->vertices) ? 0 : mesh->vertices + idx;
+       case GOAT3D_MESH_ATTR_NORMAL:
+               return dynarr_empty(mesh->normals) ? 0 : mesh->normals + idx;
+       case GOAT3D_MESH_ATTR_TANGENT:
+               return dynarr_empty(mesh->tangents) ? 0 : mesh->tangents + idx;
+       case GOAT3D_MESH_ATTR_TEXCOORD:
+               return dynarr_empty(mesh->texcoords) ? 0 : mesh->texcoords + idx;
+       case GOAT3D_MESH_ATTR_SKIN_WEIGHT:
+               return dynarr_empty(mesh->skin_weights) ? 0 : mesh->skin_weights + idx;
+       case GOAT3D_MESH_ATTR_SKIN_MATRIX:
+               return dynarr_empty(mesh->skin_matrices) ? 0 : mesh->skin_matrices + idx;
+       case GOAT3D_MESH_ATTR_COLOR:
+               return dynarr_empty(mesh->colors) ? 0 : mesh->colors + idx;
+       default:
+               break;
+       }
+       return 0;
+}
+
+
+GOAT3DAPI int goat3d_set_mesh_faces(struct goat3d_mesh *mesh, const int *data, int num)
+{
+       void *tmp;
+       if(!(tmp = dynarr_resize(mesh->faces, num))) {
+               goat3d_logmsg(LOG_ERROR, "failed to resize face array (%d)\n", num);
+               return -1;
+       }
+       mesh->faces = tmp;
+       memcpy(mesh->faces, data, num * sizeof *mesh->faces);
+       return 0;
+}
+
+GOAT3DAPI int goat3d_add_mesh_face(struct goat3d_mesh *mesh, int a, int b, int c)
+{
+       void *tmp;
+       struct face face;
+
+       face.v[0] = a;
+       face.v[1] = b;
+       face.v[2] = c;
+
+       if(!(tmp = dynarr_push(mesh->faces, &face))) {
+               goat3d_logmsg(LOG_ERROR, "failed to add face\n");
+               return -1;
+       }
+       mesh->faces = tmp;
+       return 0;
+}
+
+GOAT3DAPI int *goat3d_get_mesh_faces(struct goat3d_mesh *mesh)
+{
+       return goat3d_get_mesh_face(mesh, 0);
+}
+
+GOAT3DAPI int *goat3d_get_mesh_face(struct goat3d_mesh *mesh, int idx)
+{
+       return dynarr_empty(mesh->faces) ? 0 : mesh->faces[idx].v;
+}
+
+// immedate mode state
+static enum goat3d_im_primitive im_prim;
+static struct goat3d_mesh *im_mesh;
+static cgm_vec3 im_norm, im_tang;
+static cgm_vec2 im_texcoord;
+static cgm_vec4 im_skinw, im_color = {1, 1, 1, 1};
+static int4 im_skinmat;
+static int im_use[NUM_GOAT3D_MESH_ATTRIBS];
+
+
+GOAT3DAPI void goat3d_begin(struct goat3d_mesh *mesh, enum goat3d_im_primitive prim)
+{
+       DYNARR_CLEAR(mesh->vertices);
+       DYNARR_CLEAR(mesh->normals);
+       DYNARR_CLEAR(mesh->tangents);
+       DYNARR_CLEAR(mesh->texcoords);
+       DYNARR_CLEAR(mesh->skin_weights);
+       DYNARR_CLEAR(mesh->skin_matrices);
+       DYNARR_CLEAR(mesh->colors);
+       DYNARR_CLEAR(mesh->faces);
+
+       im_mesh = mesh;
+       memset(im_use, 0, sizeof im_use);
+
+       im_prim = prim;
+}
+
+GOAT3DAPI void goat3d_end(void)
+{
+       int i, vidx, num_faces, num_quads;
+       void *tmp;
+
+       switch(im_prim) {
+       case GOAT3D_TRIANGLES:
+               {
+                       num_faces = dynarr_size(im_mesh->vertices) / 3;
+                       if(!(tmp = dynarr_resize(im_mesh->faces, num_faces))) {
+                               return;
+                       }
+                       im_mesh->faces = tmp;
+
+                       vidx = 0;
+                       for(i=0; i<num_faces; i++) {
+                               im_mesh->faces[i].v[0] = vidx++;
+                               im_mesh->faces[i].v[1] = vidx++;
+                               im_mesh->faces[i].v[2] = vidx++;
+                       }
+               }
+               break;
+
+       case GOAT3D_QUADS:
+               {
+                       num_quads = dynarr_size(im_mesh->vertices) / 4;
+                       if(!(tmp = dynarr_resize(im_mesh->faces, num_quads * 2))) {
+                               return;
+                       }
+                       im_mesh->faces = tmp;
+
+                       vidx = 0;
+                       for(i=0; i<num_quads; i++) {
+                               im_mesh->faces[i * 2].v[0] = vidx;
+                               im_mesh->faces[i * 2].v[1] = vidx + 1;
+                               im_mesh->faces[i * 2].v[2] = vidx + 2;
+
+                               im_mesh->faces[i * 2 + 1].v[0] = vidx;
+                               im_mesh->faces[i * 2 + 1].v[1] = vidx + 2;
+                               im_mesh->faces[i * 2 + 1].v[2] = vidx + 3;
+
+                               vidx += 4;
+                       }
+               }
+               break;
+
+       default:
+               break;
+       }
+}
+
+GOAT3DAPI void goat3d_vertex3f(float x, float y, float z)
+{
+       void *tmp;
+       cgm_vec3 v;
+
+       cgm_vcons(&v, x, y, z);
+       if(!(tmp = dynarr_push(im_mesh->vertices, &v))) {
+               return;
+       }
+       im_mesh->vertices = tmp;
+
+       if(im_use[GOAT3D_MESH_ATTR_NORMAL]) {
+               if((tmp = dynarr_push(im_mesh->normals, &im_norm))) {
+                       im_mesh->normals = tmp;
+               }
+       }
+       if(im_use[GOAT3D_MESH_ATTR_TANGENT]) {
+               if((tmp = dynarr_push(im_mesh->tangents, &im_tang))) {
+                       im_mesh->tangents = tmp;
+               }
+       }
+       if(im_use[GOAT3D_MESH_ATTR_TEXCOORD]) {
+               if((tmp = dynarr_push(im_mesh->texcoords, &im_texcoord))) {
+                       im_mesh->texcoords = tmp;
+               }
+       }
+       if(im_use[GOAT3D_MESH_ATTR_SKIN_WEIGHT]) {
+               if((tmp = dynarr_push(im_mesh->skin_weights, &im_skinw))) {
+                       im_mesh->skin_weights = tmp;
+               }
+       }
+       if(im_use[GOAT3D_MESH_ATTR_SKIN_MATRIX]) {
+               if((tmp = dynarr_push(im_mesh->skin_matrices, &im_skinmat))) {
+                       im_mesh->skin_matrices = tmp;
+               }
+       }
+       if(im_use[GOAT3D_MESH_ATTR_COLOR]) {
+               if((tmp = dynarr_push(im_mesh->colors, &im_color))) {
+                       im_mesh->colors = tmp;
+               }
+       }
+}
+
+GOAT3DAPI void goat3d_normal3f(float x, float y, float z)
+{
+       cgm_vcons(&im_norm, x, y, z);
+       im_use[GOAT3D_MESH_ATTR_NORMAL] = 1;
+}
+
+GOAT3DAPI void goat3d_tangent3f(float x, float y, float z)
+{
+       cgm_vcons(&im_tang, x, y, z);
+       im_use[GOAT3D_MESH_ATTR_TANGENT] = 1;
+}
+
+GOAT3DAPI void goat3d_texcoord2f(float x, float y)
+{
+       im_texcoord.x = x;
+       im_texcoord.y = y;
+       im_use[GOAT3D_MESH_ATTR_TEXCOORD] = 1;
+}
+
+GOAT3DAPI void goat3d_skin_weight4f(float x, float y, float z, float w)
+{
+       cgm_wcons(&im_skinw, x, y, z, w);
+       im_use[GOAT3D_MESH_ATTR_SKIN_WEIGHT] = 1;
+}
+
+GOAT3DAPI void goat3d_skin_matrix4i(int x, int y, int z, int w)
+{
+       im_skinmat.x = x;
+       im_skinmat.y = y;
+       im_skinmat.z = z;
+       im_skinmat.w = w;
+       im_use[GOAT3D_MESH_ATTR_SKIN_MATRIX] = 1;
+}
+
+GOAT3DAPI void goat3d_color3f(float x, float y, float z)
+{
+       goat3d_color4f(x, y, z, 1.0f);
+}
+
+GOAT3DAPI void goat3d_color4f(float x, float y, float z, float w)
+{
+       cgm_wcons(&im_color, x, y, z, w);
+       im_use[GOAT3D_MESH_ATTR_COLOR] = 1;
+}
+
+GOAT3DAPI void goat3d_get_mesh_bounds(const struct goat3d_mesh *mesh, float *bmin, float *bmax)
+{
+       struct aabox box;
+
+       g3dimpl_mesh_bounds(&box, (struct goat3d_mesh*)mesh, 0);
+
+       bmin[0] = box.bmin.x;
+       bmin[1] = box.bmin.y;
+       bmin[2] = box.bmin.z;
+       bmax[0] = box.bmax.x;
+       bmax[1] = box.bmax.y;
+       bmax[2] = box.bmax.z;
+}
+
+/* lights */
+GOAT3DAPI int goat3d_add_light(struct goat3d *g, struct goat3d_light *lt)
+{
+       struct goat3d_light **arr;
+       if(!(arr = dynarr_push(g->lights, &lt))) {
+               return -1;
+       }
+       g->lights = arr;
+       return 0;
+}
+
+GOAT3DAPI int goat3d_get_light_count(struct goat3d *g)
+{
+       return dynarr_size(g->lights);
+}
+
+GOAT3DAPI struct goat3d_light *goat3d_get_light(struct goat3d *g, int idx)
+{
+       return g->lights[idx];
+}
+
+GOAT3DAPI struct goat3d_light *goat3d_get_light_by_name(struct goat3d *g, const char *name)
+{
+       int i, num = dynarr_size(g->lights);
+       for(i=0; i<num; i++) {
+               if(strcmp(g->lights[i]->name, name) == 0) {
+                       return g->lights[i];
+               }
+       }
+       return 0;
+}
+
+
+GOAT3DAPI struct goat3d_light *goat3d_create_light(void)
+{
+       struct goat3d_light *lt;
+
+       if(!(lt = malloc(sizeof *lt))) {
+               return 0;
+       }
+       if(g3dimpl_obj_init((struct object*)lt, OBJTYPE_LIGHT) == -1) {
+               free(lt);
+               return 0;
+       }
+       return lt;
+}
+
+GOAT3DAPI void goat3d_destroy_light(struct goat3d_light *lt)
+{
+       g3dimpl_obj_destroy((struct object*)lt);
+       free(lt);
+}
+
+GOAT3DAPI int goat3d_set_light_name(struct goat3d_light *lt, const char *name)
+{
+       SETNAME(lt->name, name);
+}
+
+GOAT3DAPI const char *goat3d_get_light_name(const struct goat3d_light *lt)
+{
+       return lt->name;
+}
+
+/* cameras */
+GOAT3DAPI int goat3d_add_camera(struct goat3d *g, struct goat3d_camera *cam)
+{
+       struct goat3d_camera **arr;
+       if(!(arr = dynarr_push(g->cameras, &cam))) {
+               return -1;
+       }
+       g->cameras = arr;
+       return 0;
+}
+
+GOAT3DAPI int goat3d_get_camera_count(struct goat3d *g)
+{
+       return dynarr_size(g->cameras);
+}
+
+GOAT3DAPI struct goat3d_camera *goat3d_get_camera(struct goat3d *g, int idx)
+{
+       return g->cameras[idx];
+}
+
+GOAT3DAPI struct goat3d_camera *goat3d_get_camera_by_name(struct goat3d *g, const char *name)
+{
+       int i, num = dynarr_size(g->cameras);
+       for(i=0; i<num; i++) {
+               if(strcmp(g->cameras[i]->name, name) == 0) {
+                       return g->cameras[i];
+               }
+       }
+       return 0;
+}
+
+GOAT3DAPI struct goat3d_camera *goat3d_create_camera(void)
+{
+       struct goat3d_camera *cam;
+
+       if(!(cam = malloc(sizeof *cam))) {
+               return 0;
+       }
+       if(g3dimpl_obj_init((struct object*)cam, OBJTYPE_CAMERA) == -1) {
+               free(cam);
+               return 0;
+       }
+       return cam;
+}
+
+GOAT3DAPI void goat3d_destroy_camera(struct goat3d_camera *cam)
+{
+       g3dimpl_obj_destroy((struct object*)cam);
+       free(cam);
+}
+
+GOAT3DAPI int goat3d_set_camera_name(struct goat3d_camera *cam, const char *name)
+{
+       SETNAME(cam->name, name);
+}
+
+GOAT3DAPI const char *goat3d_get_camera_name(const struct goat3d_camera *cam)
+{
+       return cam->name;
+}
+
+/* node */
+GOAT3DAPI int goat3d_add_node(struct goat3d *g, struct goat3d_node *node)
+{
+       struct goat3d_node **arr;
+       if(!(arr = dynarr_push(g->nodes, &node))) {
+               return -1;
+       }
+       g->nodes = arr;
+       return 0;
+}
+
+GOAT3DAPI int goat3d_get_node_count(struct goat3d *g)
+{
+       return dynarr_size(g->nodes);
+}
+
+GOAT3DAPI struct goat3d_node *goat3d_get_node(struct goat3d *g, int idx)
+{
+       return g->nodes[idx];
+}
+
+GOAT3DAPI struct goat3d_node *goat3d_get_node_by_name(struct goat3d *g, const char *name)
+{
+       int i, num = dynarr_size(g->nodes);
+       for(i=0; i<num; i++) {
+               if(strcmp(g->nodes[i]->name, name) == 0) {
+                       return g->nodes[i];
+               }
+       }
+       return 0;
+}
+
+GOAT3DAPI struct goat3d_node *goat3d_create_node(void)
+{
+       struct goat3d_node *node;
+
+       if(!(node = calloc(1, sizeof *node))) {
+               return 0;
+       }
+       node->type = GOAT3D_NODE_NULL;
+       node->obj = 0;
+       node->child_count = 0;
+
+       node->rot.w = node->arot.w = 1;
+       cgm_vcons(&node->scale, 1, 1, 1);
+       cgm_midentity(node->matrix);
+
+       return node;
+}
+
+GOAT3DAPI void goat3d_destroy_node(struct goat3d_node *node)
+{
+       if(!node) return;
+       free(node->name);
+       free(node);
+}
+
+GOAT3DAPI int goat3d_set_node_name(struct goat3d_node *node, const char *name)
+{
+       SETNAME(node->name, name);
+}
+
+GOAT3DAPI const char *goat3d_get_node_name(const struct goat3d_node *node)
+{
+       return node->name;
+}
+
+GOAT3DAPI void goat3d_set_node_object(struct goat3d_node *node, enum goat3d_node_type type, void *obj)
+{
+       node->obj = obj;
+       node->type = type;
+}
+
+GOAT3DAPI void *goat3d_get_node_object(const struct goat3d_node *node)
+{
+       return node->obj;
+}
+
+GOAT3DAPI enum goat3d_node_type goat3d_get_node_type(const struct goat3d_node *node)
+{
+       return node->type;
+}
+
+GOAT3DAPI void goat3d_add_node_child(struct goat3d_node *node, struct goat3d_node *child)
+{
+       child->next = node->child;
+       node->child = child;
+       child->parent = node;
+       node->child_count++;
+
+       child->matrix_valid = 0;
+}
+
+GOAT3DAPI int goat3d_get_node_child_count(const struct goat3d_node *node)
+{
+       return node->child_count;
+}
+
+GOAT3DAPI struct goat3d_node *goat3d_get_node_child(const struct goat3d_node *node, int idx)
+{
+       struct goat3d_node *c = node->child;
+       while(c && idx-- > 0) {
+               c = c->next;
+       }
+       return c;
+}
+
+GOAT3DAPI struct goat3d_node *goat3d_get_node_parent(const struct goat3d_node *node)
+{
+       return node->parent;
+}
+
+static void invalidate_subtree(struct goat3d_node *node)
+{
+       struct goat3d_node *c = node->child;
+
+       while(c) {
+               invalidate_subtree(c);
+               c = c->next;
+       }
+       node->matrix_valid = 0;
+}
+
+
+GOAT3DAPI void goat3d_set_node_position(struct goat3d_node *node, float x, float y, float z)
+{
+       cgm_vcons(&node->pos, x, y, z);
+       invalidate_subtree(node);
+}
+
+GOAT3DAPI void goat3d_set_node_rotation(struct goat3d_node *node, float qx, float qy, float qz, float qw)
+{
+       cgm_qcons(&node->rot, qx, qy, qz, qw);
+       invalidate_subtree(node);
+}
+
+GOAT3DAPI void goat3d_set_node_scaling(struct goat3d_node *node, float sx, float sy, float sz)
+{
+       cgm_vcons(&node->scale, sx, sy, sz);
+       invalidate_subtree(node);
+}
+
+GOAT3DAPI void goat3d_get_node_position(const struct goat3d_node *node, float *xptr, float *yptr, float *zptr)
+{
+       if(node->has_anim) {
+               *xptr = node->apos.x;
+               *yptr = node->apos.y;
+               *zptr = node->apos.z;
+       } else {
+               *xptr = node->pos.x;
+               *yptr = node->pos.y;
+               *zptr = node->pos.z;
+       }
+}
+
+GOAT3DAPI void goat3d_get_node_rotation(const struct goat3d_node *node, float *xptr, float *yptr, float *zptr, float *wptr)
+{
+       if(node->has_anim) {
+               *xptr = node->arot.x;
+               *yptr = node->arot.y;
+               *zptr = node->arot.z;
+               *wptr = node->arot.w;
+       } else {
+               *xptr = node->rot.x;
+               *yptr = node->rot.y;
+               *zptr = node->rot.z;
+               *wptr = node->rot.w;
+       }
+}
+
+GOAT3DAPI void goat3d_get_node_scaling(const struct goat3d_node *node, float *xptr, float *yptr, float *zptr)
+{
+       if(node->has_anim) {
+               *xptr = node->ascale.x;
+               *yptr = node->ascale.y;
+               *zptr = node->ascale.z;
+       } else {
+               *xptr = node->scale.x;
+               *yptr = node->scale.y;
+               *zptr = node->scale.z;
+       }
+}
+
+
+GOAT3DAPI void goat3d_set_node_pivot(struct goat3d_node *node, float px, float py, float pz)
+{
+       cgm_vcons(&node->pivot, px, py, pz);
+       invalidate_subtree(node);
+}
+
+GOAT3DAPI void goat3d_get_node_pivot(const struct goat3d_node *node, float *xptr, float *yptr, float *zptr)
+{
+       *xptr = node->pivot.x;
+       *yptr = node->pivot.y;
+       *zptr = node->pivot.z;
+}
+
+static void calc_node_matrix(const struct goat3d_node *node, float *mat)
+{
+       int i;
+       float rmat[16];
+       cgm_vec3 pos, scale;
+       cgm_quat rot;
+
+       if(node->has_anim) {
+               pos = node->apos;
+               rot = node->arot;
+               scale = node->ascale;
+       } else {
+               pos = node->pos;
+               rot = node->rot;
+               scale = node->scale;
+       }
+
+       cgm_mtranslation(mat, node->pivot.x, node->pivot.y, node->pivot.z);
+       cgm_mrotation_quat(rmat, &rot);
+
+       for(i=0; i<3; i++) {
+               mat[i] = rmat[i];
+               mat[4 + i] = rmat[4 + i];
+               mat[8 + i] = rmat[8 + i];
+       }
+
+       mat[0] *= scale.x; mat[4] *= scale.y; mat[8] *= scale.z; mat[12] += pos.x;
+       mat[1] *= scale.x; mat[5] *= scale.y; mat[9] *= scale.z; mat[13] += pos.y;
+       mat[2] *= scale.x; mat[6] *= scale.y; mat[10] *= scale.z; mat[14] += pos.z;
+
+       cgm_mpretranslate(mat, -node->pivot.x, -node->pivot.y, -node->pivot.z);
+
+       /* that's basically: pivot * rotation * translation * scaling * -pivot */
+}
+
+GOAT3DAPI void goat3d_get_node_matrix(const struct goat3d_node *node, float *matrix)
+{
+       if(!node->matrix_valid) {
+               calc_node_matrix(node, (float*)node->matrix);
+               ((struct goat3d_node*)node)->matrix_valid = 1;
+       }
+       memcpy(matrix, node->matrix, sizeof node->matrix);
+}
+
+GOAT3DAPI void goat3d_get_matrix(const struct goat3d_node *node, float *matrix)
+{
+       goat3d_get_node_matrix(node, matrix);
+       if(node->parent) {
+               cgm_mmul(matrix, node->parent->matrix);
+       }
+}
+
+GOAT3DAPI void goat3d_get_node_bounds(const struct goat3d_node *node, float *bmin, float *bmax)
+{
+       struct aabox box;
+       g3dimpl_node_bounds(&box, (struct goat3d_node*)node);
+
+       bmin[0] = box.bmin.x;
+       bmin[1] = box.bmin.y;
+       bmin[2] = box.bmin.z;
+       bmax[0] = box.bmax.x;
+       bmax[1] = box.bmax.y;
+       bmax[2] = box.bmax.z;
+}
+
+
+/* tracks */
+#define BASETYPE(type) ((int)(type) & 0xff)
+static const int key_val_sz[] = {1, 3, 4, 4};
+
+GOAT3DAPI struct goat3d_track *goat3d_create_track(void)
+{
+       int i;
+       struct goat3d_track *trk;
+
+       if(!(trk = calloc(1, sizeof *trk))) {
+               return 0;
+       }
+
+       for(i=0; i<4; i++) {
+               if(anm_init_track(trk->trk + i) == -1) {
+                       while(--i >= 0) {
+                               anm_destroy_track(trk->trk + i);
+                       }
+                       free(trk);
+                       return 0;
+               }
+       }
+
+       return trk;
+}
+
+GOAT3DAPI void goat3d_destroy_track(struct goat3d_track *trk)
+{
+       int i;
+
+       if(!trk) return;
+
+       free(trk->name);
+
+       for(i=0; i<4; i++) {
+               anm_destroy_track(trk->trk + i);
+       }
+}
+
+GOAT3DAPI int goat3d_set_track_name(struct goat3d_track *trk, const char *name)
+{
+       SETNAME(trk->name, name);
+}
+
+GOAT3DAPI const char *goat3d_get_track_name(const struct goat3d_track *trk)
+{
+       return trk->name;
+}
+
+GOAT3DAPI void goat3d_set_track_type(struct goat3d_track *trk, enum goat3d_track_type type)
+{
+       trk->type = type;
+
+       switch(BASETYPE(type)) {
+       case GOAT3D_TRACK_QUAT:
+       case GOAT3D_TRACK_VEC4:
+               anm_set_track_default(trk->trk + 3, 1);
+       case GOAT3D_TRACK_VEC3:
+               anm_set_track_default(trk->trk + 1, 0);
+               anm_set_track_default(trk->trk + 2, 0);
+       case GOAT3D_TRACK_VAL:
+               anm_set_track_default(trk->trk + 0, 0);
+       }
+}
+
+GOAT3DAPI enum goat3d_track_type goat3d_get_track_type(const struct goat3d_track *trk)
+{
+       return trk->type;
+}
+
+GOAT3DAPI void goat3d_set_track_node(struct goat3d_track *trk, struct goat3d_node *node)
+{
+       trk->node = node;
+}
+
+GOAT3DAPI struct goat3d_node *goat3d_get_track_node(const struct goat3d_track *trk)
+{
+       return trk->node;
+}
+
+GOAT3DAPI void goat3d_set_track_interp(struct goat3d_track *trk, enum goat3d_interp in)
+{
+       int i;
+       for(i=0; i<4; i++) {
+               anm_set_track_interpolator(trk->trk + i, in);
+       }
+}
+
+GOAT3DAPI enum goat3d_interp goat3d_get_track_interp(const struct goat3d_track *trk)
+{
+       return trk->trk[0].interp;
+}
+
+GOAT3DAPI void goat3d_set_track_extrap(struct goat3d_track *trk, enum goat3d_extrap ex)
+{
+       int i;
+       for(i=0; i<4; i++) {
+               anm_set_track_extrapolator(trk->trk + i, ex);
+       }
+}
+
+GOAT3DAPI enum goat3d_extrap goat3d_get_track_extrap(const struct goat3d_track *trk)
+{
+       return trk->trk[0].extrap;
+}
+
+GOAT3DAPI int goat3d_set_track_key(struct goat3d_track *trk, const struct goat3d_key *key)
+{
+       int i, num;
+       enum goat3d_track_type basetype;
+       long tm = ANM_MSEC2TM(key->tm);
+
+       basetype = BASETYPE(trk->type); /* e.g. ROT -> QUAT */
+       num = key_val_sz[basetype];
+
+       for(i=0; i<num; i++) {
+               if(anm_set_value(trk->trk + i, tm, key->val[i]) == -1) {
+                       return -1;
+               }
+       }
+       return 0;
+}
+
+GOAT3DAPI int goat3d_get_track_key(const struct goat3d_track *trk, int idx, struct goat3d_key *key)
+{
+       int i, num;
+       struct anm_keyframe *akey;
+       enum goat3d_track_type basetype;
+
+       basetype = BASETYPE(trk->type);
+       num = key_val_sz[basetype];
+
+       for(i=0; i<num; i++) {
+               if(!(akey = anm_get_keyframe(trk->trk + i, idx))) {
+                       return -1;
+               }
+               if(i == 0) {
+                       key->tm = ANM_TM2MSEC(akey->time);
+               }
+               key->val[i] = akey->val;
+       }
+       return 0;
+}
+
+GOAT3DAPI int goat3d_get_track_key_count(const struct goat3d_track *trk)
+{
+       return trk->trk[0].count;
+}
+
+GOAT3DAPI int goat3d_set_track_val(struct goat3d_track *trk, long msec, float val)
+{
+       struct goat3d_key key = {0};
+       enum goat3d_track_type basetype;
+
+       basetype = BASETYPE(trk->type);
+
+       if(basetype != GOAT3D_TRACK_VAL) {
+               goat3d_logmsg(LOG_WARNING, "goat3d_set_track_val called on %s track\n",
+                               g3dimpl_trktypestr(trk->type));
+               return -1;
+       }
+
+       key.tm = msec;
+       key.val[0] = val;
+       return goat3d_set_track_key(trk, &key);
+}
+
+GOAT3DAPI int goat3d_set_track_vec3(struct goat3d_track *trk, long msec, float x, float y, float z)
+{
+       struct goat3d_key key = {0};
+       enum goat3d_track_type basetype;
+
+       basetype = BASETYPE(trk->type);
+
+       if(basetype != GOAT3D_TRACK_VEC3) {
+               goat3d_logmsg(LOG_WARNING, "goat3d_set_track_vec3 called on %s track\n",
+                               g3dimpl_trktypestr(trk->type));
+               return -1;
+       }
+
+       key.tm = msec;
+       key.val[0] = x;
+       key.val[1] = y;
+       key.val[2] = z;
+       return goat3d_set_track_key(trk, &key);
+}
+
+GOAT3DAPI int goat3d_set_track_vec4(struct goat3d_track *trk, long msec, float x, float y, float z, float w)
+{
+       struct goat3d_key key = {0};
+       enum goat3d_track_type basetype;
+
+       basetype = BASETYPE(trk->type);
+
+       if(basetype != GOAT3D_TRACK_VEC4) {
+               goat3d_logmsg(LOG_WARNING, "goat3d_set_track_vec4 called on %s track\n",
+                               g3dimpl_trktypestr(trk->type));
+               return -1;
+       }
+
+       key.tm = msec;
+       key.val[0] = x;
+       key.val[1] = y;
+       key.val[2] = z;
+       key.val[3] = w;
+       return goat3d_set_track_key(trk, &key);
+}
+
+GOAT3DAPI int goat3d_set_track_quat(struct goat3d_track *trk, long msec, float x, float y, float z, float w)
+{
+       struct goat3d_key key = {0};
+       enum goat3d_track_type basetype;
+
+       basetype = BASETYPE(trk->type);
+
+       if(basetype != GOAT3D_TRACK_QUAT) {
+               goat3d_logmsg(LOG_WARNING, "goat3d_set_track_quat called on %s track\n",
+                               g3dimpl_trktypestr(trk->type));
+               return -1;
+       }
+
+       key.tm = msec;
+       key.val[0] = x;
+       key.val[1] = y;
+       key.val[2] = z;
+       key.val[3] = w;
+       return goat3d_set_track_key(trk, &key);
+}
+
+
+GOAT3DAPI void goat3d_get_track_val(const struct goat3d_track *trk, long msec, float *valp)
+{
+       enum goat3d_track_type basetype;
+       anm_time_t tm = ANM_MSEC2TM(msec);
+
+       basetype = BASETYPE(trk->type);
+
+       if(basetype != GOAT3D_TRACK_VAL) {
+               goat3d_logmsg(LOG_WARNING, "goat3d_get_track_val called on %s track\n",
+                               g3dimpl_trktypestr(trk->type));
+               return;
+       }
+
+       *valp = anm_get_value(trk->trk, tm);
+}
+
+GOAT3DAPI void goat3d_get_track_vec3(const struct goat3d_track *trk, long msec, float *xp, float *yp, float *zp)
+{
+       enum goat3d_track_type basetype;
+       anm_time_t tm = ANM_MSEC2TM(msec);
+
+       basetype = BASETYPE(trk->type);
+
+       if(basetype != GOAT3D_TRACK_VEC3) {
+               goat3d_logmsg(LOG_WARNING, "goat3d_get_track_vec3 called on %s track\n",
+                               g3dimpl_trktypestr(trk->type));
+               return;
+       }
+
+       *xp = anm_get_value(trk->trk, tm);
+       *yp = anm_get_value(trk->trk + 1, tm);
+       *zp = anm_get_value(trk->trk + 2, tm);
+}
+
+GOAT3DAPI void goat3d_get_track_vec4(const struct goat3d_track *trk, long msec, float *xp, float *yp, float *zp, float *wp)
+{
+       enum goat3d_track_type basetype;
+       anm_time_t tm = ANM_MSEC2TM(msec);
+
+       basetype = BASETYPE(trk->type);
+
+       if(basetype != GOAT3D_TRACK_VEC4) {
+               goat3d_logmsg(LOG_WARNING, "goat3d_get_track_vec4 called on %s track\n",
+                               g3dimpl_trktypestr(trk->type));
+               return;
+       }
+
+       *xp = anm_get_value(trk->trk, tm);
+       *yp = anm_get_value(trk->trk + 1, tm);
+       *zp = anm_get_value(trk->trk + 2, tm);
+       *wp = anm_get_value(trk->trk + 3, tm);
+}
+
+GOAT3DAPI void goat3d_get_track_quat(const struct goat3d_track *trk, long msec, float *xp, float *yp, float *zp, float *wp)
+{
+       enum goat3d_track_type basetype;
+       float quat[4];
+       anm_time_t tm = ANM_MSEC2TM(msec);
+
+       basetype = BASETYPE(trk->type);
+
+       if(basetype != GOAT3D_TRACK_QUAT) {
+               goat3d_logmsg(LOG_WARNING, "goat3d_get_track_quat called on %s track\n",
+                               g3dimpl_trktypestr(trk->type));
+               return;
+       }
+
+       anm_get_quat(trk->trk, trk->trk + 1, trk->trk + 2, trk->trk + 3, tm, quat);
+       *xp = quat[0];
+       *yp = quat[1];
+       *zp = quat[2];
+       *wp = quat[3];
+}
+
+GOAT3DAPI long goat3d_get_track_timeline(const struct goat3d_track *trk, long *tstart, long *tend)
+{
+       int i, j, num;
+       enum goat3d_track_type basetype;
+       struct anm_keyframe *key;
+       anm_time_t start = ANM_TIME_MAX;
+       anm_time_t end = ANM_TIME_MIN;
+
+       basetype = BASETYPE(trk->type);
+       num = key_val_sz[basetype];
+
+       for(i=0; i<num; i++) {
+               for(j=0; j<trk->trk[i].count; j++) {
+                       key = anm_get_keyframe(trk->trk + i, j);
+                       if(key->time < start) start = key->time;
+                       if(key->time > end) end = key->time;
+               }
+       }
+
+       if(end < start) {
+               return -1;
+       }
+       *tstart = ANM_TM2MSEC(start);
+       *tend = ANM_TM2MSEC(end);
+       return *tend - *tstart;
+}
+
+/* animation */
+GOAT3DAPI int goat3d_add_anim(struct goat3d *g, struct goat3d_anim *anim)
+{
+       struct goat3d_anim **arr;
+       if(!(arr = dynarr_push(g->anims, &anim))) {
+               return -1;
+       }
+       g->anims = arr;
+       return 0;
+}
+
+GOAT3DAPI int goat3d_get_anim_count(const struct goat3d *g)
+{
+       return dynarr_size(g->anims);
+}
+
+GOAT3DAPI struct goat3d_anim *goat3d_get_anim(const struct goat3d *g, int idx)
+{
+       return g->anims[idx];
+}
+
+GOAT3DAPI struct goat3d_anim *goat3d_get_anim_by_name(const struct goat3d *g, const char *name)
+{
+       int i, num = dynarr_size(g->anims);
+       for(i=0; i<num; i++) {
+               if(strcmp(g->anims[i]->name, name) == 0) {
+                       return g->anims[i];
+               }
+       }
+       return 0;
+}
+
+GOAT3DAPI struct goat3d_anim *goat3d_create_anim(void)
+{
+       struct goat3d_anim *anim;
+
+       if(!(anim = malloc(sizeof *anim))) {
+               return 0;
+       }
+       if(g3dimpl_anim_init(anim) == -1) {
+               free(anim);
+               return 0;
+       }
+       return anim;
+}
+
+GOAT3DAPI void goat3d_destroy_anim(struct goat3d_anim *anim)
+{
+       g3dimpl_anim_destroy(anim);
+       free(anim);
+}
+
+GOAT3DAPI int goat3d_set_anim_name(struct goat3d_anim *anim, const char *name)
+{
+       SETNAME(anim->name, name);
+}
+
+GOAT3DAPI const char *goat3d_get_anim_name(const struct goat3d_anim *anim)
+{
+       return anim->name;
+}
+
+GOAT3DAPI int goat3d_add_anim_track(struct goat3d_anim *anim, struct goat3d_track *trk)
+{
+       struct goat3d_track **tmptrk;
+
+       if(!(tmptrk = dynarr_push(anim->tracks, &trk))) {
+               return -1;
+       }
+       anim->tracks = tmptrk;
+       return 0;
+}
+
+GOAT3DAPI struct goat3d_track *goat3d_get_anim_track(const struct goat3d_anim *anim, int idx)
+{
+       return anim->tracks[idx];
+}
+
+GOAT3DAPI struct goat3d_track *goat3d_get_anim_track_by_name(const struct goat3d_anim *anim, const char *name)
+{
+       int i, num = dynarr_size(anim->tracks);
+       for(i=0; i<num; i++) {
+               if(strcmp(anim->tracks[i]->name, name) == 0) {
+                       return anim->tracks[i];
+               }
+       }
+       return 0;
+}
+
+GOAT3DAPI struct goat3d_track *goat3d_get_anim_track_by_type(const struct goat3d_anim *anim, enum goat3d_track_type type)
+{
+       int i, num = dynarr_size(anim->tracks);
+       for(i=0; i<num; i++) {
+               if(anim->tracks[i]->type == type) {
+                       return anim->tracks[i];
+               }
+       }
+       return 0;
+}
+
+GOAT3DAPI int goat3d_get_anim_track_count(const struct goat3d_anim *anim)
+{
+       return dynarr_size(anim->tracks);
+}
+
+GOAT3DAPI long goat3d_get_anim_timeline(const struct goat3d_anim *anim, long *tstart, long *tend)
+{
+       int i, num = dynarr_size(anim->tracks);
+       long start, end, trkstart, trkend;
+
+       start = LONG_MAX;
+       end = LONG_MIN;
+
+       for(i=0; i<num; i++) {
+               if(goat3d_get_track_timeline(anim->tracks[i], &trkstart, &trkend) != -1) {
+                       if(trkstart < start) start = trkstart;
+                       if(trkend > end) end = trkend;
+               }
+       }
+
+       if(end < start) {
+               return -1;
+       }
+
+       *tstart = start;
+       *tend = end;
+       return end - start;
+}
+
+
+
+
+static long read_file(void *buf, size_t bytes, void *uptr)
+{
+       return (long)fread(buf, 1, bytes, (FILE*)uptr);
+}
+
+static long write_file(const void *buf, size_t bytes, void *uptr)
+{
+       return (long)fwrite(buf, 1, bytes, (FILE*)uptr);
+}
+
+static long seek_file(long offs, int whence, void *uptr)
+{
+       if(fseek((FILE*)uptr, offs, whence) == -1) {
+               return -1;
+       }
+       return ftell((FILE*)uptr);
+}
+
+static char *clean_filename(char *str)
+{
+       char *last_slash, *ptr;
+
+       if(!(last_slash = strrchr(str, '/'))) {
+               last_slash = strrchr(str, '\\');
+       }
+       if(last_slash) {
+               str = last_slash + 1;
+       }
+
+       ptr = str;
+       while(*ptr) {
+               char c = tolower(*ptr);
+               *ptr++ = c;
+       }
+       *ptr = 0;
+       return str;
+}
diff --git a/libs/goat3d/src/log.c b/libs/goat3d/src/log.c
new file mode 100644 (file)
index 0000000..f41f28c
--- /dev/null
@@ -0,0 +1,36 @@
+/*
+goat3d - 3D scene, and animation file format library.
+Copyright (C) 2013-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/>.
+*/
+#include <stdio.h>
+#include <stdarg.h>
+#include "log.h"
+
+int goat3d_log_level = 256;
+
+void goat3d_logmsg(int prio, const char *fmt, ...)
+{
+       va_list ap;
+
+       if(goat3d_log_level < prio) {
+               return;
+       }
+
+       fprintf(stderr, "goat3d: ");
+       va_start(ap, fmt);
+       vfprintf(stderr, fmt, ap);
+       va_end(ap);
+}
diff --git a/libs/goat3d/src/log.h b/libs/goat3d/src/log.h
new file mode 100644 (file)
index 0000000..19307a0
--- /dev/null
@@ -0,0 +1,25 @@
+/*
+goat3d - 3D scene, and animation file format library.
+Copyright (C) 2013-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/>.
+*/
+#ifndef LOG_H_
+#define LOG_H_
+
+enum { LOG_ERROR, LOG_WARNING, LOG_INFO };
+
+void goat3d_logmsg(int prio, const char *fmt, ...);
+
+#endif /* LOG_H_ */
diff --git a/libs/goat3d/src/read.c b/libs/goat3d/src/read.c
new file mode 100644 (file)
index 0000000..368ae75
--- /dev/null
@@ -0,0 +1,849 @@
+/*
+goat3d - 3D scene, and animation file format library.
+Copyright (C) 2013-2023  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/>.
+*/
+#include <assert.h>
+#include "treestor.h"
+#include "goat3d.h"
+#include "g3dscn.h"
+#include "log.h"
+#include "dynarr.h"
+#include "track.h"
+
+#if defined(__WATCOMC__) || defined(_WIN32) || defined(__DJGPP__)
+#include <malloc.h>
+#else
+#include <alloca.h>
+#endif
+
+
+struct key {
+       long tm;
+       cgm_vec4 val;
+};
+
+static struct goat3d_material *read_material(struct goat3d *g, struct ts_node *tsmtl);
+static char *read_material_attrib(struct material_attrib *attr, struct ts_node *tsmattr);
+static struct goat3d_mesh *read_mesh(struct goat3d *g, struct ts_node *tsmesh);
+static void *read_veclist(void *arr, int dim, const char *nodename, const char *attrname, struct ts_node *tsnode);
+static void *read_intlist(void *arr, int dim, const char *nodename, const char *attrname, struct ts_node *tsnode);
+static void *read_bonelist(struct goat3d *g, struct goat3d_node **arr, struct ts_node *tsnode);
+static int read_node(struct goat3d *g, struct goat3d_node *node, struct ts_node *tsnode);
+static int read_anim(struct goat3d *g, struct ts_node *tsanim);
+static struct goat3d_track *read_track(struct goat3d *g, struct ts_node *tstrk);
+
+GOAT3DAPI void *goat3d_b64decode(const char *str, void *buf, int *bufsz);
+#define b64decode goat3d_b64decode
+
+
+int g3dimpl_scnload(struct goat3d *g, struct goat3d_io *io)
+{
+       int idx;
+       struct ts_io tsio;
+       struct ts_node *tsroot, *c;
+       const char *str;
+
+       tsio.data = io->cls;
+       tsio.read = io->read;
+       tsio.write = io->write;
+
+       if(!(tsroot = ts_load_io(&tsio))) {
+               goat3d_logmsg(LOG_ERROR, "failed to load scene\n");
+               return -1;
+       }
+       if(strcmp(tsroot->name, "scene") != 0) {
+               goat3d_logmsg(LOG_ERROR, "invalid scene file, root node is not \"scene\"\n");
+               ts_free_tree(tsroot);
+               return -1;
+       }
+
+       /* read all materials */
+       c = tsroot->child_list;
+       while(c) {
+               if(strcmp(c->name, "mtl") == 0) {
+                       struct goat3d_material *mtl = read_material(g, c);
+                       if(mtl) {
+                               goat3d_add_mtl(g, mtl);
+                       }
+               }
+               c = c->next;
+       }
+
+       /* create placeholder nodes, only populating the name field, so that bone
+        * references in meshes can be resolved. We'll read the rest of the node
+        * info, including their mesh/light/camera references at the end.
+        */
+       c = tsroot->child_list;
+       while(c) {
+               if(strcmp(c->name, "node") == 0) {
+                       struct goat3d_node *node;
+
+                       if(!(node = goat3d_create_node())) {
+                               goat3d_logmsg(LOG_ERROR, "failed to allocate node\n");
+                               c = c->next;
+                               continue;
+                       }
+                       if((str = ts_get_attr_str(c, "name", 0))) {
+                               goat3d_set_node_name(node, str);
+                       }
+                       goat3d_add_node(g, node);
+               }
+               c = c->next;
+       }
+
+       /* read all meshes, cameras, lights, animations */
+       c = tsroot->child_list;
+       while(c) {
+               if(strcmp(c->name, "mesh") == 0) {
+                       struct goat3d_mesh *mesh = read_mesh(g, c);
+                       if(mesh) {
+                               goat3d_add_mesh(g, mesh);
+                       }
+               } else if(strcmp(c->name, "anim") == 0) {
+                       read_anim(g, c);
+               }
+
+               c = c->next;
+       }
+
+       /* now load the nodes properly */
+       idx = 0;
+       c = tsroot->child_list;
+       while(c) {
+               if(strcmp(c->name, "node") == 0) {
+                       struct goat3d_node *node = goat3d_get_node(g, idx++);
+                       assert(node);
+                       read_node(g, node, c);
+               }
+               c = c->next;
+       }
+
+       ts_free_tree(tsroot);
+       return 0;
+}
+
+int g3dimpl_anmload(struct goat3d *g, struct goat3d_io *io)
+{
+       /*
+       struct ts_io tsio;
+       tsio.data = io->cls;
+       tsio.read = io->read;
+       tsio.write = io->write;
+       */
+       return -1;
+}
+
+static struct goat3d_material *read_material(struct goat3d *g, struct ts_node *tsmtl)
+{
+       struct goat3d_material *mtl;
+       struct material_attrib mattr, *arr;
+       struct ts_node *c;
+       const char *str;
+
+       if(!(mtl = malloc(sizeof *mtl)) || g3dimpl_mtl_init(mtl) == -1) {
+               free(mtl);
+               goat3d_logmsg(LOG_ERROR, "read_material: failed to allocate material\n");
+               return 0;
+       }
+
+       if(!(str = ts_get_attr_str(tsmtl, "name", 0)) || !*str) {
+               /* XXX wait, we can refer to materials by index, why is the name important? */
+               goat3d_logmsg(LOG_WARNING, "read_material: ignoring material without a name\n");
+               g3dimpl_mtl_destroy(mtl);
+               return 0;
+       }
+       goat3d_set_mtl_name(mtl, str);
+
+       /* read all material attributes */
+       c = tsmtl->child_list;
+       while(c) {
+               if(strcmp(c->name, "attr") == 0) {
+                       if(read_material_attrib(&mattr, c)) {
+                               if(!(arr = dynarr_push(mtl->attrib, &mattr))) {
+                                       goat3d_logmsg(LOG_ERROR, "read_material: failed to resize material attribute array\n");
+                                       g3dimpl_mtl_destroy(mtl);
+                                       return 0;
+                               }
+                               mtl->attrib = arr;
+                       }
+               }
+               c = c->next;
+       }
+
+       /*if(dynarr_empty(mtl->attrib)) {
+               goat3d_logmsg(LOG_WARNING, "read_material: ignoring empty material: %s\n", mtl->name);
+               g3dimpl_mtl_destroy(mtl);
+               return 0;
+       }*/
+       return mtl;
+}
+
+static char *read_material_attrib(struct material_attrib *attr, struct ts_node *tsnode)
+{
+       int i;
+       struct ts_attr *tsattr;
+       const char *name, *map;
+
+       memset(attr, 0, sizeof *attr);
+
+       if((tsattr = ts_get_attr(tsnode, "val"))) {
+               attr->value.w = 1.0f;   /* default W to 1 if we get less than a float4 */
+
+               switch(tsattr->val.type) {
+               case TS_NUMBER:
+                       attr->value.x = tsattr->val.fnum;
+                       break;
+               case TS_VECTOR:
+                       assert(tsattr->val.vec_size <= 4);
+                       for(i=0; i<tsattr->val.vec_size; i++) {
+                               (&attr->value.x)[i] = tsattr->val.vec[i];
+                       }
+                       break;
+               default: /* no valid val attribute found */
+                       return 0;
+               }
+       }
+
+       if(!(name = ts_get_attr_str(tsnode, "name", 0)) || !*name) {
+               return 0;
+       }
+       if(!(attr->name = malloc(strlen(name) + 1))) {
+               goat3d_logmsg(LOG_ERROR, "read_material_attrib: failed to allocate name\n");
+               return 0;
+       }
+       strcpy(attr->name, name);
+
+       if((map = ts_get_attr_str(tsnode, "map", 0)) && *map) {
+               if(!(attr->map = malloc(strlen(map) + 1))) {
+                       goat3d_logmsg(LOG_ERROR, "read_material_attrib: failed to allocate map name\n");
+                       free(attr->name);
+                       return 0;
+               }
+               strcpy(attr->map, map);
+       }
+       return attr->name;
+}
+
+static struct goat3d_mesh *read_mesh(struct goat3d *g, struct ts_node *tsmesh)
+{
+       struct goat3d_mesh *mesh;
+       struct goat3d_material *mtl;
+       struct ts_node *c;
+       const char *str;
+       int num;
+       void *tmp;
+
+       if(!(mesh = malloc(sizeof *mesh)) || g3dimpl_obj_init((struct object*)mesh, OBJTYPE_MESH)) {
+               goat3d_logmsg(LOG_ERROR, "read_mesh: failed to allocate mesh\n");
+               goto fail;
+       }
+
+       if((str = ts_get_attr_str(tsmesh, "name", 0))) {
+               goat3d_set_mesh_name(mesh, str);
+       }
+
+       /* material reference */
+       if((num = ts_get_attr_num(tsmesh, "material", -1)) >= 0) {
+               if((mtl = goat3d_get_mtl(g, num))) {
+                       goat3d_set_mesh_mtl(mesh, mtl);
+               } else {
+                       goat3d_logmsg(LOG_WARNING, "read_mesh: mesh %s refers to invalid material: %d\n",
+                                       mesh->name, num);
+               }
+       } else if((str = ts_get_attr_str(tsmesh, "material", 0))) {
+               if((mtl = goat3d_get_mtl_by_name(g, str))) {
+                       goat3d_set_mesh_mtl(mesh, mtl);
+               } else {
+                       goat3d_logmsg(LOG_WARNING, "read_mesh: mesh %s refers to invalid material: %s\n",
+                                       mesh->name, str);
+               }
+       }
+
+       /* external mesh data */
+       if((str = ts_get_attr_str(tsmesh, "file", 0))) {
+               const char *fname = str;
+               char *pathbuf;
+
+               if(g->search_path) {
+                       pathbuf = alloca(strlen(str) + strlen(g->search_path) + 2);
+                       sprintf(pathbuf, "%s/%s", g->search_path, str);
+                       fname = pathbuf;
+               }
+
+               if(g3dimpl_loadmesh(mesh, fname) == -1) {
+                       goat3d_logmsg(LOG_ERROR, "read_mesh: failed to load external mesh: %s\n", fname);
+                       goto fail;
+               }
+
+               /* done loading, we can't have both an external mesh AND internal data,
+                * if there's anything else hanging under this tsnode, ignore it.
+                */
+               if(tsmesh->child_list) {
+                       goat3d_logmsg(LOG_WARNING, "read_mesh: external mesh node also has unexpected children; ignoring.\n");
+               }
+               return mesh;
+       }
+
+       c = tsmesh->child_list;
+       while(c) {
+               if(strcmp(c->name, "vertex_list") == 0) {
+                       if(!(tmp = read_veclist(mesh->vertices, 3, "vertex", "pos", c))) {
+                               goat3d_logmsg(LOG_ERROR, "read_mesh: failed to read vertex array for mesh %s\n",
+                                               mesh->name);
+                               goto fail;
+                       }
+                       mesh->vertices = tmp;
+
+               } else if(strcmp(c->name, "normal_list") == 0) {
+                       if(!(tmp = read_veclist(mesh->normals, 3, "normal", "dir", c))) {
+                               goat3d_logmsg(LOG_WARNING, "read_mesh: failed to read normals array for mesh %s\n",
+                                               mesh->name);
+                       } else {
+                               mesh->normals = tmp;
+                       }
+
+               } else if(strcmp(c->name, "tangent_list") == 0) {
+                       if(!(tmp = read_veclist(mesh->tangents, 3, "tangent", "dir", c))) {
+                               goat3d_logmsg(LOG_WARNING, "read_mesh: failed to read tangents array for mesh %s\n",
+                                               mesh->name);
+                       } else {
+                               mesh->tangents = tmp;
+                       }
+
+               } else if(strcmp(c->name, "texcoord_list") == 0) {
+                       if(!(tmp = read_veclist(mesh->texcoords, 2, "texcoord", "uv", c))) {
+                               goat3d_logmsg(LOG_WARNING, "read_mesh: failed to read texcoord array for mesh %s\n",
+                                               mesh->name);
+                       } else {
+                               mesh->texcoords = tmp;
+                       }
+
+               } else if(strcmp(c->name, "skinweight_list") == 0) {
+                       if(!(tmp = read_veclist(mesh->skin_weights, 4, "skinweight", "weights", c))) {
+                               goat3d_logmsg(LOG_WARNING, "read_mesh: failed to read skin weights array for mesh %s\n",
+                                               mesh->name);
+                       } else {
+                               mesh->skin_weights = tmp;
+                       }
+
+               } else if(strcmp(c->name, "skinmatrix_list") == 0) {
+                       if(!(tmp = read_intlist(mesh->skin_matrices, 4, "skinmatrix", "idx", c))) {
+                               goat3d_logmsg(LOG_WARNING, "read_mesh: failed to read skin matrix index array for mesh %s\n",
+                                               mesh->name);
+                       } else {
+                               mesh->skin_matrices = tmp;
+                       }
+
+               } else if(strcmp(c->name, "color_list") == 0) {
+                       if(!(tmp = read_veclist(mesh->colors, 4, "color", "color", c))) {
+                               goat3d_logmsg(LOG_WARNING, "read_mesh: failed to read color array for mesh %s\n",
+                                               mesh->name);
+                       } else {
+                               mesh->colors = tmp;
+                       }
+
+               } else if(strcmp(c->name, "bone_list") == 0) {
+                       if(!(tmp = read_bonelist(g, mesh->bones, c))) {
+                               goat3d_logmsg(LOG_WARNING, "read_mesh: failed to read bones array for mesh %s\n",
+                                               mesh->name);
+                       } else {
+                               mesh->bones = tmp;
+                       }
+
+               } else if(strcmp(c->name, "face_list") == 0) {
+                       if(!(tmp = read_intlist(mesh->faces, 3, "face", "idx", c))) {
+                               goat3d_logmsg(LOG_ERROR, "read_mesh: failed to read faces array for mesh %s\n",
+                                               mesh->name);
+                               goto fail;
+                       }
+                       mesh->faces = tmp;
+               }
+               c = c->next;
+       }
+       return mesh;
+
+fail:
+       g3dimpl_obj_destroy((struct object*)mesh);
+       return 0;
+}
+
+static int calc_b64_size(const char *s)
+{
+       int len = strlen(s);
+       const char *end = s + len;
+       while(end > s && *--end == '=') len--;
+       return len * 3 / 4;
+}
+
+static void *read_veclist(void *arr, int dim, const char *nodename, const char *attrname, struct ts_node *tslist)
+{
+       int i, size, bufsz;
+       struct ts_node *c;
+       struct ts_attr *attr;
+       float vec[4];
+       const char *str;
+
+       arr = dynarr_clear(arr);
+       assert(arr);
+
+       if((size = ts_get_attr_int(tslist, "list_size", -1)) <= 0) {
+               goat3d_logmsg(LOG_WARNING, "read_veclist: list_size attribute missing or invalid\n");
+               size = -1;
+       }
+
+       if((str = ts_get_attr_str(tslist, "base64", 0))) {
+               if(size == -1) size = calc_b64_size(str);
+               if(!(arr = dynarr_resize(arr, size))) {
+                       goat3d_logmsg(LOG_ERROR, "read_veclist: failed to resize %s array\n",
+                                       nodename);
+                       return 0;
+               }
+
+               bufsz = size * dim * sizeof(float);
+               b64decode(str, arr, &bufsz);
+       }
+
+       c = tslist->child_list;
+       while(c) {
+               if(strcmp(c->name, nodename) != 0) {
+                       c = c->next;
+                       continue;
+               }
+
+               if((attr = ts_get_attr(c, attrname)) && attr->val.type == TS_VECTOR) {
+                       for(i=0; i<dim; i++) {
+                               if(i >= attr->val.vec_size) {
+                                       vec[i] = 0;
+                               } else {
+                                       vec[i] = attr->val.vec[i];
+                               }
+                       }
+
+                       if(!(arr = dynarr_push(arr, vec))) {
+                               goat3d_logmsg(LOG_ERROR, "read_veclist: failed to resize %s array\n",
+                                               nodename);
+                               return 0;
+                       }
+               }
+               c = c->next;
+       }
+
+       if(size > 0 && dynarr_size(arr) != size) {
+               goat3d_logmsg(LOG_WARNING, "read_veclist: expected %d items, read %d\n", size, dynarr_size(arr));
+       }
+       return arr;
+}
+
+static void *read_intlist(void *arr, int dim, const char *nodename, const char *attrname, struct ts_node *tslist)
+{
+       int i, size, bufsz;
+       struct ts_node *c;
+       struct ts_attr *attr;
+       int ivec[4];
+       const char *str;
+
+       arr = dynarr_clear(arr);
+       assert(arr);
+
+       if((size = ts_get_attr_int(tslist, "list_size", -1)) <= 0) {
+               goat3d_logmsg(LOG_WARNING, "read_intlist: list_size attribute missing or invalid\n");
+               size = -1;
+       }
+
+       if((str = ts_get_attr_str(tslist, "base64", 0))) {
+               if(size == -1) size = calc_b64_size(str);
+               if(!(arr = dynarr_resize(arr, size))) {
+                       goat3d_logmsg(LOG_ERROR, "read_intlist: failed to resize %s array\n",
+                                       nodename);
+                       return 0;
+               }
+
+               bufsz = size * dim * sizeof(int);
+               b64decode(str, arr, &bufsz);
+       }
+
+       c = tslist->child_list;
+       while(c) {
+               if(strcmp(c->name, nodename) != 0) {
+                       c = c->next;
+                       continue;
+               }
+
+               if((attr = ts_get_attr(c, attrname)) && attr->val.type == TS_VECTOR) {
+                       for(i=0; i<dim; i++) {
+                               if(i >= attr->val.vec_size) {
+                                       ivec[i] = 0;
+                               } else {
+                                       ivec[i] = attr->val.vec[i];
+                               }
+                       }
+
+                       if(!(arr = dynarr_push(arr, ivec))) {
+                               goat3d_logmsg(LOG_ERROR, "read_intlist: failed to resize %s array\n",
+                                               nodename);
+                               return 0;
+                       }
+               }
+               c = c->next;
+       }
+
+       if(size > 0 && dynarr_size(arr) != size) {
+               goat3d_logmsg(LOG_WARNING, "read_intlist: expected %d items, read %d\n", size, dynarr_size(arr));
+       }
+       return arr;
+}
+
+static void *read_bonelist(struct goat3d *g, struct goat3d_node **arr, struct ts_node *tslist)
+{
+       int size, idx;
+       struct ts_node *c;
+       struct goat3d_node *bone;
+       const char *str;
+
+       arr = dynarr_clear(arr);
+       assert(arr);
+
+       if((size = ts_get_attr_int(tslist, "list_size", -1)) <= 0) {
+               goat3d_logmsg(LOG_WARNING, "read_bonelist: list_size attribute missing or invalid\n");
+               size = -1;
+       }
+
+       /* TODO base64 data support */
+
+       c = tslist->child_list;
+       while(c) {
+               if(strcmp(c->name, "bone") != 0) {
+                       c = c->next;
+                       continue;
+               }
+
+               bone = 0;
+
+               if((idx = ts_get_attr_int(c, "bone", -1)) >= 0) {
+                       if(!(bone = goat3d_get_node(g, idx))) {
+                               goat3d_logmsg(LOG_ERROR, "read_bonelist: reference to invalid bone: %d\n", idx);
+                               return 0;
+                       }
+
+               } else if((str = ts_get_attr_str(c, "bone", 0))) {
+                       if(!(bone = goat3d_get_node_by_name(g, str))) {
+                               goat3d_logmsg(LOG_ERROR, "read_bonelist: reference to invalid bone: %s\n", str);
+                               return 0;
+                       }
+               }
+
+               if(bone && !(arr = dynarr_push(arr, &bone))) {
+                       goat3d_logmsg(LOG_ERROR, "read_bonelist: failed to resize bone array\n");
+                       return 0;
+               }
+               c = c->next;
+       }
+
+       if(size > 0 && dynarr_size(arr) != size) {
+               goat3d_logmsg(LOG_WARNING, "read_bonelist: expected %d items, read %d\n", size, dynarr_size(arr));
+       }
+       return arr;
+}
+
+#define GETREF(ptr, typestr, getname) \
+       do { \
+               ptr = 0; \
+               if((idx = ts_get_attr_int(tsnode, typestr, -1)) >= 0) { \
+                       if(!(ptr = goat3d_get_##getname(g, idx))) { \
+                               goat3d_logmsg(LOG_ERROR, "read_node: ignoring reference to invalid " typestr ": %d\n", idx); \
+                       } \
+               } else if((str = ts_get_attr_str(tsnode, typestr, 0))) { \
+                       if(!(ptr = goat3d_get_##getname##_by_name(g, str))) { \
+                               goat3d_logmsg(LOG_ERROR, "read_node: ignoring reference to invalid " typestr ": %s\n", str); \
+                       } \
+               } \
+       } while(0)
+
+static int read_node(struct goat3d *g, struct goat3d_node *node, struct ts_node *tsnode)
+{
+       int idx;
+       const char *str;
+       struct goat3d_node *parent;
+       float *vec;
+
+       GETREF(parent, "parent", node);
+       if(parent) {
+               goat3d_add_node_child(parent, node);
+       }
+
+       node->type = GOAT3D_NODE_MESH;
+       GETREF(node->obj, "mesh", mesh);
+       if(!node->obj) {
+               node->type = GOAT3D_NODE_LIGHT;
+               GETREF(node->obj, "light", light);
+       }
+       if(!node->obj) {
+               node->type = GOAT3D_NODE_CAMERA;
+               GETREF(node->obj, "camera", camera);
+       }
+       if(!node->obj) {
+               node->type = GOAT3D_NODE_NULL;
+       }
+
+       if((vec = ts_get_attr_vec(tsnode, "pos", 0))) {
+               goat3d_set_node_position(node, vec[0], vec[1], vec[2]);
+       }
+       if((vec = ts_get_attr_vec(tsnode, "rot", 0))) {
+               goat3d_set_node_rotation(node, vec[0], vec[1], vec[2], vec[3]);
+       }
+       if((vec = ts_get_attr_vec(tsnode, "scale", 0))) {
+               goat3d_set_node_scaling(node, vec[0], vec[1], vec[2]);
+       }
+       if((vec = ts_get_attr_vec(tsnode, "pivot", 0))) {
+               goat3d_set_node_pivot(node, vec[0], vec[1], vec[2]);
+       }
+
+       return 0;
+}
+
+static int read_anim(struct goat3d *g, struct ts_node *tsanim)
+{
+       struct ts_node *c;
+       const char *str;
+       struct goat3d_anim *anim;
+       struct goat3d_track *trk;
+
+       if(!(str = ts_get_attr_str(tsanim, "name", 0))) {
+               goat3d_logmsg(LOG_WARNING, "read_anim: ignoring animation without a name\n");
+               return -1;
+       }
+
+       if(!(anim = goat3d_create_anim())) {
+               goat3d_logmsg(LOG_ERROR, "read_anim: failed to initialize animation: %s\n", str);
+               return -1;
+       }
+       goat3d_set_anim_name(anim, str);
+
+       c = tsanim->child_list;
+       while(c) {
+               if(strcmp(c->name, "track") == 0) {
+                       if(!(trk = read_track(g, c))) {
+                               c = c->next;
+                               continue;
+                       }
+                       goat3d_add_anim_track(anim, trk);
+               }
+               c = c->next;
+       }
+
+       goat3d_add_anim(g, anim);
+       return 0;
+}
+
+static int parsetype(const char *str)
+{
+       if(strcmp(str, "pos") == 0) return GOAT3D_TRACK_POS;
+       if(strcmp(str, "rot") == 0) return GOAT3D_TRACK_ROT;
+       if(strcmp(str, "scale") == 0) return GOAT3D_TRACK_SCALE;
+       if(strcmp(str, "val") == 0) return GOAT3D_TRACK_VAL;
+       if(strcmp(str, "vec3") == 0) return GOAT3D_TRACK_VEC3;
+       if(strcmp(str, "vec4") == 0) return GOAT3D_TRACK_VEC4;
+       if(strcmp(str, "quat") == 0) return GOAT3D_TRACK_QUAT;
+       return -1;
+}
+
+static int parseinterp(const char *str)
+{
+       if(strcmp(str, "step") == 0) return GOAT3D_INTERP_STEP;
+       if(strcmp(str, "linear") == 0) return GOAT3D_INTERP_LINEAR;
+       if(strcmp(str, "cubic") == 0) return GOAT3D_INTERP_CUBIC;
+       return -1;
+}
+
+static int parseextrap(const char *str)
+{
+       if(strcmp(str, "extend") == 0) return GOAT3D_EXTRAP_EXTEND;
+       if(strcmp(str, "clamp") == 0) return GOAT3D_EXTRAP_CLAMP;
+       if(strcmp(str, "repeat") == 0) return GOAT3D_EXTRAP_REPEAT;
+       if(strcmp(str, "pingpong") == 0) return GOAT3D_EXTRAP_PINGPONG;
+       return -1;
+}
+
+static struct goat3d_track *read_track(struct goat3d *g, struct ts_node *tstrk)
+{
+       int i, idx;
+       const char *str;
+       struct goat3d_track *trk;
+       struct goat3d_node *node;
+       struct goat3d_key key;
+       int type, in, ex;
+       struct ts_node *c;
+       struct ts_attr *tsattr;
+
+       if(!(str = ts_get_attr_str(tstrk, "type", 0)) || (type = parsetype(str)) == -1) {
+               goat3d_logmsg(LOG_WARNING, "read_track: ignoring track with missing or invalid type attribute\n");
+               return 0;
+       }
+
+       if((idx = ts_get_attr_int(tstrk, "node", -1)) >= 0) {
+               if(!(node = goat3d_get_node(g, idx))) {
+                       goat3d_logmsg(LOG_WARNING, "read_track: ignoring track with invalid node reference (%d)\n", idx);
+                       return 0;
+               }
+       } else if((str = ts_get_attr_str(tstrk, "node", 0))) {
+               if(!(node = goat3d_get_node_by_name(g, str))) {
+                       goat3d_logmsg(LOG_WARNING, "read_track: ignoring track with invalid node reference (%s)\n", str);
+                       return 0;
+               }
+       } else {
+               goat3d_logmsg(LOG_WARNING, "read_track: ignoring track with missing node reference\n");
+               return 0;
+       }
+
+       if(!(trk = goat3d_create_track())) {
+               goat3d_logmsg(LOG_ERROR, "read_track: failed to create new keyframe track\n");
+               return 0;
+       }
+       goat3d_set_track_node(trk, node);
+       goat3d_set_track_type(trk, type);
+
+       if((str = ts_get_attr_str(tstrk, "name", 0))) {
+               goat3d_set_track_name(trk, str);
+       }
+
+       if((str = ts_get_attr_str(tstrk, "interp", 0))) {
+               if((in = parseinterp(str)) == -1) {
+                       goat3d_logmsg(LOG_WARNING, "read_track: ignoring invalid interpolation mode: %s\n", str);
+               } else {
+                       goat3d_set_track_interp(trk, in);
+               }
+       }
+       if((str = ts_get_attr_str(tstrk, "extrap", 0))) {
+               if((ex = parseextrap(str)) == -1) {
+                       goat3d_logmsg(LOG_WARNING, "read_track: ignoring invalid extrapolation mode: %s\n", str);
+               } else {
+                       goat3d_set_track_extrap(trk, ex);
+               }
+       }
+
+       c = tstrk->child_list;
+       while(c) {
+               if(strcmp(c->name, "key") == 0) {
+                       if((key.tm = ts_get_attr_int(c, "time", INT_MIN)) == INT_MIN) {
+                               goat3d_logmsg(LOG_WARNING, "read_track: ignoring keyframe with missing or invalid time (%s)\n",
+                                               ts_get_attr_str(c, "time", ""));
+                               c = c->next;
+                               continue;
+                       }
+                       if(!(tsattr = ts_get_attr(c, "value")) || (tsattr->val.type != TS_NUMBER &&
+                                               tsattr->val.type != TS_VECTOR)) {
+                               goat3d_logmsg(LOG_WARNING, "read_track: ignoring keyframe with missing or invalid value (%s)\n",
+                                               ts_get_attr_str(c, "value", ""));
+                               c = c->next;
+                               continue;
+                       }
+
+                       if(tsattr->val.type == TS_NUMBER) {
+                               key.val[0] = tsattr->val.fnum;
+                       } else {
+                               for(i=0; i<4; i++) {
+                                       if(i < tsattr->val.vec_size) {
+                                               key.val[i] = tsattr->val.vec[i];
+                                       } else {
+                                               key.val[i] = 0;
+                                       }
+                               }
+                       }
+                       goat3d_set_track_key(trk, &key);
+               }
+               c = c->next;
+       }
+
+       /* force lazy re-sorting of keyframes if necessary */
+       goat3d_get_track_key(trk, 0, &key);
+
+       return trk;
+}
+
+static int b64bits(int c)
+{
+       if(c >= 'A' && c <= 'Z') {
+               return c - 'A';
+       }
+       if(c >= 'a' && c <= 'z') {
+               return c - 'a' + 26;
+       }
+       if(c >= '0' && c <= '9') {
+               return c - '0' + 52;
+       }
+       if(c == '+') return 62;
+       if(c == '/') return 63;
+
+       return -1;
+}
+
+GOAT3DAPI void *goat3d_b64decode(const char *str, void *buf, int *bufsz)
+{
+       unsigned char *dest, *end;
+       unsigned char acc;
+       int bits, sz;
+       unsigned int gidx;
+
+       if(buf) {
+               sz = *bufsz;
+       } else {
+               sz = calc_b64_size(str);
+               if(!(buf = malloc(sz))) {
+                       return 0;
+               }
+               if(bufsz) *bufsz = sz;
+       }
+       dest = buf;
+       end = (unsigned char*)buf + sz;
+
+       sz = 0;
+       gidx = 0;
+       acc = 0;
+       while(*str) {
+               if((bits = b64bits(*str++)) == -1) {
+                       continue;
+               }
+
+               switch(gidx++ & 3) {
+               case 0:
+                       acc = bits << 2;
+                       break;
+               case 1:
+                       if(dest < end) *dest = acc | (bits >> 4);
+                       dest++;
+                       acc = bits << 4;
+                       break;
+               case 2:
+                       if(dest < end) *dest = acc | (bits >> 2);
+                       dest++;
+                       acc = bits << 6;
+                       break;
+               case 3:
+                       if(dest < end) *dest = acc | bits;
+                       dest++;
+               default:
+                       break;
+               }
+       }
+
+       if(gidx & 3) {
+               if(dest < end) *dest = acc;
+               dest++;
+       }
+
+       if(bufsz) *bufsz = dest - (unsigned char*)buf;
+       return buf;
+}
diff --git a/libs/goat3d/src/track.c b/libs/goat3d/src/track.c
new file mode 100644 (file)
index 0000000..90d07cc
--- /dev/null
@@ -0,0 +1,415 @@
+/*
+libanim - hierarchical keyframe animation library
+Copyright (C) 2012-2023 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/>.
+*/
+
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include "track.h"
+#include "dynarr.h"
+
+#include "cgmath/cgmath.h"
+
+static int find_prev_key(const struct anm_keyframe *arr, int start, int end, anm_time_t tm);
+
+static float interp_step(float v0, float v1, float v2, float v3, float t);
+static float interp_linear(float v0, float v1, float v2, float v3, float t);
+static float interp_cubic(float v0, float v1, float v2, float v3, float t);
+
+static anm_time_t remap_extend(anm_time_t tm, anm_time_t start, anm_time_t end);
+static anm_time_t remap_clamp(anm_time_t tm, anm_time_t start, anm_time_t end);
+static anm_time_t remap_repeat(anm_time_t tm, anm_time_t start, anm_time_t end);
+static anm_time_t remap_pingpong(anm_time_t tm, anm_time_t start, anm_time_t end);
+
+/* XXX keep this in sync with enum anm_interpolator at track.h */
+static float (*interp[])(float, float, float, float, float) = {
+       interp_step,
+       interp_linear,
+       interp_cubic,
+       0
+};
+
+/* XXX keep this in sync with enum anm_extrapolator at track.h */
+static anm_time_t (*remap_time[])(anm_time_t, anm_time_t, anm_time_t) = {
+       remap_extend,
+       remap_clamp,
+       remap_repeat,
+       remap_pingpong,
+       0
+};
+
+int anm_init_track(struct anm_track *track)
+{
+       memset(track, 0, sizeof *track);
+
+       if(!(track->keys = dynarr_alloc(0, sizeof *track->keys))) {
+               return -1;
+       }
+       track->keys_sorted = 1;
+       track->interp = ANM_INTERP_LINEAR;
+       track->extrap = ANM_EXTRAP_CLAMP;
+       return 0;
+}
+
+void anm_destroy_track(struct anm_track *track)
+{
+       dynarr_free(track->keys);
+}
+
+struct anm_track *anm_create_track(void)
+{
+       struct anm_track *track;
+
+       if((track = malloc(sizeof *track))) {
+               if(anm_init_track(track) == -1) {
+                       free(track);
+                       return 0;
+               }
+       }
+       return track;
+}
+
+void anm_free_track(struct anm_track *track)
+{
+       anm_destroy_track(track);
+       free(track);
+}
+
+void anm_copy_track(struct anm_track *dest, const struct anm_track *src)
+{
+       free(dest->name);
+       if(dest->keys) {
+               dynarr_free(dest->keys);
+       }
+
+       if(src->name) {
+               dest->name = malloc(strlen(src->name) + 1);
+               strcpy(dest->name, src->name);
+       }
+
+       dest->count = src->count;
+       dest->keys = dynarr_alloc(src->count, sizeof *dest->keys);
+       memcpy(dest->keys, src->keys, src->count * sizeof *dest->keys);
+
+       dest->def_val = src->def_val;
+       dest->interp = src->interp;
+       dest->extrap = src->extrap;
+       dest->keys_sorted = src->keys_sorted;
+}
+
+int anm_set_track_name(struct anm_track *track, const char *name)
+{
+       char *tmp;
+
+       if(!(tmp = malloc(strlen(name) + 1))) {
+               return -1;
+       }
+       free(track->name);
+       track->name = tmp;
+       return 0;
+}
+
+const char *anm_get_track_name(const struct anm_track *track)
+{
+       return track->name;
+}
+
+void anm_set_track_interpolator(struct anm_track *track, enum anm_interpolator in)
+{
+       track->interp = in;
+}
+
+void anm_set_track_extrapolator(struct anm_track *track, enum anm_extrapolator ex)
+{
+       track->extrap = ex;
+}
+
+anm_time_t anm_remap_time(const struct anm_track *track, anm_time_t tm, anm_time_t start, anm_time_t end)
+{
+       return remap_time[track->extrap](tm, start, end);
+}
+
+void anm_set_track_default(struct anm_track *track, float def)
+{
+       track->def_val = def;
+}
+
+static int keycmp(const void *a, const void *b)
+{
+       return ((struct anm_keyframe*)a)->time - ((struct anm_keyframe*)b)->time;
+}
+
+int anm_set_keyframe(struct anm_track *track, struct anm_keyframe *key)
+{
+       int idx = anm_get_key_interval(track, key->time);
+
+       /* if we got a valid keyframe index, compare them... */
+       if(idx >= 0 && idx < track->count && keycmp(key, track->keys + idx) == 0) {
+               /* ... it's the same key, just update the value */
+               track->keys[idx].val = key->val;
+       } else {
+               /* ... it's a new key, add it and re-sort them if necessary */
+               void *tmp;
+               if(!(tmp = dynarr_push(track->keys, key))) {
+                       return -1;
+               }
+               track->keys = tmp;
+               idx = track->count++;
+               if(idx > 0 && track->keys[idx - 1].time > key->time) {
+                       /* key shold not go to the end, mark for re-sorting */
+                       track->keys_sorted = 0;
+               }
+       }
+       return 0;
+}
+
+#define lazysort_keys(track)   \
+       if(track->count > 1 && !track->keys_sorted) { \
+               qsort(track->keys, track->count, sizeof *track->keys, keycmp); \
+               ((struct anm_track*)track)->keys_sorted = 1; \
+       }
+
+struct anm_keyframe *anm_get_keyframe(const struct anm_track *track, int idx)
+{
+       if(idx < 0 || idx >= track->count) {
+               return 0;
+       }
+       lazysort_keys(track);
+       return track->keys + idx;
+}
+
+int anm_get_key_interval(const struct anm_track *track, anm_time_t tm)
+{
+       int last;
+
+       lazysort_keys(track);
+
+       if(!track->count || tm < track->keys[0].time) {
+               return -1;
+       }
+
+       last = track->count - 1;
+       if(tm > track->keys[last].time) {
+               return last;
+       }
+
+       return find_prev_key(track->keys, 0, last, tm);
+}
+
+static int find_prev_key(const struct anm_keyframe *arr, int start, int end, anm_time_t tm)
+{
+       int mid;
+
+       if(end - start <= 1) {
+               return start;
+       }
+
+       mid = (start + end) / 2;
+       if(tm < arr[mid].time) {
+               return find_prev_key(arr, start, mid, tm);
+       }
+       if(tm > arr[mid].time) {
+               return find_prev_key(arr, mid, end, tm);
+       }
+       return mid;
+}
+
+int anm_set_value(struct anm_track *track, anm_time_t tm, float val)
+{
+       struct anm_keyframe key;
+       key.time = tm;
+       key.val = val;
+
+       return anm_set_keyframe(track, &key);
+}
+
+float anm_get_value(const struct anm_track *track, anm_time_t tm)
+{
+       int idx0, idx1, last_idx;
+       anm_time_t tstart, tend;
+       float t, dt;
+       float v0, v1, v2, v3;
+
+       if(!track->count) {
+               return track->def_val;
+       }
+       lazysort_keys(track);
+
+       last_idx = track->count - 1;
+
+       tstart = track->keys[0].time;
+       tend = track->keys[last_idx].time;
+
+       if(tstart == tend) {
+               return track->keys[0].val;
+       }
+
+       tm = remap_time[track->extrap](tm, tstart, tend);
+
+       idx0 = anm_get_key_interval(track, tm);
+       assert(idx0 >= 0 && idx0 < track->count);
+       idx1 = idx0 + 1;
+
+       if(idx0 == last_idx) {
+               return track->keys[idx0].val;
+       }
+
+       dt = (float)(track->keys[idx1].time - track->keys[idx0].time);
+       t = (float)(tm - track->keys[idx0].time) / dt;
+
+       v1 = track->keys[idx0].val;
+       v2 = track->keys[idx1].val;
+
+       /* get the neigboring values to allow for cubic interpolation */
+       v0 = idx0 > 0 ? track->keys[idx0 - 1].val : v1;
+       v3 = idx1 < last_idx ? track->keys[idx1 + 1].val : v2;
+
+       return interp[track->interp](v0, v1, v2, v3, t);
+}
+
+
+void anm_get_quat(const struct anm_track *xtrk, const struct anm_track *ytrk,
+               const struct anm_track *ztrk, const struct anm_track *wtrk, anm_time_t tm, float *qres)
+{
+       int idx0, idx1, last_idx;
+       anm_time_t tstart, tend;
+       float t, dt;
+       cgm_quat q1, q2;
+
+       if(!xtrk->count) {
+               qres[0] = xtrk->def_val;
+               qres[1] = ytrk->def_val;
+               qres[2] = ztrk->def_val;
+               qres[3] = wtrk->def_val;
+               return;
+       }
+
+       lazysort_keys(xtrk);
+       lazysort_keys(ytrk);
+       lazysort_keys(ztrk);
+       lazysort_keys(wtrk);
+
+       last_idx = xtrk->count - 1;
+
+       tstart = xtrk->keys[0].time;
+       tend = xtrk->keys[last_idx].time;
+
+       if(tstart == tend) {
+               qres[0] = xtrk->keys[0].val;
+               qres[1] = ytrk->keys[0].val;
+               qres[2] = ztrk->keys[0].val;
+               qres[3] = wtrk->keys[0].val;
+               return;
+       }
+
+       tm = anm_remap_time(xtrk, tm, tstart, tend);
+
+       idx0 = anm_get_key_interval(xtrk, tm);
+       assert(idx0 >= 0 && idx0 < xtrk->count);
+       idx1 = idx0 + 1;
+
+       if(idx0 == last_idx) {
+               qres[0] = xtrk->keys[idx0].val;
+               qres[1] = ytrk->keys[idx0].val;
+               qres[2] = ztrk->keys[idx0].val;
+               qres[3] = wtrk->keys[idx0].val;
+               return;
+       }
+
+       dt = (float)(xtrk->keys[idx1].time - xtrk->keys[idx0].time);
+       t = (float)(tm - xtrk->keys[idx0].time) / dt;
+
+       q1.x = xtrk->keys[idx0].val;
+       q1.y = ytrk->keys[idx0].val;
+       q1.z = ztrk->keys[idx0].val;
+       q1.w = wtrk->keys[idx0].val;
+
+       q2.x = xtrk->keys[idx1].val;
+       q2.y = ytrk->keys[idx1].val;
+       q2.z = ztrk->keys[idx1].val;
+       q2.w = wtrk->keys[idx1].val;
+
+       cgm_qslerp((cgm_quat*)qres, &q1, &q2, t);
+}
+
+
+static float interp_step(float v0, float v1, float v2, float v3, float t)
+{
+       return v1;
+}
+
+static float interp_linear(float v0, float v1, float v2, float v3, float t)
+{
+       return v1 + (v2 - v1) * t;
+}
+
+static float interp_cubic(float a, float b, float c, float d, float t)
+{
+       float x, y, z, w;
+       float tsq = t * t;
+
+       x = -a + 3.0 * b - 3.0 * c + d;
+       y = 2.0 * a - 5.0 * b + 4.0 * c - d;
+       z = c - a;
+       w = 2.0 * b;
+
+       return 0.5 * (x * tsq * t + y * tsq + z * t + w);
+}
+
+static anm_time_t remap_extend(anm_time_t tm, anm_time_t start, anm_time_t end)
+{
+       return remap_repeat(tm, start, end);
+}
+
+static anm_time_t remap_clamp(anm_time_t tm, anm_time_t start, anm_time_t end)
+{
+       if(start == end) {
+               return start;
+       }
+       return tm < start ? start : (tm >= end ? end : tm);
+}
+
+static anm_time_t remap_repeat(anm_time_t tm, anm_time_t start, anm_time_t end)
+{
+       anm_time_t x, interv = end - start;
+
+       if(interv == 0) {
+               return start;
+       }
+
+       x = (tm - start) % interv;
+       if(x < 0) {
+               x += interv;
+       }
+       return x + start;
+
+       /*if(tm < start) {
+               while(tm < start) {
+                       tm += interv;
+               }
+               return tm;
+       }
+       return (tm - start) % interv + start;*/
+}
+
+static anm_time_t remap_pingpong(anm_time_t tm, anm_time_t start, anm_time_t end)
+{
+       anm_time_t interv = end - start;
+       anm_time_t x = remap_repeat(tm, start, end + interv);
+
+       return x > end ? end + interv - x : x;
+}
diff --git a/libs/goat3d/src/track.h b/libs/goat3d/src/track.h
new file mode 100644 (file)
index 0000000..17f044b
--- /dev/null
@@ -0,0 +1,131 @@
+/*
+libanim - hierarchical keyframe animation library
+Copyright (C) 2012-2023 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/>.
+*/
+
+/* An animation track defines the values of a single scalar over time
+ * and supports various interpolation and extrapolation modes.
+ */
+#ifndef LIBANIM_TRACK_H_
+#define LIBANIM_TRACK_H_
+
+#include <limits.h>
+/*#include "config.h"*/
+
+enum anm_interpolator {
+       ANM_INTERP_STEP,
+       ANM_INTERP_LINEAR,
+       ANM_INTERP_CUBIC
+};
+
+enum anm_extrapolator {
+       ANM_EXTRAP_EXTEND,      /* extend to infinity */
+       ANM_EXTRAP_CLAMP,       /* clamp to last value */
+       ANM_EXTRAP_REPEAT,      /* repeat motion */
+       ANM_EXTRAP_PINGPONG     /* repeat with mirroring */
+};
+
+typedef long anm_time_t;
+#define ANM_TIME_MIN   LONG_MIN
+#define ANM_TIME_MAX   LONG_MAX
+#define ANM_TIME_INVAL LONG_MIN
+
+#define ANM_SEC2TM(x)  ((anm_time_t)((x) * 1000))
+#define ANM_MSEC2TM(x) ((anm_time_t)(x))
+#define ANM_TM2SEC(x)  ((x) / 1000.0)
+#define ANM_TM2MSEC(x) (x)
+
+struct anm_keyframe {
+       anm_time_t time;
+       float val;
+};
+
+struct anm_track {
+       char *name;
+       int count;
+       struct anm_keyframe *keys;
+
+       float def_val;
+
+       enum anm_interpolator interp;
+       enum anm_extrapolator extrap;
+
+       int keys_sorted;
+};
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* track constructor and destructor */
+int anm_init_track(struct anm_track *track);
+void anm_destroy_track(struct anm_track *track);
+
+/* helper functions that use anm_init_track and anm_destroy_track internally */
+struct anm_track *anm_create_track(void);
+void anm_free_track(struct anm_track *track);
+
+/* copies track src to dest
+ * XXX: dest must have been initialized first
+ */
+void anm_copy_track(struct anm_track *dest, const struct anm_track *src);
+
+int anm_set_track_name(struct anm_track *track, const char *name);
+const char *anm_get_track_name(const struct anm_track *track);
+
+void anm_set_track_interpolator(struct anm_track *track, enum anm_interpolator in);
+void anm_set_track_extrapolator(struct anm_track *track, enum anm_extrapolator ex);
+
+anm_time_t anm_remap_time(const struct anm_track *track, anm_time_t tm,
+               anm_time_t start, anm_time_t end);
+
+void anm_set_track_default(struct anm_track *track, float def);
+
+/* set or update a keyframe */
+int anm_set_keyframe(struct anm_track *track, struct anm_keyframe *key);
+
+/* get the idx-th keyframe, returns null if it doesn't exist */
+struct anm_keyframe *anm_get_keyframe(const struct anm_track *track, int idx);
+
+/* Finds the 0-based index of the intra-keyframe interval which corresponds
+ * to the specified time. If the time falls exactly onto the N-th keyframe
+ * the function returns N.
+ *
+ * Special cases:
+ * - if the time is before the first keyframe -1 is returned.
+ * - if the time is after the last keyframe, the index of the last keyframe
+ *   is returned.
+ */
+int anm_get_key_interval(const struct anm_track *track, anm_time_t tm);
+
+int anm_set_value(struct anm_track *track, anm_time_t tm, float val);
+
+/* evaluates and returns the value of the track for a particular time */
+float anm_get_value(const struct anm_track *track, anm_time_t tm);
+
+/* evaluates a set of 4 tracks treated as a quaternion, to perform slerp instead
+ * of linear interpolation. Result is returned through the last argument, which
+ * is expected to point to an array of 4 floats (x,y,z,w)
+ */
+void anm_get_quat(const struct anm_track *xtrk, const struct anm_track *ytrk,
+               const struct anm_track *ztrk, const struct anm_track *wtrk, anm_time_t tm, float *qres);
+
+#ifdef __cplusplus
+}
+#endif
+
+
+#endif /* LIBANIM_TRACK_H_ */
diff --git a/libs/goat3d/src/write.c b/libs/goat3d/src/write.c
new file mode 100644 (file)
index 0000000..b144be5
--- /dev/null
@@ -0,0 +1,736 @@
+/*
+goat3d - 3D scene, and animation file format library.
+Copyright (C) 2013-2023  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/>.
+*/
+#include <stdlib.h>
+#include <string.h>
+#include "g3dscn.h"
+#include "log.h"
+#include "dynarr.h"
+#include "treestor.h"
+
+/* type passed to namegen */
+enum { MTL, MESH, LIGHT, CAMERA, NODE, ANIM, TRACK };
+
+static struct ts_node *create_mtltree(struct goat3d *g, const struct goat3d_material *mtl);
+static struct ts_node *create_meshtree(struct goat3d *g, const struct goat3d_mesh *mesh);
+static struct ts_node *create_lighttree(struct goat3d *g, const struct goat3d_light *light);
+static struct ts_node *create_camtree(struct goat3d *g, const struct goat3d_camera *cam);
+static struct ts_node *create_nodetree(struct goat3d *g, const struct goat3d_node *node);
+static struct ts_node *create_animtree(struct goat3d *g, const struct goat3d_anim *anim);
+static struct ts_node *create_tracktree(struct goat3d *g, const struct goat3d_track *trk);
+
+static void init_namegen(struct goat3d *g);
+static const char *namegen(struct goat3d *g, const char *name, int type);
+
+GOAT3DAPI char *goat3d_b64encode(const void *data, int size, char *buf, int *bufsz);
+#define b64encode goat3d_b64encode
+
+#define create_tsnode(n, p, nstr) \
+       do { \
+               int len = strlen(nstr); \
+               if(!((n) = ts_alloc_node())) { \
+                       goat3d_logmsg(LOG_ERROR, "failed to create treestore node\n"); \
+                       goto err; \
+               } \
+               if(!((n)->name = malloc(len + 1))) { \
+                       goat3d_logmsg(LOG_ERROR, "failed to allocate node name string\n"); \
+                       ts_free_node(n); \
+                       goto err; \
+               } \
+               memcpy((n)->name, (nstr), len + 1); \
+               if(p) { \
+                       ts_add_child((p), (n)); \
+               } \
+       } while(0)
+
+#define create_tsattr(a, n, nstr, atype) \
+       do { \
+               if(!((a) = ts_alloc_attr())) { \
+                       goat3d_logmsg(LOG_ERROR, "failed to create treestore attribute\n"); \
+                       goto err; \
+               } \
+               if(ts_set_attr_name(a, nstr) == -1) { \
+                       goat3d_logmsg(LOG_ERROR, "failed to allocate attrib name string\n"); \
+                       ts_free_attr(a); \
+                       goto err; \
+               } \
+               (a)->val.type = (atype); \
+               if(n) { \
+                       ts_add_attr((n), (a)); \
+               } \
+       } while(0)
+
+
+
+int g3dimpl_scnsave(const struct goat3d *g, struct goat3d_io *io)
+{
+       int i, num;
+       struct ts_io tsio;
+       struct ts_node *tsroot = 0, *tsn, *tsenv;
+       struct ts_attr *tsa;
+
+       tsio.data = io->cls;
+       tsio.read = io->read;
+       tsio.write = io->write;
+
+       init_namegen((struct goat3d*)g);
+
+       create_tsnode(tsroot, 0, "scene");
+
+       /* environment */
+       create_tsnode(tsenv, tsroot, "env");
+       create_tsattr(tsa, tsenv, "ambient", TS_VECTOR);
+       ts_set_valuefv(&tsa->val, 3, g->ambient.x, g->ambient.y, g->ambient.z);
+       /* TODO: fog */
+
+       num = dynarr_size(g->materials);
+       for(i=0; i<num; i++) {
+               if(!(tsn = create_mtltree((struct goat3d*)g, g->materials[i]))) {
+                       goto err;
+               }
+               ts_add_child(tsroot, tsn);
+       }
+
+       num = dynarr_size(g->meshes);
+       for(i=0; i<num; i++) {
+               if(!(tsn = create_meshtree((struct goat3d*)g, g->meshes[i]))) {
+                       goto err;
+               }
+               ts_add_child(tsroot, tsn);
+       }
+
+       num = dynarr_size(g->lights);
+       for(i=0; i<num; i++) {
+               if(!(tsn = create_lighttree((struct goat3d*)g, g->lights[i]))) {
+                       goto err;
+               }
+               ts_add_child(tsroot, tsn);
+       }
+
+       num = dynarr_size(g->cameras);
+       for(i=0; i<num; i++) {
+               if(!(tsn = create_camtree((struct goat3d*)g, g->cameras[i]))) {
+                       goto err;
+               }
+               ts_add_child(tsroot, tsn);
+       }
+
+       num = dynarr_size(g->nodes);
+       for(i=0; i<num; i++) {
+               if(!(tsn = create_nodetree((struct goat3d*)g, g->nodes[i]))) {
+                       goto err;
+               }
+               ts_add_child(tsroot, tsn);
+       }
+
+       num = dynarr_size(g->anims);
+       for(i=0; i<num; i++) {
+               if(!(tsn = create_animtree((struct goat3d*)g, g->anims[i]))) {
+                       goto err;
+               }
+               ts_add_child(tsroot, tsn);
+       }
+
+       if(ts_save_io(tsroot, &tsio) == -1) {
+               goat3d_logmsg(LOG_ERROR, "g3dimpl_scnsave: failed\n");
+               goto err;
+       }
+       return 0;
+
+err:
+       ts_free_tree(tsroot);
+       return -1;
+}
+
+int g3dimpl_anmsave(const struct goat3d *g, struct goat3d_io *io)
+{
+       return -1;
+}
+
+static struct ts_node *create_mtltree(struct goat3d *g, const struct goat3d_material *mtl)
+{
+       int i, num_attr;
+       struct ts_node *tsn, *tsmtl = 0;
+       struct ts_attr *tsa;
+
+       create_tsnode(tsmtl, 0, "mtl");
+       create_tsattr(tsa, tsmtl, "name", TS_STRING);
+       if(ts_set_value_str(&tsa->val, namegen(g, mtl->name, MTL)) == -1) {
+               goto err;
+       }
+
+       num_attr = dynarr_size(mtl->attrib);
+       for(i=0; i<num_attr; i++) {
+               struct material_attrib *attr = mtl->attrib + i;
+
+               create_tsnode(tsn, tsmtl, "attr");
+               create_tsattr(tsa, tsn, "name", TS_STRING);
+               if(ts_set_value_str(&tsa->val, attr->name) == -1) {
+                       goto err;
+               }
+               create_tsattr(tsa, tsn, "val", TS_VECTOR);
+               ts_set_valuefv(&tsa->val, 4, attr->value.x, attr->value.y, attr->value.z, attr->value.w);
+               if(attr->map) {
+                       create_tsattr(tsa, tsn, "map", TS_STRING);
+                       if(ts_set_value_str(&tsa->val, attr->map) == -1) {
+                               goto err;
+                       }
+               }
+       }
+       return tsmtl;
+
+err:
+       ts_free_tree(tsmtl);
+       return 0;
+}
+
+static struct ts_node *create_meshtree(struct goat3d *g, const struct goat3d_mesh *mesh)
+{
+       int i, num;
+       struct ts_node *tsmesh = 0, *tslist, *tsitem;
+       struct ts_attr *tsa;
+
+       create_tsnode(tsmesh, 0, "mesh");
+       create_tsattr(tsa, tsmesh, "name", TS_STRING);
+       if(ts_set_value_str(&tsa->val, namegen(g, mesh->name, MESH)) == -1) {
+               goto err;
+       }
+
+       if(mesh->mtl) {
+               if(mesh->mtl->name) {
+                       create_tsattr(tsa, tsmesh, "material", TS_STRING);
+                       if(ts_set_value_str(&tsa->val, mesh->mtl->name) == -1) {
+                               goto err;
+                       }
+               } else {
+                       create_tsattr(tsa, tsmesh, "material", TS_NUMBER);
+                       ts_set_valuei(&tsa->val, mesh->mtl->idx);
+               }
+       }
+
+       /* TODO option of saving separate mesh files */
+
+       if((num = dynarr_size(mesh->vertices))) {
+               create_tsnode(tslist, tsmesh, "vertex_list");
+               create_tsattr(tsa, tslist, "list_size", TS_NUMBER);
+               ts_set_valuei(&tsa->val, num);
+
+               if(goat3d_getopt(g, GOAT3D_OPT_SAVEBINDATA)) {
+                       create_tsattr(tsa, tslist, "base64", TS_STRING);
+                       if(!(tsa->val.str = b64encode(mesh->vertices, num * 3 * sizeof(float), 0, 0))) {
+                               goto err;
+                       }
+               } else {
+                       for(i=0; i<num; i++) {
+                               cgm_vec3 *vptr = mesh->vertices + i;
+                               create_tsnode(tsitem, tslist, "vertex");
+                               create_tsattr(tsa, tsitem, "pos", TS_VECTOR);
+                               ts_set_valuefv(&tsa->val, 3, vptr->x, vptr->y, vptr->z);
+                       }
+               }
+       }
+
+       if((num = dynarr_size(mesh->normals))) {
+               create_tsnode(tslist, tsmesh, "normal_list");
+               create_tsattr(tsa, tslist, "list_size", TS_NUMBER);
+               ts_set_valuei(&tsa->val, num);
+
+               if(goat3d_getopt(g, GOAT3D_OPT_SAVEBINDATA)) {
+                       create_tsattr(tsa, tslist, "base64", TS_STRING);
+                       if(!(tsa->val.str = b64encode(mesh->normals, num * 3 * sizeof(float), 0, 0))) {
+                               goto err;
+                       }
+               } else {
+                       for(i=0; i<num; i++) {
+                               cgm_vec3 *nptr = mesh->normals + i;
+                               create_tsnode(tsitem, tslist, "normal");
+                               create_tsattr(tsa, tsitem, "dir", TS_VECTOR);
+                               ts_set_valuefv(&tsa->val, 3, nptr->x, nptr->y, nptr->z);
+                       }
+               }
+       }
+
+       if((num = dynarr_size(mesh->tangents))) {
+               create_tsnode(tslist, tsmesh, "tangent_list");
+               create_tsattr(tsa, tslist, "list_size", TS_NUMBER);
+               ts_set_valuei(&tsa->val, num);
+
+               if(goat3d_getopt(g, GOAT3D_OPT_SAVEBINDATA)) {
+                       create_tsattr(tsa, tslist, "base64", TS_STRING);
+                       if(!(tsa->val.str = b64encode(mesh->tangents, num * 3 * sizeof(float), 0, 0))) {
+                               goto err;
+                       }
+               } else {
+                       for(i=0; i<num; i++) {
+                               cgm_vec3 *tptr = mesh->tangents + i;
+                               create_tsnode(tsitem, tslist, "tangent");
+                               create_tsattr(tsa, tsitem, "dir", TS_VECTOR);
+                               ts_set_valuefv(&tsa->val, 3, tptr->x, tptr->y, tptr->z);
+                       }
+               }
+       }
+
+       if((num = dynarr_size(mesh->texcoords))) {
+               create_tsnode(tslist, tsmesh, "texcoord_list");
+               create_tsattr(tsa, tslist, "list_size", TS_NUMBER);
+               ts_set_valuei(&tsa->val, num);
+
+               if(goat3d_getopt(g, GOAT3D_OPT_SAVEBINDATA)) {
+                       create_tsattr(tsa, tslist, "base64", TS_STRING);
+                       if(!(tsa->val.str = b64encode(mesh->texcoords, num * 3 * sizeof(float), 0, 0))) {
+                               goto err;
+                       }
+               } else {
+                       for(i=0; i<num; i++) {
+                               cgm_vec2 *uvptr = mesh->texcoords + i;
+                               create_tsnode(tsitem, tslist, "texcoord");
+                               create_tsattr(tsa, tsitem, "uv", TS_VECTOR);
+                               ts_set_valuefv(&tsa->val, 3, uvptr->x, uvptr->y, 0.0f);
+                       }
+               }
+       }
+
+       if((num = dynarr_size(mesh->skin_weights))) {
+               create_tsnode(tslist, tsmesh, "skinweight_list");
+               create_tsattr(tsa, tslist, "list_size", TS_NUMBER);
+               ts_set_valuei(&tsa->val, num);
+
+               if(goat3d_getopt(g, GOAT3D_OPT_SAVEBINDATA)) {
+                       create_tsattr(tsa, tslist, "base64", TS_STRING);
+                       if(!(tsa->val.str = b64encode(mesh->skin_weights, num * 4 * sizeof(float), 0, 0))) {
+                               goto err;
+                       }
+               } else {
+                       for(i=0; i<num; i++) {
+                               cgm_vec4 *wptr = mesh->skin_weights + i;
+                               create_tsnode(tsitem, tslist, "skinweight");
+                               create_tsattr(tsa, tsitem, "weights", TS_VECTOR);
+                               ts_set_valuefv(&tsa->val, 4, wptr->x, wptr->y, wptr->z, wptr->w);
+                       }
+               }
+       }
+
+       if((num = dynarr_size(mesh->skin_matrices))) {
+               create_tsnode(tslist, tsmesh, "skinmatrix_list");
+               create_tsattr(tsa, tslist, "list_size", TS_NUMBER);
+               ts_set_valuei(&tsa->val, num);
+
+               if(goat3d_getopt(g, GOAT3D_OPT_SAVEBINDATA)) {
+                       create_tsattr(tsa, tslist, "base64", TS_STRING);
+                       if(!(tsa->val.str = b64encode(mesh->skin_matrices, num * 4 * sizeof(int), 0, 0))) {
+                               goto err;
+                       }
+               } else {
+                       for(i=0; i<num; i++) {
+                               int4 *iptr = mesh->skin_matrices + i;
+                               create_tsnode(tsitem, tslist, "skinmatrix");
+                               create_tsattr(tsa, tsitem, "idx", TS_VECTOR);
+                               ts_set_valueiv(&tsa->val, 4, iptr->x, iptr->y, iptr->z, iptr->w);
+                       }
+               }
+       }
+
+       if((num = dynarr_size(mesh->colors))) {
+               create_tsnode(tslist, tsmesh, "color_list");
+               create_tsattr(tsa, tslist, "list_size", TS_NUMBER);
+               ts_set_valuei(&tsa->val, num);
+
+               if(goat3d_getopt(g, GOAT3D_OPT_SAVEBINDATA)) {
+                       create_tsattr(tsa, tslist, "base64", TS_STRING);
+                       if(!(tsa->val.str = b64encode(mesh->colors, num * 4 * sizeof(float), 0, 0))) {
+                               goto err;
+                       }
+               } else {
+                       for(i=0; i<num; i++) {
+                               cgm_vec4 *cptr = mesh->colors + i;
+                               create_tsnode(tsitem, tslist, "color");
+                               create_tsattr(tsa, tsitem, "color", TS_VECTOR);
+                               ts_set_valuefv(&tsa->val, 4, cptr->x, cptr->y, cptr->z, cptr->w);
+                       }
+               }
+       }
+
+       if((num = dynarr_size(mesh->bones))) {
+               create_tsnode(tslist, tsmesh, "bone_list");
+               create_tsattr(tsa, tslist, "list_size", TS_NUMBER);
+               ts_set_valuei(&tsa->val, num);
+
+               /* TODO: base64 option */
+               for(i=0; i<num; i++) {
+                       create_tsnode(tsitem, tslist, "bone");
+                       create_tsattr(tsa, tsitem, "name", TS_STRING);
+                       if(ts_set_value_str(&tsa->val, mesh->bones[i]->name) == -1) {
+                               goto err;
+                       }
+               }
+       }
+
+       if((num = dynarr_size(mesh->faces))) {
+               create_tsnode(tslist, tsmesh, "face_list");
+               create_tsattr(tsa, tslist, "list_size", TS_NUMBER);
+               ts_set_valuei(&tsa->val, num);
+
+               if(goat3d_getopt(g, GOAT3D_OPT_SAVEBINDATA)) {
+                       create_tsattr(tsa, tslist, "base64", TS_STRING);
+                       if(!(tsa->val.str = b64encode(mesh->faces, num * 3 * sizeof(int), 0, 0))) {
+                               goto err;
+                       }
+               } else {
+                       for(i=0; i<num; i++) {
+                               struct face *fptr = mesh->faces + i;
+                               create_tsnode(tsitem, tslist, "face");
+                               create_tsattr(tsa, tsitem, "idx", TS_VECTOR);
+                               ts_set_valueiv(&tsa->val, 3, fptr->v[0], fptr->v[1], fptr->v[2]);
+                       }
+               }
+       }
+
+       return tsmesh;
+
+err:
+       ts_free_tree(tsmesh);
+       return 0;
+}
+
+static struct ts_node *create_lighttree(struct goat3d *g, const struct goat3d_light *light)
+{
+       struct ts_node *tslight = 0;
+       struct ts_attr *tsa;
+
+       create_tsnode(tslight, 0, "light");
+       create_tsattr(tsa, tslight, "name", TS_STRING);
+       if(ts_set_value_str(&tsa->val, namegen(g, light->name, LIGHT)) == -1) {
+               goto err;
+       }
+
+       if(light->ltype != LTYPE_DIR) {
+               create_tsattr(tsa, tslight, "pos", TS_VECTOR);
+               ts_set_valuefv(&tsa->val, 3, light->pos.x, light->pos.y, light->pos.z);
+       }
+
+       if(light->ltype != LTYPE_POINT) {
+               create_tsattr(tsa, tslight, "dir", TS_VECTOR);
+               ts_set_valuefv(&tsa->val, 3, light->dir.x, light->dir.y, light->dir.z);
+       }
+
+       if(light->ltype == LTYPE_SPOT) {
+               create_tsattr(tsa, tslight, "cone_inner", TS_NUMBER);
+               ts_set_valuef(&tsa->val, light->inner_cone);
+               create_tsattr(tsa, tslight, "cone_outer", TS_NUMBER);
+               ts_set_valuef(&tsa->val, light->outer_cone);
+       }
+
+       create_tsattr(tsa, tslight, "color", TS_VECTOR);
+       ts_set_valuefv(&tsa->val, 3, light->color.x, light->color.y, light->color.z);
+
+       create_tsattr(tsa, tslight, "atten", TS_VECTOR);
+       ts_set_valuefv(&tsa->val, 3, light->attenuation.x, light->attenuation.y, light->attenuation.z);
+
+       create_tsattr(tsa, tslight, "distance", TS_NUMBER);
+       ts_set_valuef(&tsa->val, light->max_dist);
+
+       return tslight;
+
+err:
+       ts_free_tree(tslight);
+       return 0;
+}
+
+static struct ts_node *create_camtree(struct goat3d *g, const struct goat3d_camera *cam)
+{
+       struct ts_node *tscam = 0;
+       struct ts_attr *tsa;
+
+       create_tsnode(tscam, 0, "camera");
+       create_tsattr(tsa, tscam, "name", TS_STRING);
+       if(ts_set_value_str(&tsa->val, namegen(g, cam->name, CAMERA)) == -1) {
+               goto err;
+       }
+
+       create_tsattr(tsa, tscam, "pos", TS_VECTOR);
+       ts_set_valuefv(&tsa->val, 3, cam->pos.x, cam->pos.y, cam->pos.z);
+
+       if(cam->camtype == CAMTYPE_TARGET) {
+               create_tsattr(tsa, tscam, "target", TS_VECTOR);
+               ts_set_valuefv(&tsa->val, 3, cam->target.x, cam->target.y, cam->target.z);
+       }
+
+       create_tsattr(tsa, tscam, "fov", TS_NUMBER);
+       ts_set_valuef(&tsa->val, cam->fov);
+
+       create_tsattr(tsa, tscam, "nearclip", TS_NUMBER);
+       ts_set_valuef(&tsa->val, cam->near_clip);
+
+       create_tsattr(tsa, tscam, "farclip", TS_NUMBER);
+       ts_set_valuef(&tsa->val, cam->far_clip);
+
+       return tscam;
+
+err:
+       ts_free_tree(tscam);
+       return 0;
+}
+
+static struct ts_node *create_nodetree(struct goat3d *g, const struct goat3d_node *node)
+{
+       struct ts_node *tsnode = 0;
+       struct ts_attr *tsa;
+       struct goat3d_node *par;
+       static const char *objtypestr[] = {"null", "mesh", "light", "camera"};
+       float vec[4];
+       float xform[16];
+
+       create_tsnode(tsnode, 0, "node");
+       create_tsattr(tsa, tsnode, "name", TS_STRING);
+       if(ts_set_value_str(&tsa->val, namegen(g, node->name, NODE)) == -1) {
+               goto err;
+       }
+
+       if((par = goat3d_get_node_parent(node))) {
+               create_tsattr(tsa, tsnode, "parent", TS_STRING);
+               if(ts_set_value_str(&tsa->val, goat3d_get_node_name(par)) == -1) {
+                       goto err;
+               }
+       }
+
+       if(node->obj && node->type != GOAT3D_NODE_NULL) {
+               create_tsattr(tsa, tsnode, objtypestr[node->type], TS_STRING);
+               if(ts_set_value_str(&tsa->val, ((struct object*)node->obj)->name) == -1) {
+                       goto err;
+               }
+       }
+
+       goat3d_get_node_position(node, vec, vec + 1, vec + 2);
+       create_tsattr(tsa, tsnode, "pos", TS_VECTOR);
+       ts_set_valuef_arr(&tsa->val, 3, vec);
+
+       goat3d_get_node_rotation(node, vec, vec + 1, vec + 2, vec + 3);
+       create_tsattr(tsa, tsnode, "rot", TS_VECTOR);
+       ts_set_valuef_arr(&tsa->val, 4, vec);
+
+       goat3d_get_node_scaling(node, vec, vec + 1, vec + 2);
+       create_tsattr(tsa, tsnode, "scale", TS_VECTOR);
+       ts_set_valuef_arr(&tsa->val, 3, vec);
+
+       goat3d_get_node_pivot(node, vec, vec + 1, vec + 2);
+       create_tsattr(tsa, tsnode, "pivot", TS_VECTOR);
+       ts_set_valuef_arr(&tsa->val, 3, vec);
+
+       goat3d_get_node_matrix(node, xform);
+       cgm_mtranspose(xform);
+       create_tsattr(tsa, tsnode, "matrix0", TS_VECTOR);
+       ts_set_valuef_arr(&tsa->val, 4, xform);
+       create_tsattr(tsa, tsnode, "matrix1", TS_VECTOR);
+       ts_set_valuef_arr(&tsa->val, 4, xform + 4);
+       create_tsattr(tsa, tsnode, "matrix2", TS_VECTOR);
+       ts_set_valuef_arr(&tsa->val, 4, xform + 8);
+
+       return tsnode;
+
+err:
+       ts_free_tree(tsnode);
+       return 0;
+}
+
+static struct ts_node *create_animtree(struct goat3d *g, const struct goat3d_anim *anim)
+{
+       int i, num_trk;
+       struct ts_node *tsanim, *tstrk;
+       struct ts_attr *tsa;
+
+       create_tsnode(tsanim, 0, "anim");
+       create_tsattr(tsa, tsanim, "name", TS_STRING);
+       if(ts_set_value_str(&tsa->val, namegen(g, anim->name, ANIM)) == -1) {
+               goto err;
+       }
+
+
+       num_trk = goat3d_get_anim_track_count(anim);
+       for(i=0; i<num_trk; i++) {
+               if((tstrk = create_tracktree(g, goat3d_get_anim_track(anim, i)))) {
+                       ts_add_child(tsanim, tstrk);
+               }
+       }
+
+       return tsanim;
+
+err:
+       ts_free_tree(tsanim);
+       return 0;
+}
+
+static const char *instr[] = {"step", "linear", "cubic"};
+static const char *exstr[] = {"extend", "clamp", "repeat", "pingpong"};
+
+static struct ts_node *create_tracktree(struct goat3d *g, const struct goat3d_track *trk)
+{
+       int i, num_keys;
+       struct ts_node *tstrk, *tskey;
+       struct ts_attr *tsa;
+       struct goat3d_key key;
+       enum goat3d_track_type basetype;
+
+       create_tsnode(tstrk, 0, "track");
+       create_tsattr(tsa, tstrk, "name", TS_STRING);
+       if(ts_set_value_str(&tsa->val, namegen(g, trk->name, TRACK)) == -1) {
+               goto err;
+       }
+
+       create_tsattr(tsa, tstrk, "type", TS_STRING);
+       if(ts_set_value_str(&tsa->val, g3dimpl_trktypestr(trk->type)) == -1) {
+               goto err;
+       }
+       basetype = trk->type & 0xff;
+
+       create_tsattr(tsa, tstrk, "interp", TS_STRING);
+       if(ts_set_value_str(&tsa->val, instr[trk->trk[0].interp]) == -1) {
+               goto err;
+       }
+       create_tsattr(tsa, tstrk, "extrap", TS_STRING);
+       if(ts_set_value_str(&tsa->val, exstr[trk->trk[0].extrap]) == -1) {
+               goto err;
+       }
+
+       if(trk->node) {
+               create_tsattr(tsa, tstrk, "node", TS_STRING);
+               if(ts_set_value_str(&tsa->val, trk->node->name) == -1) {
+                       goto err;
+               }
+       }
+
+       num_keys = goat3d_get_track_key_count(trk);
+       for(i=0; i<num_keys; i++) {
+               goat3d_get_track_key(trk, i, &key);
+
+               create_tsnode(tskey, tstrk, "key");
+               create_tsattr(tsa, tskey, "time", TS_NUMBER);
+               ts_set_valuei(&tsa->val, key.tm);
+
+               if(basetype == GOAT3D_TRACK_VAL) {
+                       create_tsattr(tsa, tskey, "value", TS_NUMBER);
+                       ts_set_valuef(&tsa->val, key.val[0]);
+               } else {
+                       static const int typecount[] = {1, 3, 4, 4};
+                       create_tsattr(tsa, tskey, "value", TS_VECTOR);
+                       ts_set_valuef_arr(&tsa->val, typecount[basetype], key.val);
+               }
+       }
+
+       return tstrk;
+
+err:
+       ts_free_tree(tstrk);
+       return 0;
+}
+
+
+
+static void init_namegen(struct goat3d *g)
+{
+       memset(g->namecnt, 0, sizeof g->namecnt);
+}
+
+static const char *namegen(struct goat3d *g, const char *name, int type)
+{
+       static const char *fmt[] = {"material%03u", "mesh%03u", "light%03u",
+               "camera%03u", "node%03u", "animation%03u", "track%03u"};
+
+       if(name) {
+               /* if an actual name happens to match our pattern, make sure to skip it
+                * for the auto-generated names
+                */
+               int n;
+               if(sscanf(name, fmt[type], &n)) {
+                       g->namecnt[type] = n;
+               }
+               return name;
+       }
+
+       sprintf(g->namebuf, fmt[type], g->namecnt[type]++);
+       return g->namebuf;
+}
+
+static const char *enctab =
+       "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+       "abcdefghijklmnopqrstuvwxyz"
+       "0123456789+/";
+
+GOAT3DAPI char *goat3d_b64encode(const void *data, int size, char *buf, int *bufsz)
+{
+       const unsigned char *src = data;
+       char *dest;
+       int i;
+       int outsz = size * 4 / 3 + 1;
+       int bitgrp[4];
+       int leftover;
+
+       if(buf) {
+               if(*bufsz < outsz) {
+                       *bufsz = outsz;
+                       return 0;
+               }
+       } else {
+               if(bufsz) *bufsz = outsz;
+
+               /* reserve more space for up to two padding bytes */
+               if(!(buf = malloc(outsz + 2))) {
+                       return 0;
+               }
+       }
+       dest = buf;
+
+       leftover = 0;
+       while(size > 0) {
+               bitgrp[0] = (src[0] & 0xfc) >> 2;
+               bitgrp[1] = (src[0] & 3) << 4;
+               if(--size <= 0) {
+                       leftover = 2;
+                       break;
+               }
+               bitgrp[1] |= src[1] >> 4;
+               bitgrp[2] = (src[1] & 0xf) << 2;
+               if(--size <= 0) {
+                       leftover = 3;
+                       break;
+               }
+               bitgrp[2] |= src[2] >> 6;
+               bitgrp[3] = src[2] & 0x3f;
+
+               dest[0] = enctab[bitgrp[0]];
+               dest[1] = enctab[bitgrp[1]];
+               dest[2] = enctab[bitgrp[2]];
+               dest[3] = enctab[bitgrp[3]];
+
+               dest += 4;
+               src += 3;
+               leftover = 0;
+               size--;
+       }
+
+       if(leftover) {
+               for(i=0; i<4; i++) {
+                       if(i < leftover) {
+                               *dest++ = enctab[bitgrp[i]];
+                       } else {
+                               *dest++ = '=';
+                       }
+               }
+       }
+
+       *dest = 0;
+       return buf;
+}
index 06b91dc..5e91e5b 100644 (file)
@@ -20,14 +20,17 @@ jobj = jpeglib/jcapimin.o jpeglib/jcapistd.o jpeglib/jccoefct.o jpeglib/jccolor.
           jpeglib/jfdctint.o jpeglib/jidctflt.o jpeglib/jidctfst.o jpeglib/jidctint.o \
           jpeglib/jidctred.o jpeglib/jmemmgr.o jpeglib/jmemnobs.o jpeglib/jquant1.o \
           jpeglib/jquant2.o jpeglib/jutils.o
-src = $(mobj) $(zobj) $(pobj) $(jobj)
-alib = ../unix/libimago.a
+obj = $(mobj) $(zobj) $(pobj) $(jobj)
+alib = ../unix/imago.a
 
 CFLAGS = -O3 -Izlib -Ilibpng -Ijpeglib $(pic)
 
 $(alib): $(obj)
        $(AR) rcs $@ $(obj)
 
+.c.o:
+       $(CC) $(CFLAGS) -c $< -o $@
+
 .PHONY: clean
 clean:
        rm -f $(obj) $(alib)
diff --git a/libs/treestor/LICENSE b/libs/treestor/LICENSE
new file mode 100644 (file)
index 0000000..536e666
--- /dev/null
@@ -0,0 +1,20 @@
+Copyright (C) 2016 John Tsiombikas <nuclear@member.fsf.org>
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/libs/treestor/Makefile b/libs/treestor/Makefile
new file mode 100644 (file)
index 0000000..6c2ea75
--- /dev/null
@@ -0,0 +1,14 @@
+obj = src/treestor.o src/text.o src/dynarr.o
+alib = ../unix/treestor.a
+
+CFLAGS = -O3 -Iinclude
+
+$(alib): $(obj)
+       $(AR) rcs $@ $(obj)
+
+.c.o:
+       $(CC) $(CFLAGS) -c $< -o $@
+
+.PHONY: clean
+clean:
+       rm -f $(obj) $(alib)
diff --git a/libs/treestor/README.md b/libs/treestor/README.md
new file mode 100644 (file)
index 0000000..f75efa9
--- /dev/null
@@ -0,0 +1,38 @@
+libtreestore
+============
+
+Libtreestore is a simple C library for reading/writing hierarchical data in a
+json-like text format, or a chunk-based binary format.
+
+A better way to describe the text format is like XML without the CDATA, and with
+curly braces instead of tags:
+
+```
+rootnode {
+    some_attribute = "some_string_value"
+    some_numeric_attrib = 10
+    vector_attrib = [255, 128, 0]
+    array_attrib = ["tom", "dick", "harry"]
+
+    # you can have multiple nodes with the same name
+    childnode {
+        childattr = "whatever"
+    }
+    childnode {
+        another_childattr = "xyzzy"
+    }
+}
+```
+
+License
+-------
+Copyright (C) 2016-2019 John Tsiombikas <nuclear@member.fsf.org>
+
+Libtreestore is free software. Feel free to use, modify, and/or redistribute
+it, under the terms of the MIT/X11 license. See LICENSE for detauls.
+
+Issues
+------
+At the moment only the text format has been implemented.
+
+More info soon...
diff --git a/libs/treestor/include/treestor.h b/libs/treestor/include/treestor.h
new file mode 100644 (file)
index 0000000..842a489
--- /dev/null
@@ -0,0 +1,167 @@
+#ifndef TREESTORE_H_
+#define TREESTORE_H_
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+
+#ifdef __cplusplus
+#define TS_DEFVAL(x) =(x)
+extern "C" {
+#else
+#define TS_DEFVAL(x)
+#endif
+
+/** set of user-supplied I/O functions, for ts_load_io/ts_save_io */
+struct ts_io {
+       void *data;
+
+       long (*read)(void *buf, size_t bytes, void *uptr);
+       long (*write)(const void *buf, size_t bytes, void *uptr);
+};
+
+enum ts_value_type { TS_STRING, TS_NUMBER, TS_VECTOR, TS_ARRAY };
+
+/** treestore node attribute value */
+struct ts_value {
+       enum ts_value_type type;
+
+       char *str;              /**< string values will have this set */
+       int inum;               /**< numeric values will have this set */
+       float fnum;             /**< numeric values will have this set */
+
+       /** vector values (arrays containing ONLY numbers) will have this set */
+       float *vec;             /**< elements of the vector */
+       int vec_size;   /**< size of the vector (in elements), same as array_size */
+
+       /** array values (including vectors) will have this set */
+       struct ts_value *array; /**< elements of the array */
+       int array_size;                 /**< size of the array (in elements) */
+};
+
+int ts_init_value(struct ts_value *tsv);
+void ts_destroy_value(struct ts_value *tsv);
+
+struct ts_value *ts_alloc_value(void);         /**< also calls ts_init_value */
+void ts_free_value(struct ts_value *tsv);      /**< also calls ts_destroy_value */
+
+/** perform a deep-copy of a ts_value */
+int ts_copy_value(struct ts_value *dest, struct ts_value *src);
+
+/** set a ts_value as a string */
+int ts_set_value_str(struct ts_value *tsv, const char *str);
+
+/** set a ts_value from a list of integers */
+int ts_set_valuei_arr(struct ts_value *tsv, int count, const int *arr);
+int ts_set_valueiv(struct ts_value *tsv, int count, ...);
+int ts_set_valueiv_va(struct ts_value *tsv, int count, va_list ap);
+int ts_set_valuei(struct ts_value *tsv, int inum);     /**< equiv: ts_set_valueiv(val, 1, inum) */
+
+/** set a ts_value from a list of floats */
+int ts_set_valuef_arr(struct ts_value *tsv, int count, const float *arr);
+int ts_set_valuefv(struct ts_value *tsv, int count, ...);
+int ts_set_valuefv_va(struct ts_value *tsv, int count, va_list ap);
+int ts_set_valuef(struct ts_value *tsv, float fnum);   /**< equiv: ts_set_valuefv(val, 1, fnum) */
+
+/** set a ts_value from a list of ts_value pointers. they are deep-copied as per ts_copy_value */
+int ts_set_value_arr(struct ts_value *tsv, int count, const struct ts_value *arr);
+int ts_set_valuev(struct ts_value *tsv, int count, ...);
+int ts_set_valuev_va(struct ts_value *tsv, int count, va_list ap);
+
+
+/** treestore node attribute */
+struct ts_attr {
+       char *name;
+       struct ts_value val;
+
+       struct ts_attr *next;
+};
+
+int ts_init_attr(struct ts_attr *attr);
+void ts_destroy_attr(struct ts_attr *attr);
+
+struct ts_attr *ts_alloc_attr(void);           /**< also calls ts_init_attr */
+void ts_free_attr(struct ts_attr *attr);       /**< also calls ts_destroy_attr */
+
+/** perform a deep-copy of a ts_attr */
+int ts_copy_attr(struct ts_attr *dest, struct ts_attr *src);
+
+int ts_set_attr_name(struct ts_attr *attr, const char *name);
+
+
+
+/** treestore node */
+struct ts_node {
+       char *name;
+
+       int attr_count;
+       struct ts_attr *attr_list, *attr_tail;
+
+       int child_count;
+       struct ts_node *child_list, *child_tail;
+       struct ts_node *parent;
+
+       struct ts_node *next;   /* next sibling */
+};
+
+int ts_init_node(struct ts_node *node);
+void ts_destroy_node(struct ts_node *node);
+
+struct ts_node *ts_alloc_node(void);   /**< also calls ts_init_node */
+void ts_free_node(struct ts_node *n);  /**< also calls ts_destroy_node */
+
+/** recursively destroy all the nodes of the tree */
+void ts_free_tree(struct ts_node *tree);
+
+int ts_set_node_name(struct ts_node *node, const char *name);
+
+void ts_add_attr(struct ts_node *node, struct ts_attr *attr);
+struct ts_attr *ts_get_attr(struct ts_node *node, const char *name);
+
+const char *ts_get_attr_str(struct ts_node *node, const char *aname,
+               const char *def_val TS_DEFVAL(0));
+float ts_get_attr_num(struct ts_node *node, const char *aname,
+               float def_val TS_DEFVAL(0.0f));
+int ts_get_attr_int(struct ts_node *node, const char *aname,
+               int def_val TS_DEFVAL(0.0f));
+float *ts_get_attr_vec(struct ts_node *node, const char *aname,
+               float *def_val TS_DEFVAL(0));
+struct ts_value *ts_get_attr_array(struct ts_node *node, const char *aname,
+               struct ts_value *def_val TS_DEFVAL(0));
+
+
+void ts_add_child(struct ts_node *node, struct ts_node *child);
+int ts_remove_child(struct ts_node *node, struct ts_node *child);
+struct ts_node *ts_get_child(struct ts_node *node, const char *name);
+
+/* load/save by opening the specified file */
+struct ts_node *ts_load(const char *fname);
+int ts_save(struct ts_node *tree, const char *fname);
+
+/* load/save using the supplied FILE pointer */
+struct ts_node *ts_load_file(FILE *fp);
+int ts_save_file(struct ts_node *tree, FILE *fp);
+
+/* load/save using custom I/O functions */
+struct ts_node *ts_load_io(struct ts_io *io);
+int ts_save_io(struct ts_node *tree, struct ts_io *io);
+
+
+struct ts_attr *ts_lookup(struct ts_node *root, const char *path);
+const char *ts_lookup_str(struct ts_node *root, const char *path,
+               const char *def_val TS_DEFVAL(0));
+float ts_lookup_num(struct ts_node *root, const char *path,
+               float def_val TS_DEFVAL(0.0f));
+int ts_lookup_int(struct ts_node *root, const char *path,
+               int def_val TS_DEFVAL(0));
+float *ts_lookup_vec(struct ts_node *root, const char *path,
+               float *def_val TS_DEFVAL(0));
+struct ts_value *ts_lookup_array(struct ts_node *root, const char *path,
+               struct ts_value *def_val TS_DEFVAL(0));
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* TREESTORE_H_ */
diff --git a/libs/treestor/src/dynarr.c b/libs/treestor/src/dynarr.c
new file mode 100644 (file)
index 0000000..2d3f611
--- /dev/null
@@ -0,0 +1,133 @@
+/* dynarr - dynamic resizable C array data structure
+ * author: John Tsiombikas <nuclear@member.fsf.org>
+ * license: public domain
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "dynarr.h"
+
+/* The array descriptor keeps auxilliary information needed to manipulate
+ * the dynamic array. It's allocated adjacent to the array buffer.
+ */
+struct arrdesc {
+       int nelem, szelem;
+       int max_elem;
+       int bufsz;      /* not including the descriptor */
+};
+
+#define DESC(x)                ((struct arrdesc*)((char*)(x) - sizeof(struct arrdesc)))
+
+void *ts_dynarr_alloc(int elem, int szelem)
+{
+       struct arrdesc *desc;
+
+       if(!(desc = malloc(elem * szelem + sizeof *desc))) {
+               return 0;
+       }
+       desc->nelem = desc->max_elem = elem;
+       desc->szelem = szelem;
+       desc->bufsz = elem * szelem;
+       return (char*)desc + sizeof *desc;
+}
+
+void ts_dynarr_free(void *da)
+{
+       if(da) {
+               free(DESC(da));
+       }
+}
+
+void *ts_dynarr_resize(void *da, int elem)
+{
+       int newsz;
+       void *tmp;
+       struct arrdesc *desc;
+
+       if(!da) return 0;
+       desc = DESC(da);
+
+       newsz = desc->szelem * elem;
+
+       if(!(tmp = realloc(desc, newsz + sizeof *desc))) {
+               return 0;
+       }
+       desc = tmp;
+
+       desc->nelem = desc->max_elem = elem;
+       desc->bufsz = newsz;
+       return (char*)desc + sizeof *desc;
+}
+
+int ts_dynarr_empty(void *da)
+{
+       return DESC(da)->nelem ? 0 : 1;
+}
+
+int ts_dynarr_size(void *da)
+{
+       return DESC(da)->nelem;
+}
+
+
+void *ts_dynarr_clear(void *da)
+{
+       return ts_dynarr_resize(da, 0);
+}
+
+/* stack semantics */
+void *ts_dynarr_push(void *da, void *item)
+{
+       struct arrdesc *desc;
+       int nelem;
+
+       desc = DESC(da);
+       nelem = desc->nelem;
+
+       if(nelem >= desc->max_elem) {
+               /* need to resize */
+               struct arrdesc *tmp;
+               int newsz = desc->max_elem ? desc->max_elem * 2 : 1;
+
+               if(!(tmp = ts_dynarr_resize(da, newsz))) {
+                       fprintf(stderr, "failed to resize\n");
+                       return da;
+               }
+               da = tmp;
+               desc = DESC(da);
+               desc->nelem = nelem;
+       }
+
+       if(item) {
+               memcpy((char*)da + desc->nelem++ * desc->szelem, item, desc->szelem);
+       }
+       return da;
+}
+
+void *ts_dynarr_pop(void *da)
+{
+       struct arrdesc *desc;
+       int nelem;
+
+       desc = DESC(da);
+       nelem = desc->nelem;
+
+       if(!nelem) return da;
+
+       if(nelem <= desc->max_elem / 3) {
+               /* reclaim space */
+               struct arrdesc *tmp;
+               int newsz = desc->max_elem / 2;
+
+               if(!(tmp = ts_dynarr_resize(da, newsz))) {
+                       fprintf(stderr, "failed to resize\n");
+                       return da;
+               }
+               da = tmp;
+               desc = DESC(da);
+               desc->nelem = nelem;
+       }
+       desc->nelem--;
+
+       return da;
+}
diff --git a/libs/treestor/src/dynarr.h b/libs/treestor/src/dynarr.h
new file mode 100644 (file)
index 0000000..513a431
--- /dev/null
@@ -0,0 +1,69 @@
+/* dynarr - dynamic resizable C array data structure
+ * author: John Tsiombikas <nuclear@member.fsf.org>
+ * license: public domain
+ */
+#ifndef DYNARR_H_
+#define DYNARR_H_
+
+/* usage example:
+ * -------------
+ * int *arr = ts_dynarr_alloc(0, sizeof *arr);
+ *
+ * int x = 10;
+ * arr = ts_dynarr_push(arr, &x);
+ * x = 5;
+ * arr = ts_dynarr_push(arr, &x);
+ * x = 42;
+ * arr = ts_dynarr_push(arr, &x);
+ *
+ * for(i=0; i<ts_dynarr_size(arr); i++) {
+ *     printf("%d\n", arr[i]);
+ *  }
+ *  ts_dynarr_free(arr);
+ */
+
+void *ts_dynarr_alloc(int elem, int szelem);
+void ts_dynarr_free(void *da);
+void *ts_dynarr_resize(void *da, int elem);
+
+int ts_dynarr_empty(void *da);
+int ts_dynarr_size(void *da);
+
+void *ts_dynarr_clear(void *da);
+
+/* stack semantics */
+void *ts_dynarr_push(void *da, void *item);
+void *ts_dynarr_pop(void *da);
+
+
+/* helper macros */
+#define DYNARR_RESIZE(da, n) \
+       do { (da) = ts_dynarr_resize((da), (n)); } while(0)
+#define DYNARR_CLEAR(da) \
+       do { (da) = ts_dynarr_clear(da); } while(0)
+#define DYNARR_PUSH(da, item) \
+       do { (da) = ts_dynarr_push((da), (item)); } while(0)
+#define DYNARR_POP(da) \
+       do { (da) = ts_dynarr_pop(da); } while(0)
+
+/* utility macros to push characters to a string. assumes and maintains
+ * the invariant that the last element is always a zero
+ */
+#define DYNARR_STRPUSH(da, c) \
+       do { \
+               char cnull = 0, ch = (char)(c); \
+               (da) = ts_dynarr_pop(da); \
+               (da) = ts_dynarr_push((da), &ch); \
+               (da) = ts_dynarr_push((da), &cnull); \
+       } while(0)
+
+#define DYNARR_STRPOP(da) \
+       do { \
+               char cnull = 0; \
+               (da) = ts_dynarr_pop(da); \
+               (da) = ts_dynarr_pop(da); \
+               (da) = ts_dynarr_push((da), &cnull); \
+       } while(0)
+
+
+#endif /* DYNARR_H_ */
diff --git a/libs/treestor/src/text.c b/libs/treestor/src/text.c
new file mode 100644 (file)
index 0000000..fa611fb
--- /dev/null
@@ -0,0 +1,465 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <assert.h>
+#include "treestor.h"
+#include "dynarr.h"
+
+struct parser {
+       struct ts_io *io;
+       int nline;
+       char *token;
+       int nextc;
+};
+
+enum { TOK_SYM, TOK_ID, TOK_NUM, TOK_STR };
+
+static struct ts_node *read_node(struct parser *pstate);
+static int read_array(struct parser *pstate, struct ts_value *tsv, char endsym);
+static int next_token(struct parser *pstate);
+
+static int print_attr(struct ts_attr *attr, struct ts_io *io, int level);
+static char *value_to_str(struct ts_value *value);
+static int tree_level(struct ts_node *n);
+static const char *indent(int x);
+static const char *toktypestr(int type);
+
+#define EXPECT(type) \
+       do { \
+               if(next_token(pst) != (type)) { \
+                       fprintf(stderr, "expected %s token\n", toktypestr(type)); \
+                       goto err; \
+               } \
+       } while(0)
+
+#define EXPECT_SYM(c) \
+       do { \
+               if(next_token(pst) != TOK_SYM || pst->token[0] != (c)) { \
+                       fprintf(stderr, "expected symbol: %c\n", c); \
+                       goto err; \
+               } \
+       } while(0)
+
+
+struct ts_node *ts_text_load(struct ts_io *io)
+{
+       char *root_name;
+       struct parser pstate, *pst = &pstate;
+       struct ts_node *node = 0;
+
+       pstate.io = io;
+       pstate.nline = 0;
+       pstate.nextc = -1;
+       if(!(pstate.token = ts_dynarr_alloc(0, 1))) {
+               perror("failed to allocate token string");
+               return 0;
+       }
+
+       EXPECT(TOK_ID);
+       if(!(root_name = strdup(pst->token))) {
+               perror("failed to allocate root node name");
+               ts_dynarr_free(pst->token);
+               return 0;
+       }
+       EXPECT_SYM('{');
+       if(!(node = read_node(pst))) {
+               ts_dynarr_free(pst->token);
+               return 0;
+       }
+       node->name = root_name;
+
+err:
+       ts_dynarr_free(pst->token);
+       return node;
+}
+
+static int read_value(struct parser *pst, int toktype, struct ts_value *val)
+{
+       switch(toktype) {
+       case TOK_NUM:
+               ts_set_valuef(val, atof(pst->token));
+               break;
+
+       case TOK_SYM:
+               if(pst->token[0] == '[' || pst->token[0] == '{') {
+                       char endsym = pst->token[0] + 2; /* end symbol is dist 2 from either '[' or '{' */
+                       if(read_array(pst, val, endsym) == -1) {
+                               return -1;
+                       }
+               } else {
+                       fprintf(stderr, "read_node: unexpected rhs symbol: %c\n", pst->token[0]);
+               }
+               break;
+
+       case TOK_ID:
+       case TOK_STR:
+       default:
+               ts_set_value_str(val, pst->token);
+       }
+
+       return 0;
+}
+
+static struct ts_node *read_node(struct parser *pst)
+{
+       int type;
+       struct ts_node *node;
+
+       if(!(node = ts_alloc_node())) {
+               perror("failed to allocate treestore node");
+               return 0;
+       }
+
+       while((type = next_token(pst)) == TOK_ID) {
+               char *id;
+
+               if(!(id = strdup(pst->token))) {
+                       goto err;
+               }
+
+               EXPECT(TOK_SYM);
+
+               if(pst->token[0] == '=') {
+                       /* attribute */
+                       struct ts_attr *attr;
+                       int type;
+
+                       if(!(attr = ts_alloc_attr())) {
+                               goto err;
+                       }
+
+                       if((type = next_token(pst)) == -1) {
+                               ts_free_attr(attr);
+                               fprintf(stderr, "read_node: unexpected EOF\n");
+                               goto err;
+                       }
+
+                       if(read_value(pst, type, &attr->val) == -1) {
+                               ts_free_attr(attr);
+                               fprintf(stderr, "failed to read value\n");
+                               goto err;
+                       }
+                       attr->name = id;
+                       ts_add_attr(node, attr);
+
+               } else if(pst->token[0] == '{') {
+                       /* child */
+                       struct ts_node *child;
+
+                       if(!(child = read_node(pst))) {
+                               ts_free_node(node);
+                               return 0;
+                       }
+
+                       child->name = id;
+                       ts_add_child(node, child);
+
+               } else {
+                       fprintf(stderr, "unexpected token: %s\n", pst->token);
+                       goto err;
+               }
+       }
+
+       if(type != TOK_SYM || pst->token[0] != '}') {
+               fprintf(stderr, "expected closing brace\n");
+               goto err;
+       }
+       return node;
+
+err:
+       fprintf(stderr, "treestore read_node failed\n");
+       ts_free_node(node);
+       return 0;
+}
+
+static int read_array(struct parser *pst, struct ts_value *tsv, char endsym)
+{
+       int type;
+       struct ts_value values[32];
+       int i, nval = 0;
+       int res;
+
+       while((type = next_token(pst)) != -1) {
+               ts_init_value(values + nval);
+               if(read_value(pst, type, values + nval) == -1) {
+                       return -1;
+               }
+               if(nval < 31) {
+                       ++nval;
+               } else {
+                       ts_destroy_value(values + nval);
+               }
+
+               type = next_token(pst);
+               if(!(type == TOK_SYM && (pst->token[0] == ',' || pst->token[0] == endsym))) {
+                       fprintf(stderr, "read_array: expected comma or end symbol ('%c')\n", endsym);
+                       return -1;
+               }
+               if(pst->token[0] == endsym) {
+                       break;  /* we're done */
+               }
+       }
+
+       if(!nval) {
+               return -1;
+       }
+
+       res = ts_set_value_arr(tsv, nval, values);
+
+       for(i=0; i<nval; i++) {
+               ts_destroy_value(values + i);
+       }
+       return res;
+}
+
+static int nextchar(struct parser *pst)
+{
+       char c;
+
+       if(pst->nextc >= 0) {
+               c = pst->nextc;
+               pst->nextc = -1;
+       } else {
+               if(pst->io->read(&c, 1, pst->io->data) < 1) {
+                       return -1;
+               }
+       }
+       return c;
+}
+
+static void ungetchar(char c, struct parser *pst)
+{
+       assert(pst->nextc == -1);
+       pst->nextc = c;
+}
+
+static int next_token(struct parser *pst)
+{
+       int c;
+
+       DYNARR_CLEAR(pst->token);
+
+       /* skip whitespace */
+       while((c = nextchar(pst)) != -1) {
+               if(c == '#') { /* skip to end of line */
+                       while((c = nextchar(pst)) != -1 && c != '\n');
+                       if(c == -1) return -1;
+               }
+               if(!isspace(c)) break;
+               if(c == '\n') ++pst->nline;
+       }
+       if(c == -1) return -1;
+
+       DYNARR_STRPUSH(pst->token, c);
+
+       if(isdigit(c) || c == '-' || c == '+') {
+               /* token is a number */
+               int found_dot = 0;
+               while((c = nextchar(pst)) != -1 &&
+                               (isdigit(c) || (c == '.' && !found_dot))) {
+                       DYNARR_STRPUSH(pst->token, c);
+                       if(c == '.') found_dot = 1;
+               }
+               if(c != -1) ungetchar(c, pst);
+               return TOK_NUM;
+       }
+       if(isalpha(c)) {
+               /* token is an identifier */
+               while((c = nextchar(pst)) != -1 && (isalnum(c) || c == '_')) {
+                       DYNARR_STRPUSH(pst->token, c);
+               }
+               if(c != -1) ungetchar(c, pst);
+               return TOK_ID;
+       }
+       if(c == '"') {
+               /* token is a string constant, remove the opening quote */
+               DYNARR_STRPOP(pst->token);
+               while((c = nextchar(pst)) != -1 && c != '"') {
+                       DYNARR_STRPUSH(pst->token, c);
+                       if(c == '\n') ++pst->nline;
+               }
+               if(c != '"') {
+                       return -1;
+               }
+               return TOK_STR;
+       }
+       return TOK_SYM;
+}
+
+int ts_text_save(struct ts_node *tree, struct ts_io *io)
+{
+       char *buf;
+       struct ts_node *c;
+       struct ts_attr *attr;
+       int lvl = tree_level(tree);
+       int sz, inline_attr, res = -1;
+
+       if(!(buf = malloc(lvl + strlen(tree->name) + 4))) {
+               perror("ts_text_save failed to allocate buffer");
+               goto end;
+       }
+
+       if(tree->child_list || (tree->attr_list && tree->attr_list->next)) {
+               inline_attr = 0;
+       } else {
+               inline_attr = 1;
+       }
+
+       sz = sprintf(buf, "%s%s {", indent(lvl), tree->name);
+       if(!inline_attr) {
+               strcat(buf, "\n");
+               sz++;
+       }
+       if(io->write(buf, sz, io->data) < sz) {
+               goto end;
+       }
+
+       attr = tree->attr_list;
+       while(attr) {
+               if(print_attr(attr, io, inline_attr ? -1 : lvl) == -1) {
+                       goto end;
+               }
+               attr = attr->next;
+       }
+
+       c = tree->child_list;
+       while(c) {
+               if(ts_text_save(c, io) == -1) {
+                       goto end;
+               }
+               c = c->next;
+       }
+
+       if(inline_attr) {
+               sz = sprintf(buf, "}\n");
+       } else {
+               sz = sprintf(buf, "%s}\n", indent(lvl));
+       }
+       if(io->write(buf, sz, io->data) < sz) {
+               goto end;
+       }
+       res = 0;
+end:
+       free(buf);
+       return res;
+}
+
+static int print_attr(struct ts_attr *attr, struct ts_io *io, int level)
+{
+       char *buf, *val;
+       int sz;
+
+       if(!(val = value_to_str(&attr->val))) {
+               return -1;
+       }
+
+       sz = (level >= 0 ? level : 0) + strlen(attr->name) + ts_dynarr_size(val) + 5;
+       if(!(buf = malloc(sz))) {
+               perror("print_attr: failed to allocate name buffer");
+               ts_dynarr_free(val);
+       }
+
+       if(level >= 0) {
+               sz = sprintf(buf, "%s%s = %s\n", indent(level + 1), attr->name, val);
+       } else {
+               sz = sprintf(buf, " %s = %s ", attr->name, val);
+       }
+       if(io->write(buf, sz, io->data) < sz) {
+               ts_dynarr_free(val);
+               free(buf);
+               return -1;
+       }
+       ts_dynarr_free(val);
+       free(buf);
+       return 0;
+}
+
+static char *append_dynstr(char *dest, char *s)
+{
+       while(*s) {
+               DYNARR_STRPUSH(dest, *s++);
+       }
+       return dest;
+}
+
+static char *value_to_str(struct ts_value *value)
+{
+       int i;
+       char buf[128];
+       char *str, *valstr;
+
+       if(!(str = ts_dynarr_alloc(0, 1))) {
+               return 0;
+       }
+
+       switch(value->type) {
+       case TS_NUMBER:
+               sprintf(buf, "%g", value->fnum);
+               str = append_dynstr(str, buf);
+               break;
+
+       case TS_VECTOR:
+               DYNARR_STRPUSH(str, '[');
+               for(i=0; i<value->vec_size; i++) {
+                       if(i == 0) {
+                               sprintf(buf, "%g", value->vec[i]);
+                       } else {
+                               sprintf(buf, ", %g", value->vec[i]);
+                       }
+                       str = append_dynstr(str, buf);
+               }
+               DYNARR_STRPUSH(str, ']');
+               break;
+
+       case TS_ARRAY:
+               DYNARR_STRPUSH(str, '[');
+               for(i=0; i<value->array_size; i++) {
+                       if(i > 0) {
+                               str = append_dynstr(str, ", ");
+                       }
+                       if(!(valstr = value_to_str(value->array + i))) {
+                               ts_dynarr_free(str);
+                               return 0;
+                       }
+                       str = append_dynstr(str, valstr);
+                       ts_dynarr_free(valstr);
+               }
+               DYNARR_STRPUSH(str, ']');
+               break;
+
+       default:
+               sprintf(buf, "\"%s\"", value->str);
+               str = append_dynstr(str, buf);
+       }
+
+       return str;
+}
+
+static int tree_level(struct ts_node *n)
+{
+       if(!n->parent) return 0;
+       return tree_level(n->parent) + 1;
+}
+
+static const char *indent(int x)
+{
+       static const char buf[] = "\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t";
+       const char *end = buf + sizeof buf - 1;
+       return x > sizeof buf - 1 ? buf : end - x;
+}
+
+static const char *toktypestr(int type)
+{
+       switch(type) {
+       case TOK_ID:
+               return "identifier";
+       case TOK_NUM:
+               return "number";
+       case TOK_STR:
+               return "string";
+       case TOK_SYM:
+               return "symbol";
+       }
+       return "unknown";
+}
diff --git a/libs/treestor/src/treestor.c b/libs/treestor/src/treestor.c
new file mode 100644 (file)
index 0000000..57b06ed
--- /dev/null
@@ -0,0 +1,809 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <assert.h>
+#include "treestor.h"
+
+#ifdef WIN32
+#include <malloc.h>
+#else
+#include <alloca.h>
+#endif
+
+struct ts_node *ts_text_load(struct ts_io *io);
+int ts_text_save(struct ts_node *tree, struct ts_io *io);
+
+static long io_read(void *buf, size_t bytes, void *uptr);
+static long io_write(const void *buf, size_t bytes, void *uptr);
+
+
+/* ---- ts_value implementation ---- */
+
+int ts_init_value(struct ts_value *tsv)
+{
+       memset(tsv, 0, sizeof *tsv);
+       return 0;
+}
+
+void ts_destroy_value(struct ts_value *tsv)
+{
+       int i;
+
+       free(tsv->str);
+       free(tsv->vec);
+
+       for(i=0; i<tsv->array_size; i++) {
+               ts_destroy_value(tsv->array + i);
+       }
+       free(tsv->array);
+}
+
+
+struct ts_value *ts_alloc_value(void)
+{
+       struct ts_value *v = malloc(sizeof *v);
+       if(!v || ts_init_value(v) == -1) {
+               free(v);
+               return 0;
+       }
+       return v;
+}
+
+void ts_free_value(struct ts_value *tsv)
+{
+       ts_destroy_value(tsv);
+       free(tsv);
+}
+
+
+int ts_copy_value(struct ts_value *dest, struct ts_value *src)
+{
+       int i;
+
+       if(dest == src) return 0;
+
+       *dest = *src;
+
+       dest->str = 0;
+       dest->vec = 0;
+       dest->array = 0;
+
+       if(src->str) {
+               if(!(dest->str = malloc(strlen(src->str) + 1))) {
+                       goto fail;
+               }
+               strcpy(dest->str, src->str);
+       }
+       if(src->vec && src->vec_size > 0) {
+               if(!(dest->vec = malloc(src->vec_size * sizeof *src->vec))) {
+                       goto fail;
+               }
+               memcpy(dest->vec, src->vec, src->vec_size * sizeof *src->vec);
+       }
+       if(src->array && src->array_size > 0) {
+               if(!(dest->array = calloc(src->array_size, sizeof *src->array))) {
+                       goto fail;
+               }
+               for(i=0; i<src->array_size; i++) {
+                       if(ts_copy_value(dest->array + i, src->array + i) == -1) {
+                               goto fail;
+                       }
+               }
+       }
+       return 0;
+
+fail:
+       free(dest->str);
+       free(dest->vec);
+       if(dest->array) {
+               for(i=0; i<dest->array_size; i++) {
+                       ts_destroy_value(dest->array + i);
+               }
+               free(dest->array);
+       }
+       return -1;
+}
+
+#define MAKE_NUMSTR_FUNC(type, fmt) \
+       static char *make_##type##str(type x) \
+       { \
+               static char scrap[128]; \
+               char *str; \
+               int sz = snprintf(scrap, sizeof scrap, fmt, x); \
+               if(!(str = malloc(sz + 1))) return 0; \
+               sprintf(str, fmt, x); \
+               return str; \
+       }
+
+MAKE_NUMSTR_FUNC(int, "%d")
+MAKE_NUMSTR_FUNC(float, "%g")
+
+
+struct val_list_node {
+       struct ts_value val;
+       struct val_list_node *next;
+};
+
+int ts_set_value_str(struct ts_value *tsv, const char *str)
+{
+       if(tsv->str) {
+               ts_destroy_value(tsv);
+               if(ts_init_value(tsv) == -1) {
+                       return -1;
+               }
+       }
+
+       tsv->type = TS_STRING;
+       if(!(tsv->str = malloc(strlen(str) + 1))) {
+               return -1;
+       }
+       strcpy(tsv->str, str);
+
+#if 0
+       /* try to parse the string and see if it fits any of the value types */
+       if(*str == '[' || *str == '{') {
+               /* try to parse as a vector */
+               struct val_list_node *list = 0, *tail = 0, *node;
+               int nelem = 0;
+               char endsym = *str++ + 2;       /* ']' is '[' + 2 and '}' is '{' + 2 */
+
+               while(*str && *str != endsym) {
+                       float val = strtod(str, &endp);
+                       if(endp == str || !(node = malloc(sizeof *node))) {
+                               break;
+                       }
+                       ts_init_value(&node->val);
+                       ts_set_valuef(&node->val, val);
+                       node->next = 0;
+
+                       if(list) {
+                               tail->next = node;
+                               tail = node;
+                       } else {
+                               list = tail = node;
+                       }
+                       ++nelem;
+                       str = endp;
+               }
+
+               if(nelem && (tsv->array = malloc(nelem * sizeof *tsv->array)) &&
+                               (tsv->vec = malloc(nelem * sizeof *tsv->vec))) {
+                       int idx = 0;
+                       while(list) {
+                               node = list;
+                               list = list->next;
+
+                               tsv->array[idx] = node->val;
+                               tsv->vec[idx] = node->val.fnum;
+                               ++idx;
+                               free(node);
+                       }
+                       tsv->type = TS_VECTOR;
+               }
+
+       } else if((tsv->fnum = strtod(str, &endp)), endp != str) {
+               /* it's a number I guess... */
+               tsv->type = TS_NUMBER;
+       }
+#endif
+
+       return 0;
+}
+
+int ts_set_valuei_arr(struct ts_value *tsv, int count, const int *arr)
+{
+       int i;
+
+       if(count < 1) return -1;
+       if(count == 1) {
+               if(!(tsv->str = make_intstr(*arr))) {
+                       return -1;
+               }
+
+               tsv->type = TS_NUMBER;
+               tsv->fnum = (float)*arr;
+               tsv->inum = *arr;
+               return 0;
+       }
+
+       /* otherwise it's an array, we need to create the ts_value array, and
+        * the simplified vector
+        */
+       if(!(tsv->vec = malloc(count * sizeof *tsv->vec))) {
+               return -1;
+       }
+       tsv->vec_size = count;
+
+       for(i=0; i<count; i++) {
+               tsv->vec[i] = arr[i];
+       }
+
+       if(!(tsv->array = malloc(count * sizeof *tsv->array))) {
+               free(tsv->vec);
+       }
+       tsv->array_size = count;
+
+       for(i=0; i<count; i++) {
+               ts_init_value(tsv->array + i);
+               ts_set_valuef(tsv->array + i, arr[i]);
+       }
+
+       tsv->type = TS_VECTOR;
+       return 0;
+}
+
+int ts_set_valueiv(struct ts_value *tsv, int count, ...)
+{
+       int res;
+       va_list ap;
+       va_start(ap, count);
+       res = ts_set_valueiv_va(tsv, count, ap);
+       va_end(ap);
+       return res;
+}
+
+int ts_set_valueiv_va(struct ts_value *tsv, int count, va_list ap)
+{
+       int i, *vec;
+
+       if(count < 1) return -1;
+       if(count == 1) {
+               int num = va_arg(ap, int);
+               ts_set_valuei(tsv, num);
+               return 0;
+       }
+
+       vec = alloca(count * sizeof *vec);
+       for(i=0; i<count; i++) {
+               vec[i] = va_arg(ap, int);
+       }
+       return ts_set_valuei_arr(tsv, count, vec);
+}
+
+int ts_set_valuei(struct ts_value *tsv, int inum)
+{
+       return ts_set_valuei_arr(tsv, 1, &inum);
+}
+
+int ts_set_valuef_arr(struct ts_value *tsv, int count, const float *arr)
+{
+       int i;
+
+       if(count < 1) return -1;
+       if(count == 1) {
+               if(!(tsv->str = make_floatstr(*arr))) {
+                       return -1;
+               }
+
+               tsv->type = TS_NUMBER;
+               tsv->fnum = *arr;
+               tsv->inum = (int)*arr;
+               return 0;
+       }
+
+       /* otherwise it's an array, we need to create the ts_value array, and
+        * the simplified vector
+        */
+       if(!(tsv->vec = malloc(count * sizeof *tsv->vec))) {
+               return -1;
+       }
+       tsv->vec_size = count;
+
+       for(i=0; i<count; i++) {
+               tsv->vec[i] = arr[i];
+       }
+
+       if(!(tsv->array = malloc(count * sizeof *tsv->array))) {
+               free(tsv->vec);
+       }
+       tsv->array_size = count;
+
+       for(i=0; i<count; i++) {
+               ts_init_value(tsv->array + i);
+               ts_set_valuef(tsv->array + i, arr[i]);
+       }
+
+       tsv->type = TS_VECTOR;
+       return 0;
+}
+
+int ts_set_valuefv(struct ts_value *tsv, int count, ...)
+{
+       int res;
+       va_list ap;
+       va_start(ap, count);
+       res = ts_set_valuefv_va(tsv, count, ap);
+       va_end(ap);
+       return res;
+}
+
+int ts_set_valuefv_va(struct ts_value *tsv, int count, va_list ap)
+{
+       int i;
+       float *vec;
+
+       if(count < 1) return -1;
+       if(count == 1) {
+               float num = va_arg(ap, double);
+               ts_set_valuef(tsv, num);
+               return 0;
+       }
+
+       vec = alloca(count * sizeof *vec);
+       for(i=0; i<count; i++) {
+               vec[i] = va_arg(ap, double);
+       }
+       return ts_set_valuef_arr(tsv, count, vec);
+}
+
+int ts_set_valuef(struct ts_value *tsv, float fnum)
+{
+       return ts_set_valuef_arr(tsv, 1, &fnum);
+}
+
+int ts_set_value_arr(struct ts_value *tsv, int count, const struct ts_value *arr)
+{
+       int i, allnum = 1;
+
+       if(count <= 1) return -1;
+
+       if(!(tsv->array = malloc(count * sizeof *tsv->array))) {
+               return -1;
+       }
+       tsv->array_size = count;
+
+       for(i=0; i<count; i++) {
+               if(arr[i].type != TS_NUMBER) {
+                       allnum = 0;
+               }
+               if(ts_copy_value(tsv->array + i, (struct ts_value*)arr + i) == -1) {
+                       while(--i >= 0) {
+                               ts_destroy_value(tsv->array + i);
+                       }
+                       free(tsv->array);
+                       tsv->array = 0;
+                       return -1;
+               }
+       }
+
+       if(allnum) {
+               if(!(tsv->vec = malloc(count * sizeof *tsv->vec))) {
+                       ts_destroy_value(tsv);
+                       return -1;
+               }
+               tsv->type = TS_VECTOR;
+               tsv->vec_size = count;
+
+               for(i=0; i<count; i++) {
+                       tsv->vec[i] = tsv->array[i].fnum;
+               }
+       } else {
+               tsv->type = TS_ARRAY;
+       }
+       return 0;
+}
+
+int ts_set_valuev(struct ts_value *tsv, int count, ...)
+{
+       int res;
+       va_list ap;
+       va_start(ap, count);
+       res = ts_set_valuev_va(tsv, count, ap);
+       va_end(ap);
+       return res;
+}
+
+int ts_set_valuev_va(struct ts_value *tsv, int count, va_list ap)
+{
+       int i;
+
+       if(count <= 1) return -1;
+
+       if(!(tsv->array = malloc(count * sizeof *tsv->array))) {
+               return -1;
+       }
+       tsv->array_size = count;
+
+       for(i=0; i<count; i++) {
+               struct ts_value *src = va_arg(ap, struct ts_value*);
+               if(ts_copy_value(tsv->array + i, src) == -1) {
+                       while(--i >= 0) {
+                               ts_destroy_value(tsv->array + i);
+                       }
+                       free(tsv->array);
+                       tsv->array = 0;
+                       return -1;
+               }
+       }
+       return 0;
+}
+
+
+/* ---- ts_attr implementation ---- */
+
+int ts_init_attr(struct ts_attr *attr)
+{
+       memset(attr, 0, sizeof *attr);
+       return ts_init_value(&attr->val);
+}
+
+void ts_destroy_attr(struct ts_attr *attr)
+{
+       free(attr->name);
+       ts_destroy_value(&attr->val);
+}
+
+struct ts_attr *ts_alloc_attr(void)
+{
+       struct ts_attr *attr = malloc(sizeof *attr);
+       if(!attr || ts_init_attr(attr) == -1) {
+               free(attr);
+               return 0;
+       }
+       return attr;
+}
+
+void ts_free_attr(struct ts_attr *attr)
+{
+       ts_destroy_attr(attr);
+       free(attr);
+}
+
+int ts_copy_attr(struct ts_attr *dest, struct ts_attr *src)
+{
+       if(dest == src) return 0;
+
+       if(ts_set_attr_name(dest, src->name) == -1) {
+               return -1;
+       }
+
+       if(ts_copy_value(&dest->val, &src->val) == -1) {
+               ts_destroy_attr(dest);
+               return -1;
+       }
+       return 0;
+}
+
+int ts_set_attr_name(struct ts_attr *attr, const char *name)
+{
+       char *n = malloc(strlen(name) + 1);
+       if(!n) return -1;
+       strcpy(n, name);
+
+       free(attr->name);
+       attr->name = n;
+       return 0;
+}
+
+
+/* ---- ts_node implementation ---- */
+
+int ts_init_node(struct ts_node *node)
+{
+       memset(node, 0, sizeof *node);
+       return 0;
+}
+
+void ts_destroy_node(struct ts_node *node)
+{
+       if(!node) return;
+
+       free(node->name);
+
+       while(node->attr_list) {
+               struct ts_attr *attr = node->attr_list;
+               node->attr_list = node->attr_list->next;
+               ts_free_attr(attr);
+       }
+}
+
+struct ts_node *ts_alloc_node(void)
+{
+       struct ts_node *node = malloc(sizeof *node);
+       if(!node || ts_init_node(node) == -1) {
+               free(node);
+               return 0;
+       }
+       return node;
+}
+
+void ts_free_node(struct ts_node *node)
+{
+       ts_destroy_node(node);
+       free(node);
+}
+
+void ts_free_tree(struct ts_node *tree)
+{
+       if(!tree) return;
+
+       while(tree->child_list) {
+               struct ts_node *child = tree->child_list;
+               tree->child_list = tree->child_list->next;
+               ts_free_tree(child);
+       }
+
+       ts_free_node(tree);
+}
+
+int ts_set_node_name(struct ts_node *node, const char *name)
+{
+       char *n = malloc(strlen(name) + 1);
+       if(!n) return -1;
+       strcpy(n, name);
+
+       free(node->name);
+       node->name = n;
+       return 0;
+}
+
+void ts_add_attr(struct ts_node *node, struct ts_attr *attr)
+{
+       attr->next = 0;
+       if(node->attr_list) {
+               node->attr_tail->next = attr;
+               node->attr_tail = attr;
+       } else {
+               node->attr_list = node->attr_tail = attr;
+       }
+       node->attr_count++;
+}
+
+struct ts_attr *ts_get_attr(struct ts_node *node, const char *name)
+{
+       struct ts_attr *attr = node->attr_list;
+       while(attr) {
+               if(strcmp(attr->name, name) == 0) {
+                       return attr;
+               }
+               attr = attr->next;
+       }
+       return 0;
+}
+
+const char *ts_get_attr_str(struct ts_node *node, const char *aname, const char *def_val)
+{
+       struct ts_attr *attr = ts_get_attr(node, aname);
+       if(!attr || !attr->val.str) {
+               return def_val;
+       }
+       return attr->val.str;
+}
+
+float ts_get_attr_num(struct ts_node *node, const char *aname, float def_val)
+{
+       struct ts_attr *attr = ts_get_attr(node, aname);
+       if(!attr || attr->val.type != TS_NUMBER) {
+               return def_val;
+       }
+       return attr->val.fnum;
+}
+
+int ts_get_attr_int(struct ts_node *node, const char *aname, int def_val)
+{
+       struct ts_attr *attr = ts_get_attr(node, aname);
+       if(!attr || attr->val.type != TS_NUMBER) {
+               return def_val;
+       }
+       return attr->val.inum;
+}
+
+float *ts_get_attr_vec(struct ts_node *node, const char *aname, float *def_val)
+{
+       struct ts_attr *attr = ts_get_attr(node, aname);
+       if(!attr || !attr->val.vec) {
+               return def_val;
+       }
+       return attr->val.vec;
+}
+
+struct ts_value *ts_get_attr_array(struct ts_node *node, const char *aname, struct ts_value *def_val)
+{
+       struct ts_attr *attr = ts_get_attr(node, aname);
+       if(!attr || !attr->val.array) {
+               return def_val;
+       }
+       return attr->val.array;
+}
+
+void ts_add_child(struct ts_node *node, struct ts_node *child)
+{
+       if(child->parent) {
+               if(child->parent == node) return;
+               ts_remove_child(child->parent, child);
+       }
+       child->parent = node;
+       child->next = 0;
+
+       if(node->child_list) {
+               node->child_tail->next = child;
+               node->child_tail = child;
+       } else {
+               node->child_list = node->child_tail = child;
+       }
+       node->child_count++;
+}
+
+int ts_remove_child(struct ts_node *node, struct ts_node *child)
+{
+       struct ts_node dummy, *iter = &dummy;
+       dummy.next = node->child_list;
+
+       while(iter->next && iter->next != child) {
+               iter = iter->next;
+       }
+       if(!iter->next) {
+               return -1;
+       }
+
+       child->parent = 0;
+
+       iter->next = child->next;
+       if(!iter->next) {
+               node->child_tail = iter;
+       }
+       node->child_list = dummy.next;
+       node->child_count--;
+       assert(node->child_count >= 0);
+       return 0;
+}
+
+struct ts_node *ts_get_child(struct ts_node *node, const char *name)
+{
+       struct ts_node *res = node->child_list;
+       while(res) {
+               if(strcmp(res->name, name) == 0) {
+                       return res;
+               }
+               res = res->next;
+       }
+       return 0;
+}
+
+struct ts_node *ts_load(const char *fname)
+{
+       FILE *fp;
+       struct ts_node *root;
+
+       if(!(fp = fopen(fname, "rb"))) {
+               fprintf(stderr, "ts_load: failed to open file: %s: %s\n", fname, strerror(errno));
+               return 0;
+       }
+
+       root = ts_load_file(fp);
+       fclose(fp);
+       return root;
+}
+
+struct ts_node *ts_load_file(FILE *fp)
+{
+       struct ts_io io = {0};
+       io.data = fp;
+       io.read = io_read;
+
+       return ts_load_io(&io);
+}
+
+struct ts_node *ts_load_io(struct ts_io *io)
+{
+       return ts_text_load(io);
+}
+
+int ts_save(struct ts_node *tree, const char *fname)
+{
+       FILE *fp;
+       int res;
+
+       if(!(fp = fopen(fname, "wb"))) {
+               fprintf(stderr, "ts_save: failed to open file: %s: %s\n", fname, strerror(errno));
+               return 0;
+       }
+       res = ts_save_file(tree, fp);
+       fclose(fp);
+       return res;
+}
+
+int ts_save_file(struct ts_node *tree, FILE *fp)
+{
+       struct ts_io io = {0};
+       io.data = fp;
+       io.write = io_write;
+
+       return ts_save_io(tree, &io);
+}
+
+int ts_save_io(struct ts_node *tree, struct ts_io *io)
+{
+       return ts_text_save(tree, io);
+}
+
+static const char *pathtok(const char *path, char *tok)
+{
+       int len;
+       const char *dot = strchr(path, '.');
+       if(!dot) {
+               strcpy(tok, path);
+               return 0;
+       }
+
+       len = dot - path;
+       memcpy(tok, path, len);
+       tok[len] = 0;
+       return dot + 1;
+}
+
+struct ts_attr *ts_lookup(struct ts_node *node, const char *path)
+{
+       char *name = alloca(strlen(path) + 1);
+
+       if(!node) return 0;
+
+       if(!(path = pathtok(path, name)) || strcmp(name, node->name) != 0) {
+               return 0;
+       }
+
+       while((path = pathtok(path, name)) && (node = ts_get_child(node, name)));
+
+       if(path || !node) return 0;
+       return ts_get_attr(node, name);
+}
+
+const char *ts_lookup_str(struct ts_node *root, const char *path, const char *def_val)
+{
+       struct ts_attr *attr = ts_lookup(root, path);
+       if(!attr || !attr->val.str) {
+               return def_val;
+       }
+       return attr->val.str;
+}
+
+float ts_lookup_num(struct ts_node *root, const char *path, float def_val)
+{
+       struct ts_attr *attr = ts_lookup(root, path);
+       if(!attr || attr->val.type != TS_NUMBER) {
+               return def_val;
+       }
+       return attr->val.fnum;
+}
+
+int ts_lookup_int(struct ts_node *root, const char *path, int def_val)
+{
+       struct ts_attr *attr = ts_lookup(root, path);
+       if(!attr || attr->val.type != TS_NUMBER) {
+               return def_val;
+       }
+       return attr->val.inum;
+}
+
+float *ts_lookup_vec(struct ts_node *root, const char *path, float *def_val)
+{
+       struct ts_attr *attr = ts_lookup(root, path);
+       if(!attr || !attr->val.vec) {
+               return def_val;
+       }
+       return attr->val.vec;
+}
+
+struct ts_value *ts_lookup_array(struct ts_node *node, const char *path, struct ts_value *def_val)
+{
+       struct ts_attr *attr = ts_lookup(node, path);
+       if(!attr || !attr->val.array) {
+               return def_val;
+       }
+       return attr->val.array;
+}
+
+static long io_read(void *buf, size_t bytes, void *uptr)
+{
+       size_t sz = fread(buf, 1, bytes, uptr);
+       if(sz < bytes && errno) return -1;
+       return sz;
+}
+
+static long io_write(const void *buf, size_t bytes, void *uptr)
+{
+       size_t sz = fwrite(buf, 1, bytes, uptr);
+       if(sz < bytes && errno) return -1;
+       return sz;
+}
index 0b5f2b9..112b6ff 100644 (file)
@@ -5,6 +5,7 @@
 #include "miniglut.h"
 #include "game.h"
 #include "util.h"
+#include "goat3d.h"
 
 static int ginit(void);
 static void gdestroy(void);
@@ -27,13 +28,44 @@ struct game_screen scr_game = {
 static float cam_theta, cam_phi = 20, cam_dist = 10;
 static float cam_pan[3];
 
+static struct goat3d *gscn;
+static int dlist;
+
+
 static int ginit(void)
 {
+       int i, num, nfaces;
+
+       if(!(gscn = goat3d_create()) || goat3d_load(gscn, "data/track1.g3d")) {
+               return -1;
+       }
+
+       dlist = glGenLists(1);
+       glNewList(dlist, GL_COMPILE);
+       num = goat3d_get_node_count(gscn);
+       for(i=0; i<num; i++) {
+               struct goat3d_node *node = goat3d_get_node(gscn, i);
+               if(goat3d_get_node_type(node) == GOAT3D_NODE_MESH) {
+                       struct goat3d_mesh *mesh = goat3d_get_node_object(node);
+
+                       glEnableClientState(GL_VERTEX_ARRAY);
+                       glVertexPointer(3, GL_FLOAT, 0, goat3d_get_mesh_attribs(mesh, GOAT3D_MESH_ATTR_VERTEX));
+
+                       nfaces = goat3d_get_mesh_face_count(mesh) / 3;
+                       glDrawElements(GL_TRIANGLES, nfaces * 3, GL_UNSIGNED_INT, goat3d_get_mesh_faces(mesh));
+
+                       glDisableClientState(GL_VERTEX_ARRAY);
+
+               }
+       }
+       glEndList();
+
        return 0;
 }
 
 static void gdestroy(void)
 {
+       goat3d_free(gscn);
 }
 
 static int gstart(void)
@@ -47,7 +79,6 @@ static void gstop(void)
 
 static void gdisplay(void)
 {
-       static int dlist;
        glMatrixMode(GL_MODELVIEW);
        glLoadIdentity();
        glTranslatef(0, 0, -cam_dist);
@@ -55,16 +86,7 @@ static void gdisplay(void)
        glRotatef(cam_theta, 0, 1, 0);
        glTranslatef(cam_pan[0], cam_pan[1], cam_pan[2]);
 
-       glColor3f(1, 1, 1);
-       glFrontFace(GL_CW);
-       if(!dlist) {
-               dlist = glGenLists(1);
-               glNewList(dlist, GL_COMPILE);
-               glutSolidTeapot(1);
-               glEndList();
-       }
        glCallList(dlist);
-       glFrontFace(GL_CCW);
 }
 
 static void greshape(int x, int y)