-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
.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
#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"
* 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];
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];
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;
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;
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++) {
}
}
-static inline void cgm_mcofmatrix(float *m)
+static CGM_INLINE void cgm_mcofmatrix(float *m)
{
float tmp[16];
int i, j;
}
}
-static inline int cgm_minverse(float *m)
+static CGM_INLINE int cgm_minverse(float *m)
{
int i, j;
float tmp[16];
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;
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;
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);
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);
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);
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:
}
}
-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);
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] = {
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;
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);
}
-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);
}
-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];
* 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];
}
}
-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;
}
}
-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];
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];
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;
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;
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;
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;
*/
#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;
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;
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,
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,
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;
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;
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;
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)
{
*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;
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;
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);
* 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;
}
-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;
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;
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;
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;
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) {
}
}
-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);
}
}
-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);
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;
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;
* 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;
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);
}
* 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];
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];
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];
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];
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;
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;
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) {
}
}
-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;
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);
}
}
-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;
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;
* 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;
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;
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;
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;
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;
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];
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];
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];
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];
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];
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];
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;
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;
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) {
}
}
-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;
--- /dev/null
+ 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>.
--- /dev/null
+ 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.
--- /dev/null
+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)
--- /dev/null
+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.
--- /dev/null
+/*
+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_ */
--- /dev/null
+/*
+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);
+}
--- /dev/null
+/*
+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_ */
--- /dev/null
+/*
+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);
+}
--- /dev/null
+/*
+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_ */
--- /dev/null
+/* 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;
+}
--- /dev/null
+/* 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_ */
--- /dev/null
+/*
+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;
+}
--- /dev/null
+/*
+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;
+}
+
--- /dev/null
+/*
+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_ */
--- /dev/null
+/*
+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(<->color, 1, 1, 1);
+ cgm_vcons(<->attenuation, 1, 0, 0);
+ cgm_vcons(<->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;
+ }
+}
--- /dev/null
+/*
+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_ */
--- /dev/null
+/*
+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, <))) {
+ 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;
+}
--- /dev/null
+/*
+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);
+}
--- /dev/null
+/*
+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_ */
--- /dev/null
+/*
+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;
+}
--- /dev/null
+/*
+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;
+}
--- /dev/null
+/*
+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_ */
--- /dev/null
+/*
+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;
+}
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)
--- /dev/null
+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.
--- /dev/null
+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)
--- /dev/null
+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...
--- /dev/null
+#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_ */
--- /dev/null
+/* 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;
+}
--- /dev/null
+/* 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_ */
--- /dev/null
+#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";
+}
--- /dev/null
+#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;
+}
#include "miniglut.h"
#include "game.h"
#include "util.h"
+#include "goat3d.h"
static int ginit(void);
static void gdestroy(void);
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)
static void gdisplay(void)
{
- static int dlist;
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glTranslatef(0, 0, -cam_dist);
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)