v4k-git-backup/engine/editor.c

432 lines
14 KiB
C

#define COOK_ON_DEMAND 1 // @fixme: these directives should be on client, not within v4k.dll
#define ENABLE_AUTOTESTS 1
#include "v4k.c"
//#include "../tools/labs/objtests.h"
// ----------------------------------------------------------------------------
TODO("editor_load_on_boot()");
TODO("editor_save_on_quit()");
TODO("file_id: glow.hdr vs glow.png");
TODO("reflect: iterate components+metas on REFLECT too, so they're properly saved/loaded");
TODO("edit: tree nav");
TODO("edit: keys up,down,left,right -> move selection");
TODO("edit: reordering/dragging items on a list. ctrl+cursors");
TODO("edit: tab -> cycle next node of matching highlighted type");
TODO("edit: ^C^V^X thru clipboard. ^C to serialize to clipboard.");
TODO("edit: ^Z^Y cursor too. also fix undo ordering");
TODO("edit: ^S^L^N. load/save as filesystems");
TODO("edit: ^B(toolbar)");
TODO("edit: ^E prop single-view for multiple selections: should inspect common fields only");
TODO("edit: two-column way (or Nth) for multiple selections");
TODO("edit: tab/caps view, world + entity only, obj printf");
TODO("edit: obj bounds, obj picking, obj collisions");
TODO("edit: LMB object picking, RMB object serialization + log, floating ICONS bulb light");
TODO("edit: worldtraveller component");
TODO("edit: levelstreamer component");
TODO("edit: OSC server/client port 2023");
TODO("edit: add/rem entities, add/rem components, add/rem/pause/resume systems");
TODO("edit: event loop: tick,draw*,spawn,delete,un/load from bvh stream,");
TODO("edit: overlay scene editor");
TODO("edit: overlay0 artwork");
TODO("edit: overlay1 world editor: gizmo, grid, snap, splats (@todo: fixed screen size gizmos)");
TODO("edit: overlay2 script editor");
TODO("edit: overlay3 track, spline, keys editor");
TODO("edit: overlay4 node editor (shader/anim/bt/hfsm/material/audio/blueprints)");
TODO("edit: overlay5 csv editor");
TODO("edit: overlay6 bug/task editor");
TODO("gfx: tree traversal from game");
TODO("gfx: bvh and collision queries");
TODO("gfx: visibility and pvs queries");
TODO("obj: finish SYSTEMS and join queries");
TODO("obj: make it work with /GL flag (VC)");
TODO("obj: impl obj_mutate() ... deprecate?");
TODO("obj: make() from mpack(m) + native(n)");
TODO("obj: make() should save schema `[{mn`+version. and (m)pack and (n)ative should start with objtype");
TODO("obj: super()");
TODO("obj: lock()/trylock()/unlock()/barrier(N)");
TODO("obj: diff()/patch()");
TODO("obj: free obj_children()/payload");
TODO("obj: free obj_components()/payload2");
TODO("pack: mp2json, json2mp");
TODO("pack: simplify msgpack API, make it growth similar to va()")
#if 0 // fwk_pack proposal
static __thread char* mpin;
static __thread unsigned mpinlen;
static __thread char mpinbuf[256];
static __thread char* mpout;
static __thread unsigned mpoutlen;
static __thread char mpoutbuf[256];
#define PACKMSG(...) (msgpack_new(mpin = mpinbuf, mpinlen = sizeof(mpinbuf)), mpinlen = msgpack(__VA_ARGS__), cobs_encode(mpin, mpinlen, mpout = mpoutbuf, mpoutlen = cobs_bounds(mpinlen)), mpout)
#define UNPACKMSG(ptr,fmt,...) (mpin = (char*)ptr, mpinlen = strlen(ptr), mpout = mpoutbuf, mpoutlen = sizeof(mpoutbuf), mpoutlen = cobs_decode(mpin, mpinlen, mpout, mpoutlen), msgunpack_new(mpout, mpoutlen) && msgunpack(fmt, __VA_ARGS__))
#endif
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")
TODO("new: integrate with Art/ browser")
TODO("bug: lite key bindings are being sent to editor")
TODO("bug: not sending quit signal to lite neither at window close nor editor close (see: temporary files)")
TODO("bug: missing search results window")
TODO("bug: missing code completions popup")
// TODO("eval: https://github.com/drmargarido/linters")
// TODO("eval: https://github.com/monolifed/theme16")
// TODO("eval: https://github.com/vincens2005/lite-formatters")
// https://github.com/takase1121/lite-xl-img
// https://github.com/takase1121/lite-xl-finder
// https://github.com/rxi/lite/commit/236a585756cb9fa70130eee6c9a604780aced424 > suru.png
// https://github.com/rxi/lite/commit/f90b00748e1fe1cd2340aaa06d2526a1b2ea54ec
void editor_gizmos(int dim) {
// debugdraw
if(dim == 2) ddraw_push_2d();
ddraw_ontop_push(0);
// draw gizmos, aabbs, markers, etc
for each_map_ptr(*editor_selected_map(),void*,o,int,selected) {
if( !*selected ) continue;
void *obj = *o;
// get transform
vec3 *p = NULL;
vec3 *r = NULL;
vec3 *s = NULL;
aabb *a = NULL;
for each_objmember(obj,TYPE,NAME,PTR) {
/**/ if( !strcmp(NAME, "position") ) p = PTR;
else if( !strcmp(NAME, "pos") ) p = PTR;
else if( !strcmp(NAME, "rotation") ) r = PTR;
else if( !strcmp(NAME, "rot") ) r = PTR;
else if( !strcmp(NAME, "scale") ) s = PTR;
else if( !strcmp(NAME, "sca") ) s = PTR;
else if( !strcmp(NAME, "aabb") ) a = PTR;
}
ddraw_ontop(0);
// bounding box 3d
if( 0 ) {
aabb box;
if( obj_hasmethod(*o, aabb) && obj_aabb(*o, &box) ) {
ddraw_color_push(YELLOW);
ddraw_aabb(box.min, box.max);
ddraw_color_pop();
}
}
// position marker
if( p ) {
static map(void*, vec3) prev_dir = 0;
do_once map_init_ptr(prev_dir);
vec3* dir = map_find_or_add(prev_dir, obj, vec3(1,0,0));
static map(void*, vec3) prev_pos = 0;
do_once map_init_ptr(prev_pos);
vec3* found = map_find_or_add(prev_pos, obj, *p), fwd = sub3(*p, *found);
if( (fwd.y = 0, len3sq(fwd)) ) {
*found = *p;
*dir = norm3(fwd);
}
// float diameter = len2( sub2(vec2(box->max.x,box->max.z), vec2(box->min.x,box->min.z) ));
// float radius = diameter * 0.5;
ddraw_position_dir(*p, *dir, 1);
}
ddraw_ontop(1);
// transform gizmo
if( p && r && s ) {
gizmo(p,r,s);
}
}
ddraw_ontop_pop();
if(dim == 2) ddraw_pop_2d();
}
// ----------------------------------------------------------------------------
// demo
typedef struct lit { OBJ
vec3 pos;
vec3 dir;
int type;
} lit;
int lit_aabb(lit *obj, aabb *box) {
*box = aabb( vec3(obj->pos.x-16,obj->pos.y-16,0), vec3(obj->pos.x+16,obj->pos.y+16,1) );
return 1;
}
const char *lit_icon(lit *obj) {
const char *icon =
obj->type == 0 ? ICON_MD_WB_IRIDESCENT :
obj->type == 1 ? ICON_MD_WB_INCANDESCENT :
obj->type == 2 ? ICON_MD_FLARE :
obj->type == 3 ? ICON_MD_WB_SUNNY : "";
return icon;
}
int lit_edit(lit *obj) {
const char *all_icons =
ICON_MD_WB_IRIDESCENT
ICON_MD_WB_INCANDESCENT
ICON_MD_FLARE
ICON_MD_WB_SUNNY
ICON_MD_LIGHT_MODE
ICON_MD_LIGHT
ICON_MD_FLASHLIGHT_OFF
ICON_MD_FLASHLIGHT_ON
ICON_MD_HIGHLIGHT
ICON_MD_HIGHLIGHT_ALT
ICON_MD_LIGHTBULB
ICON_MD_LIGHTBULB_OUTLINE
ICON_MD_NIGHTLIGHT
ICON_MD_NIGHTLIGHT_ROUND
// MDI
ICON_MDI_LIGHTBULB_ON_OUTLINE // generic
ICON_MDI_WALL_SCONCE_ROUND //
ICON_MDI_WALL_SCONCE_FLAT // emissive
ICON_MDI_CEILING_LIGHT // spotlight
ICON_MDI_TRACK_LIGHT // spotlight
ICON_MDI_WEATHER_SUNNY // directional
ICON_MDI_LIGHTBULB_FLUORESCENT_TUBE_OUTLINE
;
// editor_symbol(obj->pos.x+16,obj->pos.y-32,all_icons);
if( editor_selected(obj) ) {
obj->pos.x += input(KEY_RIGHT) - input(KEY_LEFT);
obj->pos.y += input(KEY_DOWN) - input(KEY_UP);
obj->type = (obj->type + !!input_down(KEY_SPACE)) % 4;
}
editor_symbol(obj->pos.x,obj->pos.y,lit_icon(obj));
return 1;
}
OBJTYPEDEF(lit,200);
AUTORUN {
STRUCT(lit, vec3, pos);
STRUCT(lit, vec3, dir);
STRUCT(lit, int, type);
EXTEND(lit, edit,icon,aabb);
}
typedef struct kid { OBJ
int kid;
vec2 pos;
vec2 vel;
float angle;
unsigned color;
int controllerid;
// --- private
char *filename;
texture_t texture_;
} kid;
void kid_ctor(kid *obj) {
obj->kid = randi(0,3);
obj->pos = vec2(randi(0, window_width()), randi(0, window_height()));
obj->vel.x = obj->vel.y = 100 + 200 * randf();
obj->controllerid = randi(0,3);
obj->texture_ = texture(obj->filename, TEXTURE_RGBA|TEXTURE_LINEAR);
}
void kid_tick(kid *obj, float dt) {
// add velocity to position
vec2 off = vec2( input(KEY_RIGHT)-input(KEY_LEFT), input(KEY_DOWN)-input(KEY_UP) );
obj->pos = add2(obj->pos, scale2(mul2(obj->vel, off), dt * (obj->controllerid == 0)));
// wrap at screen boundaries
const int appw = window_width(), apph = window_height();
if( obj->pos.x < 0 ) obj->pos.x += appw; else if( obj->pos.x > appw ) obj->pos.x -= appw;
if( obj->pos.y < 0 ) obj->pos.y += apph; else if( obj->pos.y > apph ) obj->pos.y -= apph;
}
void kid_draw(kid *obj) {
// 4x4 tilesheet
int col = (((int)obj->kid) % 4);
int row = (((int)obj->pos.x / 10 ^ (int)obj->pos.y / 10) % 4);
float position[3] = {obj->pos.x,obj->pos.y,obj->pos.y}; // position(x,y,depth: sort by Y)
float offset[2]={0,0}, scale[2]={1,1};
float coords[3]={col * 4 + row,4,4}; // num_frame(x) in a 4x4(y,z) spritesheet
unsigned flags = 0;
sprite_sheet(obj->texture_, coords, position, obj->angle*TO_DEG, offset, scale, obj->color, flags);
}
int kid_aabb(kid *obj, aabb *box) {
*box = aabb( vec3(obj->pos.x-16,obj->pos.y-16,0), vec3(obj->pos.x+16,obj->pos.y+16,1) );
return 1;
}
int kid_edit(kid *obj) {
aabb box;
if( kid_aabb(obj, &box) ) {
ddraw_color_push(YELLOW);
ddraw_push_2d();
ddraw_aabb(box.min, box.max);
ddraw_pop_2d();
ddraw_color_pop();
}
if( editor_selected(obj) ) {
obj->pos.x += input(KEY_RIGHT) - input(KEY_LEFT);
obj->pos.y += input(KEY_DOWN) - input(KEY_UP);
editor_symbol(obj->pos.x+16,obj->pos.y-16,ICON_MD_VIDEOGAME_ASSET);
}
return 1;
}
void kid_menu(kid *obj, const char *argv) {
ui_label("Private section");
ui_color4("Color_", &obj->color);
ui_texture("Texture_", obj->texture_);
ui_separator();
}
OBJTYPEDEF(kid,201);
AUTORUN {
// reflect
STRUCT(kid, int, kid);
STRUCT(kid, vec2, pos);
STRUCT(kid, vec2, vel);
STRUCT(kid, float, angle, "Tilt degrees");
STRUCT(kid, rgba, color, "Tint color");
STRUCT(kid, char*, filename, "Filename" );
EXTEND(kid, ctor,tick,draw,aabb,edit,menu);
}
void game(unsigned frame, float dt, double t) {
static kid *root;
static kid *o1;
static kid *o2;
static camera_t cam;
if( !frame ) {
// init camera (x,y) (z = zoom)
cam = camera();
cam.position = vec3(window_width()/2,window_height()/2,1);
camera_enable(&cam);
root = obj_make(
"[kid]\n"
"filename=spriteSheetExample.png\n"
"pos=5,2\n"
"angle=pi/12\n"
"color=#ffcf\n"
);
o1 = obj_make(
"[kid]\n"
"filename=spriteSheetExample.png\n"
"pos=1,100\n"
"angle=pi/12\n"
"color=#fcc\n"
);
o2 = obj_make(
"[kid]\n"
"filename=spriteSheetExample.png\n"
"pos=50,200\n"
"angle=pi/12\n"
"color=#ccccffff\n"
);
//obj_setname(root, "root");
obj_setname(o1, "o1");
obj_setname(o2, "o2");
obj*o3 = obj_make(
"[lit]\n"
"pos=300,300,0\n"
"type=1"
);
obj*o4 = obj_new_ext(obj, "o4");
obj*o5 = obj_new_ext(obj, "o5");
obj_attach(root, o1);
obj_attach(o1, o2);
obj_attach(o2, o3);
obj_attach(o1, o4);
obj_attach(root, o5);
editor_watch(root);
}
// camera panning (x,y) & zooming (z)
if(0)
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
}
// tick game
if( dt ) {
kid_tick(root, dt);
kid_tick(o1, dt);
kid_tick(o2, dt);
root->angle = 5 * sin(t+dt);
}
// fps camera
bool active = 0;
if( input_down(MOUSE_M) || input_down(MOUSE_R) ) {
active = ui_hover() || ui_active() || gizmo_active() || editor_first_selected() ? false : true;
} else {
active = !window_has_cursor() && (input(MOUSE_M) || input(MOUSE_R));
}
window_cursor( !active );
if( active ) cam.speed = clampf(cam.speed + input_diff(MOUSE_W) / 10, 0.05f, 5.0f);
vec2 mouse = scale2(vec2(input_diff(MOUSE_X), -input_diff(MOUSE_Y)), 0.2f * active);
vec3 wasdecq = scale3(vec3(input(KEY_D)-input(KEY_A),input(KEY_E)-(input(KEY_C)||input(KEY_Q)),input(KEY_W)-input(KEY_S)), cam.speed);
camera_moveby(&cam, wasdecq);
camera_fps(&cam, mouse.x,mouse.y);
// draw world
ddraw_ontop_push(0);
ddraw_grid(0);
ddraw_ontop_pop();
ddraw_flush();
// draw game
kid_draw(root);
kid_draw(o1);
kid_draw(o2);
}
int main(){
// borderless, see:
// https://github.com/glfw/glfw/pull/1420
// https://github.com/glfw/glfw/pull/987
// https://github.com/glfw/glfw/pull/991
// https://github.com/glfw/glfw/pull/990
window_title("Editor " EDITOR_VERSION);
if( flag("--transparent") )
window_create(101, 0);
else
window_create(80, flag("--windowed") ? 0 : WINDOW_BORDERLESS);
window_icon("scale-ruler-icon.png");
while( window_swap() ) {
editor_frame(game);
editor_gizmos(2);
}
}