v4k-git-backup/tools/editor/v4k_sprite.c

455 lines
15 KiB
C

#include "engine/v4k.c"
// texture_t texture_createclip(unsigned cx,unsigned cy,unsigned cw,unsigned ch, unsigned tw,unsigned th,unsigned tn,void *pixels, unsigned flags) {
// return texture_create(tw,th,tn,pixels,flags);
// static array(unsigned) clip = 0;
// array_resize(clip, cw*ch*4);
// for( unsigned y = 0; y < ch; ++y )
// memcpy((char *)clip + (0+(0+y)*cw)*tn, (char*)pixels + (cx+(cy+y)*tw)*tn, cw*tn);
// return texture_create(cw,ch,tn,clip,flags);
// }
#define array_reserve_(arr,x) (array_count(arr) > (x) ? (arr) : array_resize(arr, 1+(x)))
#define ui_array(label,type,ptr) do { \
int changed = 0; \
if( ui_collapse(label, va(#type "%p",ptr)) ) { \
char label_ex[8]; \
for( int idx = 0, iend = array_count(*(ptr)); idx < iend; ++idx ) { \
type* it = *(ptr) + idx; \
snprintf(label_ex, sizeof(label_ex), "[%d]", idx); \
changed |= ui_##type(label_ex, it); \
} \
ui_collapse_end(); \
} \
} while(0)
int ui_vec2i(const char *label, vec2i *v) { return ui_unsigned2(label, (unsigned*)v); }
int ui_vec3i(const char *label, vec3i *v) { return ui_unsigned3(label, (unsigned*)v); }
int ui_vec2(const char *label, vec2 *v) { return ui_float2(label, (float*)v); }
int ui_vec3(const char *label, vec3 *v) { return ui_float3(label, (float*)v); }
int ui_vec4(const char *label, vec4 *v) { return ui_float4(label, (float*)v); }
char *trimspace(char *str) {
for( char *s = str; *s; ++s )
if(*s <= 32) memmove(s, s+1, strlen(s));
return str;
}
char *file_parent(const char *f) { // folder/folder/abc
char *p = file_path(f); // folder/folder/
char *last = strrchr(p, '/'); // ^
if( !last ) return p; // return parent if no sep
*last = '\0'; // folder/folder
last = strrchr(p, '/'); // ^
return last ? last + 1 : p; // return parent if no sep
}
int ui_obj(const char *fmt, obj *o) {
int changed = 0, item = 1;
for each_objmember(o, TYPE,NAME,PTR) {
char *label = va(fmt, NAME);
/**/ if(!strcmp(TYPE,"float")) { if(ui_float(label, PTR)) changed = item; }
else if(!strcmp(TYPE,"int")) { if(ui_int(label, PTR)) changed = item; }
else if(!strcmp(TYPE,"unsigned")) { if(ui_unsigned(label, PTR)) changed = item; }
else if(!strcmp(TYPE,"vec2")) { if(ui_float2(label, PTR)) changed = item; }
else if(!strcmp(TYPE,"vec3")) { if(ui_float3(label, PTR)) changed = item; }
else if(!strcmp(TYPE,"vec4")) { if(ui_float4(label, PTR)) changed = item; }
else if(!strcmp(TYPE,"rgb")) { if(ui_color3(label, PTR)) changed = item; }
else if(!strcmp(TYPE,"rgba")) { if(ui_color4(label, PTR)) changed = item; }
else if(!strcmp(TYPE,"color")) { if(ui_color4f(label, PTR)) changed = item; }
else if(!strcmp(TYPE,"color3f")) { if(ui_color3f(label, PTR)) changed = item; }
else if(!strcmp(TYPE,"color4f")) { if(ui_color4f(label, PTR)) changed = item; }
else if(!strcmp(TYPE,"char*")) { if(ui_string(label, PTR)) changed = item; }
else ui_label2(label, va("(%s)", TYPE)); // INFO instead of (TYPE)?
++item;
}
return changed;
}
TODO("serialize array(types)")
TODO("serialize map(char*,types)")
TODO("serialize map(int,types)")
TODO("sprite: solid platforms, one-way platforms")
TODO("sprite: shake left-right, up-down")
TODO("sprite: coyote time")
TODO("sprite: jump buffering before grounded")
TODO("sprite: double jump, wall sliding, wall climbing")
TODO("sprite: hitbox for enemies -> wall detection")
#define OBJTYPEDEF2(...) OBJTYPEDEF(__VA_ARGS__); AUTORUN
typedef unsigned quark_t;
typedef struct atlas_frame_t {
unsigned delay;
vec4 sheet;
vec2 anchor; // @todo
array(vec3i) indices;
array(vec2) coords;
array(vec2) uvs;
} atlas_frame_t;
typedef struct atlas_anim_t {
quark_t name;
array(unsigned) frames;
} atlas_anim_t;
typedef struct atlas_t {
texture_t tex;
array(atlas_frame_t) frames;
array(atlas_anim_t) anims;
quarks_db db;
} atlas_t;
int ui_atlas_frame(atlas_frame_t *f) {
ui_unsigned("delay", &f->delay);
ui_vec4("sheet", &f->sheet);
ui_array("indices", vec3i, &f->indices);
ui_array("coords", vec2, &f->coords);
ui_array("uvs", vec2, &f->uvs);
return 0;
}
int ui_atlas(atlas_t *a) {
int changed = 0;
ui_texture(NULL, a->tex);
for( int i = 0; i < array_count(a->anims); ++i ) {
if( ui_collapse(quark_string(&a->db, a->anims[i].name), va("%p%d", a, a->anims[i].name) ) ) {
changed = i+1;
for( int j = 0; j < array_count(a->anims[i].frames); ++j ) {
if( ui_collapse(va("[%d]",j), va("%p%d.%d", a, a->anims[i].name,j) ) ) {
ui_unsigned("Frame", &a->anims[i].frames[j]);
ui_atlas_frame(a->frames + a->anims[i].frames[j]);
ui_collapse_end();
}
}
ui_collapse_end();
}
}
return changed;
}
void atlas_destroy(atlas_t *a) {
if( a ) {
texture_destroy(&a->tex);
memset(a, 0, sizeof(atlas_t));
}
}
atlas_t atlas_create(const char *inifile, unsigned flags) {
atlas_t a = {0};
int padding = 0, border = 0;
ini_t kv = ini(inifile);
for each_map(kv, char*,k, char*,v ) {
unsigned index = atoi(k);
/**/ if( strend(k, ".name") ) {
array_reserve_(a.anims, index);
a.anims[index].name = quark_intern(&a.db, v);
}
else if( strend(k, ".frames") ) {
array_reserve_(a.anims, index);
array(char*) pairs = strsplit(v, ",");
for( int i = 0, end = array_count(pairs); i < end; i += 2 ) {
unsigned frame = atoi(pairs[i]);
unsigned delay = atoi(pairs[i+1]);
array_reserve_(a.frames, frame);
a.frames[frame].delay = delay;
array_push(a.anims[index].frames, frame);
}
}
else if( strend(k, ".sheet") ) {
array_reserve_(a.frames, index);
vec4 sheet = atof4(v); //x,y,x2+2,y2+2 -> x,y,w,h (for 2,2 padding)
a.frames[index].sheet = vec4(sheet.x,sheet.y,sheet.z-sheet.x,sheet.w-sheet.y);
}
else if( strend(k, ".indices") ) {
array_reserve_(a.frames, index);
const char *text = v;
array(char*) tuples = strsplit(text, ",");
for( int i = 0, end = array_count(tuples); i < end; i += 3 ) {
unsigned p1 = atoi(tuples[i]);
unsigned p2 = atoi(tuples[i+1]);
unsigned p3 = atoi(tuples[i+2]);
array_push(a.frames[index].indices, vec3i(p1,p2,p3));
}
}
else if( strend(k, ".coords") ) {
array_reserve_(a.frames, index);
const char *text = v;
array(char*) pairs = strsplit(text, ",");
for( int i = 0, end = array_count(pairs); i < end; i += 2 ) {
unsigned x = atoi(pairs[i]);
unsigned y = atoi(pairs[i+1]);
array_push(a.frames[index].coords, vec2(x,y));
}
}
else if( strend(k, ".uvs") ) {
array_reserve_(a.frames, index);
const char *text = v;
array(char*) pairs = strsplit(text, ",");
for( int i = 0, end = array_count(pairs); i < end; i += 2 ) {
unsigned u = atoi(pairs[i]);
unsigned v = atoi(pairs[i+1]);
array_push(a.frames[index].uvs, vec2(u,v));
}
}
else if( strend(k, "padding") ) {
padding = atoi(v);
}
else if( strend(k, "border") ) {
border = atoi(v);
}
else if( strend(k, "file") ) {
a.tex = texture(v, 0);
}
else if( strend(k, "bitmap") ) {
const char *text = v;
array(char) bin = base64_decode(text, strlen(text));
a.tex = texture_from_mem(bin, array_count(bin), 0);
array_free(bin);
}
#if 0
else if( strend(k, ".frame") ) {
array_reserve_(a.frames, index);
puts(k), puts(v);
}
#endif
}
// post-process: normalize uvs and coords into [0..1] ranges
for each_array_ptr(a.frames, atlas_frame_t, f) {
for each_array_ptr(f->uvs, vec2, uv) {
uv->x /= a.tex.w;
uv->y /= a.tex.h;
}
for each_array_ptr(f->coords, vec2, xy) {
xy->x /= a.tex.w;
xy->y /= a.tex.h;
}
// @todo: adjust padding/border
}
#if 0
// post-process: specify an anchor for each anim based on 1st frame dims
for each_array_ptr(a.anims, atlas_anim_t, anim) {
atlas_frame_t *first = a.frames + *anim->frames;
for( int i = 0; i < array_count(anim->frames); i += 2) {
atlas_frame_t *ff = a.frames + anim->frames[ i ];
ff->anchor.x = (ff->sheet.z - first->sheet.z) / 2;
ff->anchor.y = (ff->sheet.w - first->sheet.w) / 2;
}
}
#endif
return a;
}
typedef struct sprite2 { OBJ
vec4 gamepad; // up,down,left,right
vec2 fire; // a,b
vec3 pos;
float tilt;
unsigned tint;
unsigned frame;
unsigned timer, timer_ms;
unsigned flip_, flipped;
unsigned play;
bool paused;
// array(unsigned) play_queue; or unsigned play_next;
atlas_t *a; // shared
atlas_t own; // owned
} sprite2;
void sprite2_setanim(sprite2 *s, unsigned name) {
if( s->play != name ) {
s->play = name;
s->frame = 0;
atlas_frame_t *f = &s->a->frames[ s->a->anims[s->play].frames[s->frame] ];
s->timer_ms = f->delay;
s->timer = s->timer_ms;
}
}
void sprite2_ctor(sprite2 *s) {
s->tint = WHITE;
s->timer_ms = 100;
s->flipped = 1;
}
void sprite2_dtor(sprite2 *s) {
memset(s, 0, sizeof(*s));
}
void sprite2_tick(sprite2 *s) {
int move = input(s->gamepad.array[3]) - input(s->gamepad.array[2]); // RIGHT - LEFT
int dt = 16; // window_delta() * 1000;
unsigned over = (s->timer - dt) > s->timer;
if(!s->paused)
s->timer -= dt;
if( over ) {
int len = array_count(s->a->anims[s->play].frames);
unsigned next = (s->frame + 1) % (len + !len);
unsigned eoa = next < s->frame;
s->frame = next;
atlas_frame_t *f = &s->a->frames[ s->a->anims[s->play].frames[s->frame] ];
s->timer_ms = f->delay;
s->timer += s->timer_ms;
}
if( s->play == 0 && move ) sprite2_setanim(s, 1);
if( s->play == 1 ) { //<
float speed = 1.0f;
if(move) s->pos.x += speed * move, s->flip_ = move < 0, sprite2_setanim(s, 1);
else sprite2_setanim(s, 0);
}
}
void sprite2_draw(sprite2 *s) {
atlas_frame_t *f = &s->a->frames[ s->a->anims[s->play].frames[s->frame] ];
#if 1
// @todo {
unsigned sample = s->a->anims[s->play].frames[s->frame];
sample = 0;
f->anchor.x = (-s->a->frames[sample].sheet.z + f->sheet.z) / 2;
f->anchor.y = (+s->a->frames[sample].sheet.w - f->sheet.w) / 2;
// }
#endif
// rect(x,y,w,h) is [0..1] normalized, z-index, pos(x,y,scale), rotation (degrees), color (rgba)
vec4 rect = { f->sheet.x / s->a->tex.w, f->sheet.y / s->a->tex.h, f->sheet.z / s->a->tex.w, f->sheet.w / s->a->tex.h };
sprite_rect(s->a->tex, rect, s->pos.y, vec4(s->pos.x+f->anchor.x,s->pos.y+f->anchor.y,s->flip_ ^ s->flipped?1:-1,1), s->tilt, s->tint);
}
void sprite2_edit(sprite2 *s) {
const char *id = vac("%p", s);
if( s && ui_collapse(id, id) ) {
ui_obj("%s", (obj*)s);
ui_bool("paused", &s->paused);
ui_label(va("frame anim [%d]", s->a->anims[s->play].frames[s->frame]));
int k = s->play;
if( ui_int("anim", &k) ) {
sprite2_setanim(s, k);
}
int selected = ui_atlas(s->a);
if( selected ) sprite2_setanim(s, selected - 1);
ui_collapse_end();
}
}
OBJTYPEDEF(sprite2,10);
AUTORUN {
STRUCT(sprite2, vec4, gamepad);
STRUCT(sprite2, vec2, fire);
STRUCT(sprite2, vec3, pos);
STRUCT(sprite2, float, tilt);
STRUCT(sprite2, rgba, tint);
STRUCT(sprite2, unsigned, frame);
STRUCT(sprite2, unsigned, timer);
STRUCT(sprite2, unsigned, timer_ms);
STRUCT(sprite2, unsigned, flipped);
STRUCT(sprite2, unsigned, play);
EXTEND(sprite2, ctor,edit,draw,tick);
}
sprite2* sprite2_new(const char *ase, int bindings[6]) {
sprite2 *s = obj_new(sprite2, {bindings[0],bindings[1],bindings[2],bindings[3]}, {bindings[4],bindings[5]});
s->own = atlas_create(ase, 0);
s->a = &s->own;
return s;
}
void sprite2_del(sprite2 *s) {
if( s ) {
if( s->a == &s->own ) atlas_destroy(&s->own);
obj_free(s);
memset(s, 0, sizeof(sprite2));
}
}
void game(unsigned frame) {
static camera_t cam;
static sprite2 *s1 = 0;
static sprite2 *s2 = 0;
static sprite2 *s3 = 0;
static sprite2 *s4 = 0;
if( !frame ) {
// camera center(x,y) zoom(z)
cam = camera();
cam.position = vec3(window_width()/2,window_height()/2,8);
camera_enable(&cam);
sprite2_del(s1);
sprite2_del(s2);
sprite2_del(s3);
sprite2_del(s4);
s1 = sprite2_new("Captain Clown Nose.ase", (int[6]){KEY_UP,KEY_DOWN,KEY_LEFT,KEY_RIGHT,KEY_A,KEY_S});
s2 = sprite2_new("Crew-Crabby.ase", (int[6]){KEY_I,KEY_K,KEY_J,KEY_L} );
s3 = sprite2_new("Props-Shooter Traps.ase", (int[6]){0} );
s4 = sprite2_new("Crew-Fierce Tooth.ase", (int[6]){0,0,KEY_N,KEY_M} );
// pos and z-order
s1->pos = vec3(window_width()/2, window_height()/2, 2);
s2->pos = vec3(window_width()/2, window_height()/2, 1);
s3->pos = vec3(window_width()/2, window_height()/2, 1);
s4->pos = vec3(window_width()/2, window_height()/2, 1);
s4->flipped ^= 1;
}
// camera panning (x,y) & zooming (z)
if( !ui_hover() && !ui_active() ) {
if( input(MOUSE_L) ) cam.position.x -= input_diff(MOUSE_X);
if( input(MOUSE_L) ) cam.position.y -= input_diff(MOUSE_Y);
cam.position.z += input_diff(MOUSE_W) * 0.1; // cam.p.z += 0.001f; for tests
}
obj_tick(s1);
obj_draw(s1);
obj_tick(s2);
obj_draw(s2);
obj_tick(s3);
obj_draw(s3);
obj_tick(s4);
obj_draw(s4);
if( ui_panel("Sprites", PANEL_OPEN)) {
obj_edit(s1);
obj_edit(s2);
obj_edit(s3);
obj_edit(s4);
ui_panel_end();
}
}
int main() {
unsigned frame = 0;
for( window_create(0.75, 0); window_swap(); ) {
if( input_down(KEY_Z) && input(KEY_ALT) ) window_record(file_counter(va("%s.mp4",app_name())));
if( input_down(KEY_F5) ) frame = 0;
game( frame++ );
}
}