Blame src/animation.c

8535a3
f63775
#include <sdl_image.h></sdl_image.h>
8535a3
8535a3
#include "private.h"
8935bc
#include "world.h"
dba3fc
#include "animation.h"
8535a3
8535a3
8535a3
static HeliArray cache;
dba3fc
static Animation first, last;
dba3fc
dba3fc
typedef struct _HeliTexture {
dba3fc
	const char *key;
dba3fc
	unsigned int id;
dba3fc
	int refcount;
dba3fc
} HeliTexture;
dba3fc
dba3fc
struct _Animation {
dba3fc
	int playing;
dba3fc
	int loop;
dba3fc
	double fps;
dba3fc
	double pos;
dba3fc
	HeliArray frames;
dba3fc
	Animation prev, next;
dba3fc
};
8535a3
f63775
f63775
f63775
static int fixsize(int x) {
f63775
	int p = (int)ceil(log2(x > 1 ? x : 1) - 0.25);
f63775
	if (p > 30) p = 30;
f63775
	return 1 << p;
f63775
}
f63775
f63775
f63775
static float colorclamp(float x)
f63775
	{ return x > 0.f ? (x < 1.f ? x : 1.f) : 0.f; }
f63775
f63775
f63775
static void mipx(float *buffer, int *w, int *h) {
f63775
	*w /= 2;
f63775
	if (*w <= 0) return;
a20939
	for(float *d = buffer, *s = buffer, *end = d + (*w)*(*h)*4; d < end; d += 4, s += 8)
f63775
		for(int i = 0; i < 4; ++i)
f63775
			d[i] = (s[i] + s[i + 4])*0.5f;
f63775
}
f63775
f63775
f63775
static void mipy(float *buffer, int *w, int *h) {
f63775
	*h /= 2;
f63775
	if (*h <= 0) return;
a20939
	int rstep = (*w)*4;
a20939
	float *d = buffer, *s0 = buffer, *s1 = s0 + rstep;
a20939
	for(float *end = d + (*h)*rstep; d < end; s0 += rstep, s1 += rstep)
a20939
		for(float *rend = d + rstep; d < rend; ++d, ++s0, ++s1)
f63775
			*d = (*s0 + *s1)*0.5f;
8535a3
}
8535a3
f63775
dba3fc
int imageLoad(const char *path, int *outWidth, int *outHeight, unsigned char **outPixels) {
dba3fc
	*outWidth = 0;
dba3fc
	*outHeight = 0;
dba3fc
	*outPixels = NULL;
f63775
	
f63775
	// load image
f63775
	SDL_Surface *surface = IMG_Load(path);
f63775
	if (!surface) {
f63775
		fprintf(stderr, "helianthus: cannot load image: %s\n", path);
dba3fc
		return FALSE;
f63775
	}
f63775
	
f63775
	// convert to RGBA
f63775
	if (surface->format->format != SDL_PIXELFORMAT_RGBA8888) {
f63775
		SDL_PixelFormat *format = SDL_AllocFormat(SDL_PIXELFORMAT_RGBA8888);
f63775
		SDL_Surface *converted = SDL_ConvertSurface(surface, format, 0);
f63775
		SDL_FreeFormat(format);
f63775
		SDL_FreeSurface(surface);
f63775
		surface = converted;
f63775
	}
f63775
	
dba3fc
	// copy
f63775
	SDL_LockSurface(surface);
f63775
	int w = surface->w;
f63775
	int h = surface->h;
dba3fc
	assert(w > 0 && h > 0);
dba3fc
	size_t rsize = (size_t)w*4;
dba3fc
	unsigned char *pixels = malloc(h*rsize);
d4e89f
	for(int i = 0; i < h; ++i) {
d4e89f
		const unsigned char *pp = surface->pixels + surface->pitch*i;
d4e89f
		for(unsigned char *p = pixels + i*rsize, *end = p + rsize; p < end; p += 4, pp += 4) {
d4e89f
			p[0] = pp[3];
d4e89f
			p[1] = pp[2];
d4e89f
			p[2] = pp[1];
d4e89f
			p[3] = pp[0];
d4e89f
		}
d4e89f
	}
dba3fc
	SDL_UnlockSurface(surface);
dba3fc
	SDL_FreeSurface(surface);
dba3fc
	
dba3fc
	*outPixels = pixels;
dba3fc
	*outWidth = w;
dba3fc
	*outHeight = h;
dba3fc
	return TRUE;
dba3fc
}
dba3fc
dba3fc
dba3fc
unsigned int imageToGLTexture(int width, int height, const unsigned char *pixels, int horWrap, int vertWrap) {
dba3fc
	// convert to float and premult alpha
dba3fc
	size_t count = (size_t)width*height*4;
f63775
	float *buffer = (float*)malloc(count*sizeof(float));
dba3fc
	const unsigned char *pp = pixels;
dba3fc
	for(float *p = buffer, *end = p + count; p < end; p += 4, pp += 4) {
d4e89f
		if (pp[3]) {
d4e89f
			float a = pp[3]/255.f;
d4e89f
			p[0] = pp[0]/255.f*a;
d4e89f
			p[1] = pp[1]/255.f*a;
d4e89f
			p[2] = pp[2]/255.f*a;
dba3fc
			p[3] = a;
dba3fc
		} else {
dba3fc
			p[0] = p[1] = p[2] = p[3] = 0;
f63775
		}
f63775
	}
f63775
	
f63775
	// resample to power of two
dba3fc
	int w = width, h = height;
f63775
	int fw = fixsize(w);
f63775
	int fh = fixsize(h);
f63775
	if (fw != w || fh != h) {
f63775
		float *fbuffer = (float*)malloc((size_t)fw*fh*4*sizeof(float));
59dae5
		double kr = (double)h/fh;
59dae5
		double kc = (double)w/fw;
f63775
		for(int r = 0; r < fh; ++r) {
f63775
			for(int c = 0; c < fw; ++c) {
f63775
				double pr1 = r*kr;
f63775
				int r0 = (int)floor(pr1 + HELI_PRECISION);
f63775
				pr1 -= r0;
f63775
				int r1 = r0 + 1;
502515
				if (r1 >= h) r1 = h - 1;
f63775
				if (r0 > r1) r0 = r1;
f63775
				double pr0 = 1 - pr1;
f63775
				
f63775
				double pc1 = c*kc;
f63775
				int c0 = (int)floor(pc1 + HELI_PRECISION);
f63775
				pc1 -= c0;
f63775
				int c1 = c0 + 1;
59dae5
				if (c1 >= w) c1 = w - 1;
f63775
				if (c0 > c1) c0 = c1;
f63775
				double pc0 = 1 - pc1;
f63775
				
f63775
				float *p = fbuffer + (r*fw + c)*4;
f63775
				const float *p00 = buffer + (r0*w + c0)*4;
f63775
				const float *p01 = buffer + (r0*w + c1)*4;
f63775
				const float *p10 = buffer + (r1*w + c0)*4;
f63775
				const float *p11 = buffer + (r1*w + c1)*4;
f63775
				for(int i = 0; i < 4; ++i)
f63775
					p[i] = p00[i]*pr0*pc0
f63775
					     + p01[i]*pr0*pc1
f63775
					     + p10[i]*pr1*pc0
f63775
					     + p11[i]*pr1*pc1;
f63775
			}
f63775
		}
f63775
		free(buffer);
f63775
		buffer = fbuffer;
f63775
		w = fw;
f63775
		h = fh;
f63775
	}
f63775
	
f63775
	// fix max texture size
f63775
	int maxSize = 0;
f63775
	glGetIntegerv(GL_MAX_TEXTURE_SIZE, &maxSize);
f63775
	if (maxSize < 64) maxSize = 64;
f63775
	while(w > maxSize) mipx(buffer, &w, &h);
f63775
	while(h > maxSize) mipy(buffer, &w, &h);
f63775
	
f63775
	// create OpenGL texture
f63775
	unsigned int texid = 0;
f63775
	glGenTextures(1, &texid);
f63775
	glBindTexture(GL_TEXTURE_2D, texid);
f63775
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
f63775
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
dba3fc
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, horWrap ? GL_REPEAT : GL_CLAMP_TO_EDGE);
dba3fc
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, vertWrap ? GL_REPEAT : GL_CLAMP_TO_EDGE);
59dae5
	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_LOD_BIAS, -0.7f);
a20939
	
f63775
	// build mip levels
f63775
	unsigned char *ibuffer = malloc((size_t)w*h*4);
a20939
	int level = 0;
f63775
	while(1) {
f63775
		float *pp = buffer;
f63775
		for(unsigned char *p = ibuffer, *end = p + (size_t)w*h*4; p < end; p += 4, pp += 4) {
f63775
			if (pp[3] > 1e-5) {
f63775
				float k = 1/pp[3];
f63775
				p[0] = (unsigned char)floor(colorclamp(pp[0]*k)*255.99);
f63775
				p[1] = (unsigned char)floor(colorclamp(pp[1]*k)*255.99);
f63775
				p[2] = (unsigned char)floor(colorclamp(pp[2]*k)*255.99);
f63775
				p[3] = (unsigned char)floor(colorclamp(pp[3])*255.99);
f63775
			} else {
f63775
				p[0] = (unsigned char)floor(colorclamp(pp[0])*255.99);
f63775
				p[1] = (unsigned char)floor(colorclamp(pp[1])*255.99);
f63775
				p[2] = (unsigned char)floor(colorclamp(pp[2])*255.99);
f63775
				p[3] = 0;
f63775
			}
f63775
		}
a20939
		glTexImage2D(GL_TEXTURE_2D, level, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, ibuffer);
a20939
		++level;
f63775
		
f63775
		if (w <= 1 && h <= 1) break;
f63775
		if (w > 1) mipx(buffer, &w, &h);
f63775
		if (h > 1) mipy(buffer, &w, &h);
8535a3
	}
f63775
	
f63775
	// done
f63775
	free(ibuffer);
f63775
	free(buffer);
f63775
	glBindTexture(GL_TEXTURE_2D, 0);
dba3fc
	
f63775
	return texid;
8535a3
}
8535a3
f63775
dba3fc
static void unloadTexture(HeliTexture *texture) {
dba3fc
	assert(!texture->refcount);
dba3fc
	glDeleteTextures(1, &texture->id);
dba3fc
}
dba3fc
dba3fc
dba3fc
static HeliTexture* loadTexture(const char *key) {
dba3fc
	HeliTexture *texture = calloc(1, sizeof(*texture));
dba3fc
	texture->key = heliStringCopy(key);
dba3fc
	int w = 0, h = 0;
dba3fc
	unsigned char *pixels = NULL;
dba3fc
	if (imageLoad(key+2, &w, &h, &pixels)) {
dba3fc
		texture->id = imageToGLTexture(w, h, pixels, key[0] == 'W', key[1] == 'W');
dba3fc
		free(pixels);
dba3fc
	}
dba3fc
	return texture;
dba3fc
}
dba3fc
dba3fc
dba3fc
static HeliTexture *getTexture(const char *key) {
dba3fc
	HeliPair *item = heliStringmapGet(&cache, key);
dba3fc
	if (!item) item = heliStringmapAdd(&cache, key, loadTexture(key), (HeliFreeCallback)&unloadTexture);
dba3fc
	HeliTexture *texture = (HeliTexture*)item->value;
dba3fc
	++texture->refcount;
dba3fc
	return texture;
dba3fc
}
dba3fc
dba3fc
dba3fc
static void unrefTexture(HeliTexture *texture) {
dba3fc
	if (--texture->refcount <= 0)
dba3fc
		heliStringmapRemove(&cache, texture->key);
dba3fc
}
dba3fc
dba3fc
dba3fc
static void heliAnimationFixPos(Animation animation) {
dba3fc
	double maxPos = animation->frames.count - HELI_PRECISION_SQR;
dba3fc
	if (!(animation->pos < maxPos)) animation->pos = maxPos;
dba3fc
	if (!(animation->pos > 0)) animation->pos = 0;
dba3fc
}
dba3fc
dba3fc
dba3fc
Animation createAnimationEmpty() {
dba3fc
	Animation animation = calloc(1, sizeof(*animation));
dba3fc
	animation->prev = last;
dba3fc
	*(animation->prev ? &animation->prev->next : &first) = last = animation;
dba3fc
	
dba3fc
	double minFps = worldGetMinFrameRate();
dba3fc
	double maxFps = worldGetMaxFrameRate();
dba3fc
	animation->fps = HELI_DEFAULT_FPS;
dba3fc
	if (animation->fps > maxFps) animation->fps = maxFps;
dba3fc
	if (animation->fps < minFps) animation->fps = minFps;
dba3fc
	animation->loop = TRUE;
dba3fc
	
dba3fc
	heliObjectRegister(animation, (HeliFreeCallback)&animationDestroy);
dba3fc
	return animation;
dba3fc
}
dba3fc
dba3fc
Animation createAnimationEx(const char* path, int horWrap, int vertWrap) {
dba3fc
	Animation animation = createAnimationEmpty();
dba3fc
	char *key = heliStringConcat("CC", path);
dba3fc
	if (horWrap) key[0] = 'W';
dba3fc
	if (vertWrap) key[1] = 'W';
8535a3
	
dba3fc
	Directory d = openDirectory(key + 2);
1015c5
	if (d) {
1015c5
		int count = directoryGetCount(d);
1015c5
		for(int i = 0; i < count; ++i) {
dba3fc
			const char *file = directoryGet(d, i);
dba3fc
			if (heliStringEndsWithLowcase(file, ".png")) {
dba3fc
				char *k = heliStringConcat3(key, "/", file);
dba3fc
				heliArrayInsert(&animation->frames, -1, getTexture(k), (HeliFreeCallback)&unrefTexture);
dba3fc
				free(k);
dba3fc
			}
8535a3
		}
1015c5
		closeDirectory(d);
8535a3
	} else {
dba3fc
		heliArrayInsert(&animation->frames, -1, getTexture(key), (HeliFreeCallback)&unrefTexture);
8535a3
	}
8535a3
	
dba3fc
	return animation;
dba3fc
}
dba3fc
dba3fc
Animation createAnimation(const char *path)
dba3fc
	{ return createAnimationEx(path, FALSE, FALSE); }
dba3fc
dba3fc
void animationDestroy(Animation animation) {
dba3fc
	*(animation->prev ? &animation->prev->next : &first) = animation->next;
dba3fc
	*(animation->next ? &animation->next->prev : &last ) = animation->prev;
dba3fc
	heliArrayDestroy(&animation->frames);
dba3fc
	heliObjectUnregister(animation);
dba3fc
	free(animation);
dba3fc
}
dba3fc
dba3fc
Animation animationCloneEx(Animation animation, int from, int to) {
dba3fc
	Animation a = createAnimationEmpty();
dba3fc
	animationInsertEx(a, 0, animation, from, to);
dba3fc
	animationSetFps(a, animationGetFps(animation));
dba3fc
	animationSetPos(a, animationGetPos(animation));
dba3fc
	if (animationIsPlaying(animation)) animationPlay(a); else animationPause(a);
8535a3
	return a;
8535a3
}
8535a3
dba3fc
Animation animationClone(Animation animation)
dba3fc
	{ return animationCloneEx(animation, 0, animationGetFramesCount(animation)); }
f63775
dba3fc
unsigned int animationGetGLTexId(Animation animation) {
dba3fc
	if (animationGetFramesCount(animation) <= 0) return 0;
dba3fc
	int i = animationGetFrame(animation);
dba3fc
	HeliTexture *texture = (HeliTexture*)animation->frames.items[i].value;
dba3fc
	return texture->id;
8535a3
}
8535a3
dba3fc
int animationGetFramesCount(Animation animation)
dba3fc
	{ return animation->frames.count; }
f63775
dba3fc
void animationInsertEx(Animation animation, int index, Animation other, int start, int count) {
dba3fc
	int dcnt = animationGetFramesCount(animation);
dba3fc
	int scnt = animationGetFramesCount(other);
dba3fc
	int i0 = start;
dba3fc
	int i1 = i0 + count;
dba3fc
	if (i0 < 0) i0 = 0;
dba3fc
	if (i1 > scnt) i1 = scnt;
dba3fc
	if (index < 0 || index > dcnt) index = dcnt;
dba3fc
	for(int i = i0; i < i1; ++i, ++index) {
dba3fc
		HeliTexture *texture = (HeliTexture*)other->frames.items[i].value;
dba3fc
		++texture->refcount;
dba3fc
		heliArrayInsert(&animation->frames, index, texture, (HeliFreeCallback)&unrefTexture);
dba3fc
	}
dba3fc
}
dba3fc
dba3fc
void animationInsert(Animation animation, int index, Animation other)
dba3fc
	{ animationInsertEx(animation, index, other, 0, animationGetFramesCount(other)); }
dba3fc
dba3fc
void animationRemove(Animation animation, int start, int count) {
dba3fc
	int cnt = animationGetFramesCount(animation);
dba3fc
	int i0 = start;
dba3fc
	int i1 = i0 + count;
dba3fc
	if (i0 < 0) i0 = 0;
dba3fc
	if (i1 > cnt) i1 = cnt;
dba3fc
	for(int i = i1 - 1; i >= i0; --i)
dba3fc
		heliArrayRemove(&animation->frames, i);
dba3fc
	heliAnimationFixPos(animation);
8535a3
}
8535a3
dba3fc
void animationClear(Animation animation)
dba3fc
	{ animationRemove(animation, 0, animationGetFramesCount(animation)); }
f63775
dba3fc
double animationGetFps(Animation animation)
dba3fc
	{ return animation->fps; }
dba3fc
void animationSetFps(Animation animation, double fps) {
dba3fc
	animation->fps = !(fps > HELI_MIN_FPS) ? HELI_MIN_FPS
dba3fc
	               : !(fps > HELI_MAX_FPS) ? HELI_MAX_FPS : fps;
dba3fc
		
8935bc
}
8535a3
dba3fc
int animationIsPlaying(Animation animation)
dba3fc
	{ return animation->playing; }
dba3fc
void animationPlay(Animation animation)
dba3fc
	{ animation->playing = TRUE; }
dba3fc
void animationPause(Animation animation)
dba3fc
	{ animation->playing = FALSE; }
dba3fc
dba3fc
void animationAddTime(Animation animation, double time) {
dba3fc
	int cnt = animationGetFramesCount(animation);
dba3fc
	if (cnt <= 0) return;
dba3fc
	double t = animation->fps * time;
dba3fc
	if (animation->loop) {
dba3fc
		t /= cnt; t -= floor(t); t *= cnt;
dba3fc
		animation->pos += t;
dba3fc
		if (animation->pos > cnt) animation->pos -= cnt;
dba3fc
	} else {
dba3fc
		animation->pos += t;
dba3fc
	}
dba3fc
	heliAnimationFixPos(animation);
dba3fc
}
dba3fc
dba3fc
int animationGetLoop(Animation animation)
dba3fc
	{ return animation->loop; }
dba3fc
void animationSetLoop(Animation animation, int loop)
dba3fc
	{ animation->loop = loop != FALSE; }
dba3fc
dba3fc
double animationGetPos(Animation animation)
dba3fc
	{ return animation->pos; }
dba3fc
dba3fc
void animationSetPos(Animation animation, double pos)
dba3fc
	{ animation->pos = pos; heliAnimationFixPos(animation); }
f63775
dba3fc
int animationGetFrame(Animation animation) {
dba3fc
	int cnt = animationGetFramesCount(animation);
dba3fc
	int i = floor(animation->pos + HELI_PRECISION);
dba3fc
	if (i > cnt) i = cnt;
dba3fc
	if (i < 0) i = 0;
dba3fc
	return i;
dba3fc
}
dba3fc
dba3fc
void animationSetFrame(Animation animation, int frame)
dba3fc
	{ animationSetPos(animation, frame); }
dba3fc
dba3fc
void animationNextFrame(Animation animation)
dba3fc
	{ animationAddTime(animation, 1/animationGetFps(animation)); }
dba3fc
dba3fc
dba3fc
void heliAnimationUpdate(double dt) {
dba3fc
	for(Animation animation = first; animation; animation = animation->next)
dba3fc
		if (animationIsPlaying(animation))
dba3fc
			animationAddTime(animation, dt);
dba3fc
}
dba3fc
dba3fc
void heliAnimationFinish() {
dba3fc
	assert(!cache.count);
dba3fc
	heliArrayDestroy(&cache);
dba3fc
}
07b70f