|
|
8535a3 |
|
|
|
f63775 |
#include <sdl_image.h></sdl_image.h>
|
|
|
8535a3 |
|
|
|
8535a3 |
#include "private.h"
|
|
|
8935bc |
#include "world.h"
|
|
|
8935bc |
#include "sprite.h"
|
|
|
8535a3 |
|
|
|
8535a3 |
|
|
|
8535a3 |
static HeliArray cache;
|
|
|
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 |
|
|
|
f63775 |
static void unloadFrame(void *x) {
|
|
|
f63775 |
unsigned int texid = (unsigned int)(size_t)x;
|
|
|
f63775 |
glDeleteTextures(1, &texid);
|
|
|
f63775 |
}
|
|
|
f63775 |
|
|
|
f63775 |
|
|
|
f63775 |
static unsigned int loadFrame(const char *path) {
|
|
|
8535a3 |
if (!heliStringEndsWithLowcase(path, ".png"))
|
|
|
f63775 |
return 0;
|
|
|
f63775 |
|
|
|
f63775 |
// load image
|
|
|
f63775 |
SDL_Surface *surface = IMG_Load(path);
|
|
|
f63775 |
if (!surface) {
|
|
|
f63775 |
fprintf(stderr, "helianthus: cannot load image: %s\n", path);
|
|
|
f63775 |
return 0;
|
|
|
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 |
|
|
|
f63775 |
// convert to float and premult alpha
|
|
|
f63775 |
SDL_LockSurface(surface);
|
|
|
f63775 |
int w = surface->w;
|
|
|
f63775 |
int h = surface->h;
|
|
|
a20939 |
size_t rcount = (size_t)w*4;
|
|
|
a20939 |
size_t count = rcount*h;
|
|
|
f63775 |
float *buffer = (float*)malloc(count*sizeof(float));
|
|
|
f63775 |
const unsigned char *pp = (const unsigned char *)surface->pixels;
|
|
|
a20939 |
for(float *p = buffer, *end = p + count; p < end; pp += surface->pitch - rcount) {
|
|
|
a20939 |
for(float *rend = p + rcount; p < rend; p += 4, pp += 4) {
|
|
|
a20939 |
if (pp[0]) {
|
|
|
a20939 |
float a = pp[0]/255.f;
|
|
|
a20939 |
p[0] = pp[3]/255.f*a;
|
|
|
a20939 |
p[1] = pp[2]/255.f*a;
|
|
|
a20939 |
p[2] = pp[1]/255.f*a;
|
|
|
a20939 |
p[3] = a;
|
|
|
a20939 |
} else {
|
|
|
a20939 |
p[0] = p[1] = p[2] = p[3] = 0;
|
|
|
a20939 |
}
|
|
|
f63775 |
}
|
|
|
f63775 |
}
|
|
|
f63775 |
SDL_UnlockSurface(surface);
|
|
|
f63775 |
SDL_FreeSurface(surface);
|
|
|
f63775 |
|
|
|
f63775 |
// resample to power of two
|
|
|
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;
|
|
|
f63775 |
if (r1 >= w) r1 = w - 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);
|
|
|
a20939 |
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
|
|
a20939 |
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, 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);
|
|
|
f63775 |
|
|
|
f63775 |
return texid;
|
|
|
8535a3 |
}
|
|
|
8535a3 |
|
|
|
f63775 |
|
|
|
1c7488 |
static HeliAnimation* load(const char *path) {
|
|
|
1c7488 |
HeliAnimation *a = calloc(1, sizeof(*a));
|
|
|
8535a3 |
a->path = heliStringCopy(path);
|
|
|
8535a3 |
|
|
|
1015c5 |
Directory d = openDirectory(path);
|
|
|
1015c5 |
if (d) {
|
|
|
1015c5 |
int count = directoryGetCount(d);
|
|
|
1015c5 |
for(int i = 0; i < count; ++i) {
|
|
|
1015c5 |
char *p = heliStringConcat3(path, "/", directoryGet(d, i));
|
|
|
f63775 |
size_t texid = loadFrame(p);
|
|
|
8535a3 |
free(p);
|
|
|
f63775 |
if (texid) heliArrayInsert(&a->frames, -1, (void*)texid, &unloadFrame);
|
|
|
8535a3 |
}
|
|
|
1015c5 |
closeDirectory(d);
|
|
|
8535a3 |
} else {
|
|
|
f63775 |
size_t texid = loadFrame(path);
|
|
|
f63775 |
if (texid) {
|
|
|
f63775 |
heliArrayInsert(&a->frames, -1, (void*)texid, &unloadFrame);
|
|
|
8535a3 |
} else {
|
|
|
981405 |
fprintf(stderr, "helianthus: cannot load animation by path: %s\n", path);
|
|
|
8535a3 |
}
|
|
|
8535a3 |
}
|
|
|
8535a3 |
|
|
|
8535a3 |
return a;
|
|
|
8535a3 |
}
|
|
|
8535a3 |
|
|
|
f63775 |
|
|
|
1c7488 |
static void unload(HeliAnimation *a) {
|
|
|
8535a3 |
assert(!a->refcount);
|
|
|
8535a3 |
free(a->path);
|
|
|
8535a3 |
heliArrayDestroy(&a->frames);
|
|
|
8535a3 |
free(a);
|
|
|
8535a3 |
}
|
|
|
8535a3 |
|
|
|
f63775 |
|
|
|
1c7488 |
HeliAnimation* heliAnimationLoad(const char *path) {
|
|
|
8535a3 |
HeliPair *item = heliStringmapGet(&cache, path);
|
|
|
8535a3 |
if (!item) item = heliStringmapAdd(&cache, path, load(path), (HeliFreeCallback)&unload);
|
|
|
1c7488 |
HeliAnimation *a = (HeliAnimation*)item->value;
|
|
|
8535a3 |
++a->refcount;
|
|
|
1c7488 |
return a;
|
|
|
8535a3 |
}
|
|
|
8535a3 |
|
|
|
f63775 |
|
|
|
1c7488 |
void heliAnimationUnref(HeliAnimation *a) {
|
|
|
1c7488 |
if (--a->refcount <= 0)
|
|
|
1c7488 |
heliStringmapRemove(&cache, a->path);
|
|
|
8935bc |
}
|
|
|
8535a3 |
|
|
|
f63775 |
|
|
|
07b70f |
void heliAnimationFinish()
|
|
|
07b70f |
{ heliArrayDestroy(&cache); }
|
|
|
07b70f |
|