main
Dominik Madarász 2023-11-02 10:54:49 +01:00
parent fb515681b1
commit 16e546b2a2
52 changed files with 17441 additions and 12502 deletions

View File

@ -957,23 +957,6 @@ ffi.cdef([[
//lcpp INF [0000] quat: macro name but used as C declaration in:API void printq( quat q );
//lcpp INF [0000] quat: macro name but used as C declaration in:STATIC void printq( quat q );
//lcpp INF [0000] quat: macro name but used as C declaration in: void printq( quat q );
//lcpp INF [0000] vec3: macro name but used as C declaration in:vec3 v;
//lcpp INF [0000] vec3: macro name but used as C declaration in:vec3 result;
//lcpp INF [0000] vec3: macro name but used as C declaration in:API void tween_setkey(tween_t *tw, float t, vec3 v, unsigned easing_mode);
//lcpp INF [0000] vec3: macro name but used as C declaration in:STATIC void tween_setkey(tween_t *tw, float t, vec3 v, unsigned easing_mode);
//lcpp INF [0000] vec3: macro name but used as C declaration in: void tween_setkey(tween_t *tw, float t, vec3 v, unsigned easing_mode);
//lcpp INF [0000] vec3: macro name but used as C declaration in:vec3 position;
//lcpp INF [0000] vec3: macro name but used as C declaration in:vec3 velocity;
//lcpp INF [0000] vec3: macro name but used as C declaration in:vec3 acceleration;
//lcpp INF [0000] vec3: macro name but used as C declaration in:vec3 prev_position;
//lcpp INF [0000] vec3: macro name but used as C declaration in:vec3* steering_targets;
//lcpp INF [0000] map: macro name but used as C declaration in:struct { map base; struct { pair p; vec3* key; array(boid_t*) val; } tmp, *ptr; array(boid_t*)* tmpval; int (*typed_cmp)(vec3*, vec3*); uint64_t (*typed_hash)(vec3*); } * voxel_cache_;
//lcpp INF [0000] vec3: macro name but used as C declaration in:struct { map base; struct { pair p; vec3* key; array(boid_t*) val; } tmp, *ptr; array(boid_t*)* tmpval; int (*typed_cmp)(vec3*, vec3*); uint64_t (*typed_hash)(vec3*); } * voxel_cache_;
//lcpp INF [0000] map: macro name but used as C declaration in:struct { map base; struct { pair p; vec3* key; boid_t** val; } tmp, *ptr; boid_t*** tmpval; int (*typed_cmp)(vec3*, vec3*); uint64_t (*typed_hash)(vec3*); } * voxel_cache_;
//lcpp INF [0000] vec3: macro name but used as C declaration in:struct { map base; struct { pair p; vec3* key; boid_t** val; } tmp, *ptr; boid_t*** tmpval; int (*typed_cmp)(vec3*, vec3*); uint64_t (*typed_hash)(vec3*); } * voxel_cache_;
//lcpp INF [0000] vec3: macro name but used as C declaration in:struct { map base; struct { pair p; vec3* key; boid_t** val; } tmp, *ptr; boid_t*** tmpval; int (*typed_cmp)(vec3*, vec3*); uint64_t (*typed_hash)(vec3*); } * voxel_cache_;
//lcpp INF [0000] vec3: macro name but used as C declaration in:struct { map base; struct { pair p; vec3* key; boid_t** val; } tmp, *ptr; boid_t*** tmpval; int (*typed_cmp)(vec3*, vec3*); uint64_t (*typed_hash)(vec3*); } * voxel_cache_;
//lcpp INF [0000] vec3: macro name but used as C declaration in:struct { map base; struct { pair p; vec3* key; boid_t** val; } tmp, *ptr; boid_t*** tmpval; int (*typed_cmp)(vec3*, vec3*); uint64_t (*typed_hash)(vec3*); } * voxel_cache_;
//lcpp INF [0000] map: macro name but used as C declaration in:API int pathfind_astar(int width, int height, const unsigned* map, vec2i src, vec2i dst, vec2i* path, size_t maxpath);
//lcpp INF [0000] vec2i: macro name but used as C declaration in:API int pathfind_astar(int width, int height, const unsigned* map, vec2i src, vec2i dst, vec2i* path, size_t maxpath);
//lcpp INF [0000] vec2i: macro name but used as C declaration in:API int pathfind_astar(int width, int height, const unsigned* map, vec2i src, vec2i dst, vec2i* path, size_t maxpath);
@ -986,6 +969,18 @@ ffi.cdef([[
//lcpp INF [0000] vec2i: macro name but used as C declaration in: int pathfind_astar(int width, int height, const unsigned* map, vec2i src, vec2i dst, vec2i* path, size_t maxpath);
//lcpp INF [0000] vec2i: macro name but used as C declaration in: int pathfind_astar(int width, int height, const unsigned* map, vec2i src, vec2i dst, vec2i* path, size_t maxpath);
//lcpp INF [0000] vec2i: macro name but used as C declaration in: int pathfind_astar(int width, int height, const unsigned* map, vec2i src, vec2i dst, vec2i* path, size_t maxpath);
//lcpp INF [0000] vec3: macro name but used as C declaration in:vec3 position;
//lcpp INF [0000] vec3: macro name but used as C declaration in:vec3 velocity;
//lcpp INF [0000] vec3: macro name but used as C declaration in:vec3 acceleration;
//lcpp INF [0000] vec3: macro name but used as C declaration in:vec3 prev_position;
//lcpp INF [0000] vec3: macro name but used as C declaration in:vec3* steering_targets;
//lcpp INF [0000] map: macro name but used as C declaration in:struct { map base; struct { pair p; vec3* key; array(boid_t*) val; } tmp, *ptr; array(boid_t*)* tmpval; int (*typed_cmp)(vec3*, vec3*); uint64_t (*typed_hash)(vec3*); } * voxel_cache_;
//lcpp INF [0000] vec3: macro name but used as C declaration in:struct { map base; struct { pair p; vec3* key; array(boid_t*) val; } tmp, *ptr; array(boid_t*)* tmpval; int (*typed_cmp)(vec3*, vec3*); uint64_t (*typed_hash)(vec3*); } * voxel_cache_;
//lcpp INF [0000] map: macro name but used as C declaration in:struct { map base; struct { pair p; vec3* key; boid_t** val; } tmp, *ptr; boid_t*** tmpval; int (*typed_cmp)(vec3*, vec3*); uint64_t (*typed_hash)(vec3*); } * voxel_cache_;
//lcpp INF [0000] vec3: macro name but used as C declaration in:struct { map base; struct { pair p; vec3* key; boid_t** val; } tmp, *ptr; boid_t*** tmpval; int (*typed_cmp)(vec3*, vec3*); uint64_t (*typed_hash)(vec3*); } * voxel_cache_;
//lcpp INF [0000] vec3: macro name but used as C declaration in:struct { map base; struct { pair p; vec3* key; boid_t** val; } tmp, *ptr; boid_t*** tmpval; int (*typed_cmp)(vec3*, vec3*); uint64_t (*typed_hash)(vec3*); } * voxel_cache_;
//lcpp INF [0000] vec3: macro name but used as C declaration in:struct { map base; struct { pair p; vec3* key; boid_t** val; } tmp, *ptr; boid_t*** tmpval; int (*typed_cmp)(vec3*, vec3*); uint64_t (*typed_hash)(vec3*); } * voxel_cache_;
//lcpp INF [0000] vec3: macro name but used as C declaration in:struct { map base; struct { pair p; vec3* key; boid_t** val; } tmp, *ptr; boid_t*** tmpval; int (*typed_cmp)(vec3*, vec3*); uint64_t (*typed_hash)(vec3*); } * voxel_cache_;
//lcpp INF [0000] vec3: macro name but used as C declaration in:vec3 a;
//lcpp INF [0000] vec3: macro name but used as C declaration in:vec3 b;
//lcpp INF [0000] vec3: macro name but used as C declaration in:vec3 a;
@ -1135,7 +1130,6 @@ ffi.cdef([[
//lcpp INF [0000] vec3: macro name but used as C declaration in: int gizmo(vec3 *pos, vec3 *rot, vec3 *sca);
//lcpp INF [0000] vec3: macro name but used as C declaration in: int gizmo(vec3 *pos, vec3 *rot, vec3 *sca);
//lcpp INF [0000] vec3: macro name but used as C declaration in: int gizmo(vec3 *pos, vec3 *rot, vec3 *sca);
//lcpp INF [0000] map: macro name but used as C declaration in:typedef struct { map base; struct { pair p; char* key; char* val; } tmp, *ptr; char** tmpval; int (*typed_cmp)(char*, char*); uint64_t (*typed_hash)(char*); } * ini_t;
//lcpp INF [0000] vec2: macro name but used as C declaration in:API vec2 font_xy();
//lcpp INF [0000] vec2: macro name but used as C declaration in:STATIC vec2 font_xy();
//lcpp INF [0000] vec2: macro name but used as C declaration in: vec2 font_xy();
@ -1148,6 +1142,49 @@ ffi.cdef([[
//lcpp INF [0000] vec2: macro name but used as C declaration in:API vec2 font_highlight(const char *text, const void *colors);
//lcpp INF [0000] vec2: macro name but used as C declaration in:STATIC vec2 font_highlight(const char *text, const void *colors);
//lcpp INF [0000] vec2: macro name but used as C declaration in: vec2 font_highlight(const char *text, const void *colors);
//lcpp INF [0000] map: macro name but used as C declaration in:typedef struct { map base; struct { pair p; char* key; char* val; } tmp, *ptr; char** tmpval; int (*typed_cmp)(char*, char*); uint64_t (*typed_hash)(char*); } * ini_t;
//lcpp INF [0000] vec2: macro name but used as C declaration in:API vec2 input2( int vk );
//lcpp INF [0000] vec2: macro name but used as C declaration in:STATIC vec2 input2( int vk );
//lcpp INF [0000] vec2: macro name but used as C declaration in: vec2 input2( int vk );
//lcpp INF [0000] vec2: macro name but used as C declaration in:API vec2 input_diff2( int vk );
//lcpp INF [0000] vec2: macro name but used as C declaration in:STATIC vec2 input_diff2( int vk );
//lcpp INF [0000] vec2: macro name but used as C declaration in: vec2 input_diff2( int vk );
//lcpp INF [0000] vec2: macro name but used as C declaration in:API vec2 input_frame2( int vk, int Nth_frame );
//lcpp INF [0000] vec2: macro name but used as C declaration in:STATIC vec2 input_frame2( int vk, int Nth_frame );
//lcpp INF [0000] vec2: macro name but used as C declaration in: vec2 input_frame2( int vk, int Nth_frame );
//lcpp INF [0000] vec2: macro name but used as C declaration in:API vec2 input_filter_positive2( vec2 v );
//lcpp INF [0000] vec2: macro name but used as C declaration in:API vec2 input_filter_positive2( vec2 v );
//lcpp INF [0000] vec2: macro name but used as C declaration in:STATIC vec2 input_filter_positive2( vec2 v );
//lcpp INF [0000] vec2: macro name but used as C declaration in:STATIC vec2 input_filter_positive2( vec2 v );
//lcpp INF [0000] vec2: macro name but used as C declaration in: vec2 input_filter_positive2( vec2 v );
//lcpp INF [0000] vec2: macro name but used as C declaration in: vec2 input_filter_positive2( vec2 v );
//lcpp INF [0000] vec2: macro name but used as C declaration in:API vec2 input_filter_deadzone( vec2 v, float deadzone_treshold );
//lcpp INF [0000] vec2: macro name but used as C declaration in:API vec2 input_filter_deadzone( vec2 v, float deadzone_treshold );
//lcpp INF [0000] vec2: macro name but used as C declaration in:STATIC vec2 input_filter_deadzone( vec2 v, float deadzone_treshold );
//lcpp INF [0000] vec2: macro name but used as C declaration in:STATIC vec2 input_filter_deadzone( vec2 v, float deadzone_treshold );
//lcpp INF [0000] vec2: macro name but used as C declaration in: vec2 input_filter_deadzone( vec2 v, float deadzone_treshold );
//lcpp INF [0000] vec2: macro name but used as C declaration in: vec2 input_filter_deadzone( vec2 v, float deadzone_treshold );
//lcpp INF [0000] vec2: macro name but used as C declaration in:API vec2 input_filter_deadzone_4way( vec2 v, float deadzone_treshold );
//lcpp INF [0000] vec2: macro name but used as C declaration in:API vec2 input_filter_deadzone_4way( vec2 v, float deadzone_treshold );
//lcpp INF [0000] vec2: macro name but used as C declaration in:STATIC vec2 input_filter_deadzone_4way( vec2 v, float deadzone_treshold );
//lcpp INF [0000] vec2: macro name but used as C declaration in:STATIC vec2 input_filter_deadzone_4way( vec2 v, float deadzone_treshold );
//lcpp INF [0000] vec2: macro name but used as C declaration in: vec2 input_filter_deadzone_4way( vec2 v, float deadzone_treshold );
//lcpp INF [0000] vec2: macro name but used as C declaration in: vec2 input_filter_deadzone_4way( vec2 v, float deadzone_treshold );
//lcpp INF [0000] vec2: macro name but used as C declaration in:API void input_touch_area(unsigned button, vec2 begin_coord_ndc, vec2 end_coord_ndc);
//lcpp INF [0000] vec2: macro name but used as C declaration in:API void input_touch_area(unsigned button, vec2 begin_coord_ndc, vec2 end_coord_ndc);
//lcpp INF [0000] vec2: macro name but used as C declaration in:STATIC void input_touch_area(unsigned button, vec2 begin_coord_ndc, vec2 end_coord_ndc);
//lcpp INF [0000] vec2: macro name but used as C declaration in:STATIC void input_touch_area(unsigned button, vec2 begin_coord_ndc, vec2 end_coord_ndc);
//lcpp INF [0000] vec2: macro name but used as C declaration in: void input_touch_area(unsigned button, vec2 begin_coord_ndc, vec2 end_coord_ndc);
//lcpp INF [0000] vec2: macro name but used as C declaration in: void input_touch_area(unsigned button, vec2 begin_coord_ndc, vec2 end_coord_ndc);
//lcpp INF [0000] vec2: macro name but used as C declaration in:API vec2 input_touch(unsigned button, float sensitivity);
//lcpp INF [0000] vec2: macro name but used as C declaration in:STATIC vec2 input_touch(unsigned button, float sensitivity);
//lcpp INF [0000] vec2: macro name but used as C declaration in: vec2 input_touch(unsigned button, float sensitivity);
//lcpp INF [0000] vec2: macro name but used as C declaration in:API vec2 input_touch_delta(unsigned button, float sensitivity);
//lcpp INF [0000] vec2: macro name but used as C declaration in:STATIC vec2 input_touch_delta(unsigned button, float sensitivity);
//lcpp INF [0000] vec2: macro name but used as C declaration in: vec2 input_touch_delta(unsigned button, float sensitivity);
//lcpp INF [0000] vec2: macro name but used as C declaration in:API vec2 input_touch_delta_from_origin(unsigned button, float sensitivity);
//lcpp INF [0000] vec2: macro name but used as C declaration in:STATIC vec2 input_touch_delta_from_origin(unsigned button, float sensitivity);
//lcpp INF [0000] vec2: macro name but used as C declaration in: vec2 input_touch_delta_from_origin(unsigned button, float sensitivity);
//lcpp INF [0000] vec2: macro name but used as C declaration in:API char* ftoa2(vec2 v);
//lcpp INF [0000] vec2: macro name but used as C declaration in:STATIC char* ftoa2(vec2 v);
//lcpp INF [0000] vec2: macro name but used as C declaration in: char* ftoa2(vec2 v);
@ -1196,48 +1233,6 @@ ffi.cdef([[
//lcpp INF [0000] vec4: macro name but used as C declaration in:STATIC void swapf4(vec4 *a, vec4 *b);
//lcpp INF [0000] vec4: macro name but used as C declaration in: void swapf4(vec4 *a, vec4 *b);
//lcpp INF [0000] vec4: macro name but used as C declaration in: void swapf4(vec4 *a, vec4 *b);
//lcpp INF [0000] vec2: macro name but used as C declaration in:API vec2 input2( int vk );
//lcpp INF [0000] vec2: macro name but used as C declaration in:STATIC vec2 input2( int vk );
//lcpp INF [0000] vec2: macro name but used as C declaration in: vec2 input2( int vk );
//lcpp INF [0000] vec2: macro name but used as C declaration in:API vec2 input_diff2( int vk );
//lcpp INF [0000] vec2: macro name but used as C declaration in:STATIC vec2 input_diff2( int vk );
//lcpp INF [0000] vec2: macro name but used as C declaration in: vec2 input_diff2( int vk );
//lcpp INF [0000] vec2: macro name but used as C declaration in:API vec2 input_frame2( int vk, int Nth_frame );
//lcpp INF [0000] vec2: macro name but used as C declaration in:STATIC vec2 input_frame2( int vk, int Nth_frame );
//lcpp INF [0000] vec2: macro name but used as C declaration in: vec2 input_frame2( int vk, int Nth_frame );
//lcpp INF [0000] vec2: macro name but used as C declaration in:API vec2 input_filter_positive2( vec2 v );
//lcpp INF [0000] vec2: macro name but used as C declaration in:API vec2 input_filter_positive2( vec2 v );
//lcpp INF [0000] vec2: macro name but used as C declaration in:STATIC vec2 input_filter_positive2( vec2 v );
//lcpp INF [0000] vec2: macro name but used as C declaration in:STATIC vec2 input_filter_positive2( vec2 v );
//lcpp INF [0000] vec2: macro name but used as C declaration in: vec2 input_filter_positive2( vec2 v );
//lcpp INF [0000] vec2: macro name but used as C declaration in: vec2 input_filter_positive2( vec2 v );
//lcpp INF [0000] vec2: macro name but used as C declaration in:API vec2 input_filter_deadzone( vec2 v, float deadzone_treshold );
//lcpp INF [0000] vec2: macro name but used as C declaration in:API vec2 input_filter_deadzone( vec2 v, float deadzone_treshold );
//lcpp INF [0000] vec2: macro name but used as C declaration in:STATIC vec2 input_filter_deadzone( vec2 v, float deadzone_treshold );
//lcpp INF [0000] vec2: macro name but used as C declaration in:STATIC vec2 input_filter_deadzone( vec2 v, float deadzone_treshold );
//lcpp INF [0000] vec2: macro name but used as C declaration in: vec2 input_filter_deadzone( vec2 v, float deadzone_treshold );
//lcpp INF [0000] vec2: macro name but used as C declaration in: vec2 input_filter_deadzone( vec2 v, float deadzone_treshold );
//lcpp INF [0000] vec2: macro name but used as C declaration in:API vec2 input_filter_deadzone_4way( vec2 v, float deadzone_treshold );
//lcpp INF [0000] vec2: macro name but used as C declaration in:API vec2 input_filter_deadzone_4way( vec2 v, float deadzone_treshold );
//lcpp INF [0000] vec2: macro name but used as C declaration in:STATIC vec2 input_filter_deadzone_4way( vec2 v, float deadzone_treshold );
//lcpp INF [0000] vec2: macro name but used as C declaration in:STATIC vec2 input_filter_deadzone_4way( vec2 v, float deadzone_treshold );
//lcpp INF [0000] vec2: macro name but used as C declaration in: vec2 input_filter_deadzone_4way( vec2 v, float deadzone_treshold );
//lcpp INF [0000] vec2: macro name but used as C declaration in: vec2 input_filter_deadzone_4way( vec2 v, float deadzone_treshold );
//lcpp INF [0000] vec2: macro name but used as C declaration in:API void input_touch_area(unsigned button, vec2 begin_coord_ndc, vec2 end_coord_ndc);
//lcpp INF [0000] vec2: macro name but used as C declaration in:API void input_touch_area(unsigned button, vec2 begin_coord_ndc, vec2 end_coord_ndc);
//lcpp INF [0000] vec2: macro name but used as C declaration in:STATIC void input_touch_area(unsigned button, vec2 begin_coord_ndc, vec2 end_coord_ndc);
//lcpp INF [0000] vec2: macro name but used as C declaration in:STATIC void input_touch_area(unsigned button, vec2 begin_coord_ndc, vec2 end_coord_ndc);
//lcpp INF [0000] vec2: macro name but used as C declaration in: void input_touch_area(unsigned button, vec2 begin_coord_ndc, vec2 end_coord_ndc);
//lcpp INF [0000] vec2: macro name but used as C declaration in: void input_touch_area(unsigned button, vec2 begin_coord_ndc, vec2 end_coord_ndc);
//lcpp INF [0000] vec2: macro name but used as C declaration in:API vec2 input_touch(unsigned button, float sensitivity);
//lcpp INF [0000] vec2: macro name but used as C declaration in:STATIC vec2 input_touch(unsigned button, float sensitivity);
//lcpp INF [0000] vec2: macro name but used as C declaration in: vec2 input_touch(unsigned button, float sensitivity);
//lcpp INF [0000] vec2: macro name but used as C declaration in:API vec2 input_touch_delta(unsigned button, float sensitivity);
//lcpp INF [0000] vec2: macro name but used as C declaration in:STATIC vec2 input_touch_delta(unsigned button, float sensitivity);
//lcpp INF [0000] vec2: macro name but used as C declaration in: vec2 input_touch_delta(unsigned button, float sensitivity);
//lcpp INF [0000] vec2: macro name but used as C declaration in:API vec2 input_touch_delta_from_origin(unsigned button, float sensitivity);
//lcpp INF [0000] vec2: macro name but used as C declaration in:STATIC vec2 input_touch_delta_from_origin(unsigned button, float sensitivity);
//lcpp INF [0000] vec2: macro name but used as C declaration in: vec2 input_touch_delta_from_origin(unsigned button, float sensitivity);
//lcpp INF [0000] map: macro name but used as C declaration in:typedef struct { map base; struct { pair p; char * key; struct profile_t val; } tmp, *ptr; struct profile_t* tmpval; int (*typed_cmp)(char *, char *); uint64_t (*typed_hash)(char *); } * profiler_t;
//lcpp INF [0000] vec4: macro name but used as C declaration in:vec4 color;
//lcpp INF [0000] vec4: macro name but used as C declaration in:API void sprite_rect( texture_t t, vec4 rect, float zindex, vec3 pos, float tilt_deg, unsigned tint_rgba);
@ -1514,6 +1509,11 @@ ffi.cdef([[
//lcpp INF [0000] vec3: macro name but used as C declaration in: void light_dir(light_t* l, vec3 dir);
//lcpp INF [0000] vec2i: macro name but used as C declaration in:vec2i* entries;
//lcpp INF [0000] vec3i: macro name but used as C declaration in:typedef vec3i guid;
//lcpp INF [0000] vec3: macro name but used as C declaration in:vec3 v;
//lcpp INF [0000] vec3: macro name but used as C declaration in:vec3 result;
//lcpp INF [0000] vec3: macro name but used as C declaration in:API void tween_setkey(tween_t *tw, float t, vec3 v, unsigned easing_mode);
//lcpp INF [0000] vec3: macro name but used as C declaration in:STATIC void tween_setkey(tween_t *tw, float t, vec3 v, unsigned easing_mode);
//lcpp INF [0000] vec3: macro name but used as C declaration in: void tween_setkey(tween_t *tw, float t, vec3 v, unsigned easing_mode);
//lcpp INF [0000] test: macro name but used as C declaration in:API int (test)(const char *file, int line, const char *expr, bool result);
//lcpp INF [0000] test: macro name but used as C declaration in:STATIC int (test)(const char *file, int line, const char *expr, bool result);
//lcpp INF [0000] test: macro name but used as C declaration in: int (test)(const char *file, int line, const char *expr, bool result);
@ -1790,81 +1790,118 @@ typedef float mat44[16];
void print33( float *m );
void print34( float *m );
void print44( float *m );
float ease_nop(float t);
float ease_linear(float t);
float ease_out_sine(float t);
float ease_out_quad(float t);
float ease_out_cubic(float t);
float ease_out_quart(float t);
float ease_out_quint(float t);
float ease_out_expo(float t);
float ease_out_circ(float t);
float ease_out_back(float t);
float ease_out_elastic(float t);
float ease_out_bounce(float t);
float ease_in_sine(float t);
float ease_in_quad(float t);
float ease_in_cubic(float t);
float ease_in_quart(float t);
float ease_in_quint(float t);
float ease_in_expo(float t);
float ease_in_circ(float t);
float ease_in_back(float t);
float ease_in_elastic(float t);
float ease_in_bounce(float t);
float ease_inout_sine(float t);
float ease_inout_quad(float t);
float ease_inout_cubic(float t);
float ease_inout_quart(float t);
float ease_inout_quint(float t);
float ease_inout_expo(float t);
float ease_inout_circ(float t);
float ease_inout_back(float t);
float ease_inout_elastic(float t);
float ease_inout_bounce(float t);
float ease_inout_perlin(float t);
enum EASE_FLAGS {
EASE_SINE,
EASE_QUAD,
EASE_CUBIC,
EASE_QUART,
EASE_QUINT,
EASE_EXPO,
EASE_CIRC,
EASE_BACK,
EASE_ELASTIC,
EASE_BOUNCE,
EASE_IN,
EASE_OUT = 0,
EASE_INOUT = EASE_IN * 2,
EASE_NOP = EASE_INOUT | (EASE_BOUNCE + 1),
EASE_LINEAR,
EASE_INOUT_PERLIN,
EASE_NUM
uintptr_t id_make(void *ptr);
void * id_handle(uintptr_t id);
void id_dispose(uintptr_t id);
bool id_valid(uintptr_t id);
typedef struct obj { struct { ifdef(debug, const char *objname;) union { uintptr_t objheader; struct { uintptr_t objtype:8; uintptr_t objsizew:8; uintptr_t objrefs:8; uintptr_t objheap:1; uintptr_t objcomps:1; uintptr_t objunused:64-8-8-8-1-1-16-3; uintptr_t objid:16+3; }; }; struct obj** objchildren; }; } obj;
typedef struct entity { struct { struct { ifdef(debug, const char *objname;) union { uintptr_t objheader; struct { uintptr_t objtype:8; uintptr_t objsizew:8; uintptr_t objrefs:8; uintptr_t objheap:1; uintptr_t objcomps:1; uintptr_t objunused:64-8-8-8-1-1-16-3; uintptr_t objid:16+3; }; }; struct obj** objchildren; }; union { struct { uintptr_t objenabled:32, objflagged:32; }; uintptr_t cflags; }; void *c[32]; }; } entity;
obj *objtmp;
void* obj_malloc(unsigned sz);
void* obj_free(void *o);
extern void (*obj_ctor[256])();
extern void (*obj_dtor[256])();
extern char* (*obj_save[256])();
extern bool (*obj_load[256])();
extern int (*obj_test[256])();
extern int (*obj_init[256])();
extern int (*obj_quit[256])();
extern int (*obj_tick[256])();
extern int (*obj_draw[256])();
extern int (*obj_lerp[256])();
extern int (*obj_edit[256])();
extern int (*obj_aabb[256])();
uintptr_t obj_header(const void *o);
uintptr_t obj_id(const void *o);
const char* obj_type(const void *o);
unsigned obj_typeid(const void *o);
int obj_sizeof(const void *o);
int obj_size(const void *o);
char* obj_data(void *o);
const char* obj_datac(const void *o);
void* obj_payload(const void *o);
void* obj_zero(void *o);
void* obj_ref(void *oo);
void* obj_unref(void *oo);
obj* obj_detach(void *c);
obj* obj_attach(void *o, void *c);
obj* obj_root(const void *o);
obj* obj_parent(const void *o);
obj***obj_children(const void *o);
obj***obj_siblings(const void *o);
int obj_dumptree(const void *o);
void* obj_setmeta(void *o, const char *key, const char *value);
const char* obj_meta(const void *o, const char *key);
void* obj_setname(void *o, const char *name);
const char* obj_name(const void *o);
void* obj_swap(void *dst, void *src);
void* obj_copy_fast(void *dst, const void *src);
void* obj_copy(void *dst, const void *src);
int obj_comp_fast(const void *a, const void *b);
int obj_comp(const void *a, const void *b);
int obj_lesser(const void *a, const void *b);
int obj_greater(const void *a, const void *b);
int obj_equal(const void *a, const void *b);
uint64_t obj_hash(const void *o);
bool obj_hexdump(const void *oo);
int obj_print(const void *o);
int obj_printf(const void *o, const char *text);
int obj_console(const void *o);
char* obj_saveini(const void *o);
obj* obj_mergeini(void *o, const char *ini);
obj* obj_loadini(void *o, const char *ini);
char* obj_savejson(const void *o);
obj* obj_mergejson(void *o, const char *json);
obj* obj_loadjson(void *o, const char *json);
char* obj_savebin(const void *o);
obj* obj_mergebin(void *o, const char *sav);
obj* obj_loadbin(void *o, const char *sav);
char* obj_savempack(const void *o);
obj* obj_mergempack(void *o, const char *sav);
obj* obj_loadmpack(void *o, const char *sav);
int obj_push(const void *o);
int obj_pop(void *o);
bool obj_addcomponent(entity *e, unsigned c, void *ptr);
bool obj_hascomponent(entity *e, unsigned c);
void* obj_getcomponent(entity *e, unsigned c);
bool obj_delcomponent(entity *e, unsigned c);
bool obj_usecomponent(entity *e, unsigned c);
bool obj_offcomponent(entity *e, unsigned c);
char* entity_save(entity *self);
void* obj_clone(const void *src);
void* obj_merge(void *dst, const void *src);
void* obj_mutate(void *dst, const void *src);
void* obj_make(const char *str);
typedef enum OBJTYPE_BUILTINS {
OBJTYPE_obj = 0,
OBJTYPE_entity = 1,
OBJTYPE_vec2 = 2,
OBJTYPE_vec3 = 3,
OBJTYPE_vec4 = 4,
OBJTYPE_quat = 5,
OBJTYPE_mat33 = 6,
OBJTYPE_mat34 = 7,
OBJTYPE_mat44 = 8,
OBJTYPE_vec2i = 9,
OBJTYPE_vec3i = 10,
} OBJTYPE_BUILTINS;
int pathfind_astar(int width, int height, const unsigned* map, vec2i src, vec2i dst, vec2i* path, size_t maxpath);
typedef int (*bt_func)();
typedef struct bt_t {
uint64_t type;
int (*action)();
union {
int argi;
float argf;
};
float ease(float t01, unsigned fn);
float ease_pong(float t01, unsigned fn);
float ease_ping_pong(float t, unsigned fn1, unsigned fn2);
float ease_pong_ping(float t, unsigned fn1, unsigned fn2);
const char *ease_enum(unsigned fn);
const char**ease_enums();
typedef struct tween_keyframe_t {
float t;
vec3 v;
unsigned ease;
} tween_keyframe_t;
typedef struct tween_t {
tween_keyframe_t* keyframes;
vec3 result;
float time;
float duration;
} tween_t;
tween_t tween();
void tween_setkey(tween_t *tw, float t, vec3 v, unsigned easing_mode);
void tween_delkey(tween_t *tw, float t);
float tween_update(tween_t *tw, float dt);
void tween_reset(tween_t *tw);
void tween_destroy(tween_t *tw);
struct bt_t* children;
} bt_t;
bt_t bt(const char *ini_file, unsigned flags);
int bt_run(bt_t *b);
void bt_addfun(const char *name, int(*func)());
bt_func bt_findfun(const char *name);
char *bt_funcname(bt_func fn);
int ui_bt(bt_t *b);
typedef enum SWARM_DISTANCE {
SWARM_DISTANCE_LINEAR,
SWARM_DISTANCE_INVERSE_LINEAR,
@ -1898,23 +1935,6 @@ float blindspot_angledeg_compare_value_;
void swarm_update_acceleration_only(swarm_t *self);
void swarm_update_acceleration_and_velocity_only(swarm_t *self, float delta);
int ui_swarm(swarm_t *self);
int pathfind_astar(int width, int height, const unsigned* map, vec2i src, vec2i dst, vec2i* path, size_t maxpath);
typedef int (*bt_func)();
typedef struct bt_t {
uint64_t type;
int (*action)();
union {
int argi;
float argf;
};
struct bt_t* children;
} bt_t;
bt_t bt(const char *ini_file, unsigned flags);
int bt_run(bt_t *b);
void bt_addfun(const char *name, int(*func)());
bt_func bt_findfun(const char *name);
char *bt_funcname(bt_func fn);
int ui_bt(bt_t *b);
void midi_send(unsigned midi_msg);
typedef struct audio_handle* audio_t;
audio_t audio_clip( const char *pathfile );
@ -1948,52 +1968,6 @@ AUDIO_MULTIPLE_INSTANCES = 0,
AUDIO_SINGLE_INSTANCE = 512,
};
int audio_queue( const void *samples, int num_samples, int flags );
enum COMPRESS_FLAGS {
COMPRESS_RAW = 0,
COMPRESS_PPP = (1<<4),
COMPRESS_ULZ = (2<<4),
COMPRESS_LZ4 = (3<<4),
COMPRESS_CRUSH = (4<<4),
COMPRESS_DEFLATE = (5<<4),
COMPRESS_LZP1 = (6<<4),
COMPRESS_LZMA = (7<<4),
COMPRESS_BALZ = (8<<4),
COMPRESS_LZW3 = (9<<4),
COMPRESS_LZSS = (10<<4),
COMPRESS_BCM = (11<<4),
COMPRESS_ZLIB = (12<<4),
};
unsigned zbounds(unsigned inlen, unsigned flags);
unsigned zencode(void *out, unsigned outlen, const void *in, unsigned inlen, unsigned flags);
unsigned zexcess(unsigned flags);
unsigned zdecode(void *out, unsigned outlen, const void *in, unsigned inlen, unsigned flags);
void *interleave( void *out, const void *list, int list_count, int sizeof_item, unsigned columns );
unsigned cobs_bounds(unsigned len);
unsigned cobs_encode(const void *in, unsigned inlen, void *out, unsigned outlen);
unsigned cobs_decode(const void *in, unsigned inlen, void *out, unsigned outlen);
unsigned base92_encode(const void *in, unsigned inlen, void* out, unsigned outlen);
unsigned base92_decode(const void *in, unsigned inlen, void* out, unsigned outlen);
unsigned base92_bounds(unsigned inlen);
unsigned netstring_bounds(unsigned inlen);
unsigned netstring_encode(const char *in, unsigned inlen, char *out, unsigned outlen);
unsigned netstring_decode(const char *in, unsigned inlen, char *out, unsigned outlen);
void delta8_encode(void *buffer, unsigned count);
void delta8_decode(void *buffer, unsigned count);
void delta16_encode(void *buffer, unsigned count);
void delta16_decode(void *buffer, unsigned count);
void delta32_encode(void *buffer, unsigned count);
void delta32_decode(void *buffer, unsigned count);
void delta64_encode(void *buffer, unsigned count);
void delta64_decode(void *buffer, unsigned count);
uint64_t zig64( int64_t value );
int64_t zag64( uint64_t value );
uint32_t enczig32u( int32_t n);
uint64_t enczig64u( int64_t n);
int32_t deczig32i(uint32_t n);
int64_t deczig64i(uint64_t n);
void *arc4( void *buffer, unsigned buflen, const void *pass, unsigned passlen );
uint64_t crc64(uint64_t h, const void *ptr, uint64_t len);
void entropy( void *buf, unsigned n );
typedef struct gjk_support {
int aid, bid;
vec3 a;
@ -2128,6 +2102,13 @@ typedef union json_t { char* s; double f; int64_t i; uintptr_t p; union json_t*
void xml_pop();
bool data_tests();
void* dll(const char *filename, const char *symbol);
void script_init();
void script_run(const char *script);
void script_runfile(const char *pathfile);
void script_bind_class(const char *objname, int num_methods, const char **c_names, void **c_functions);
void script_bind_function(const char *c_name, void *c_function);
void script_call(const char *lua_function);
bool script_tests();
vec3 editor_pick(float mouse_x, float mouse_y);
char* editor_path(const char *path);
float* engine_getf(const char *key);
@ -2150,6 +2131,38 @@ typedef union json_t { char* s; double f; int64_t i; uintptr_t p; union json_t*
char* kit_translate2( const char *id, const char *langcode_iso639_1 );
void kit_locale( const char *langcode_iso639_1 );
char* kit_translate( const char *id );
enum FONT_FLAGS {
FONT_512 =0,
FONT_1024 =1,
FONT_2048 =2,
FONT_4096 =4,
FONT_NO_OVERSAMPLE =0,
FONT_OVERSAMPLE_X =8,
FONT_OVERSAMPLE_Y =16,
FONT_ASCII =2048,
FONT_AR =4096,
FONT_ZH =8192,
FONT_EL =16384,
FONT_EM =32768,
FONT_EU =65536,
FONT_HE =131072,
FONT_JP =262144,
FONT_KR =524288,
FONT_RU =1048576,
FONT_TH =2097152,
FONT_VI =4194304,
FONT_CJK = FONT_ZH|FONT_JP|FONT_KR,
};
void font_face(const char *face_tag, const char *filename_ttf, float font_size, unsigned flags);
void font_face_from_mem(const char *tag, const void *ttf_buffer, unsigned ttf_len, float font_size, unsigned flags);
void font_scales(const char *face_tag, float h1, float h2, float h3, float h4, float h5, float h6);
void font_color(const char *color_tag, uint32_t color);
vec2 font_xy();
void font_goto(float x, float y);
vec2 font_print(const char *text);
vec2 font_rect(const char *text);
void* font_colorize(const char *text, const char *comma_types, const char *comma_keywords);
vec2 font_highlight(const char *text, const void *colors);
char** file_list( const char *pathmasks );
bool file_write( const char *file, const void *ptr, int len );
bool file_append( const char *file, const void *ptr, int len );
@ -2197,159 +2210,6 @@ typedef struct { map base; struct { pair p; char* key; char* val; } tmp, *ptr; c
ini_t ini_from_mem(const char *data);
void ini_destroy(ini_t);
bool ini_write(const char *filename, const char *section, const char *key, const char *value);
enum FONT_FLAGS {
FONT_512 =0,
FONT_1024 =1,
FONT_2048 =2,
FONT_4096 =4,
FONT_NO_OVERSAMPLE =0,
FONT_OVERSAMPLE_X =8,
FONT_OVERSAMPLE_Y =16,
FONT_ASCII =2048,
FONT_AR =4096,
FONT_ZH =8192,
FONT_EL =16384,
FONT_EM =32768,
FONT_EU =65536,
FONT_HE =131072,
FONT_JP =262144,
FONT_KR =524288,
FONT_RU =1048576,
FONT_TH =2097152,
FONT_VI =4194304,
FONT_CJK = FONT_ZH|FONT_JP|FONT_KR,
};
void font_face(const char *face_tag, const char *filename_ttf, float font_size, unsigned flags);
void font_face_from_mem(const char *tag, const void *ttf_buffer, unsigned ttf_len, float font_size, unsigned flags);
void font_scales(const char *face_tag, float h1, float h2, float h3, float h4, float h5, float h6);
void font_color(const char *color_tag, uint32_t color);
vec2 font_xy();
void font_goto(float x, float y);
vec2 font_print(const char *text);
vec2 font_rect(const char *text);
void* font_colorize(const char *text, const char *comma_types, const char *comma_keywords);
vec2 font_highlight(const char *text, const void *colors);
uintptr_t id_make(void *ptr);
void * id_handle(uintptr_t id);
void id_dispose(uintptr_t id);
bool id_valid(uintptr_t id);
int semver( int major, int minor, int patch );
int semvercmp( int v1, int v2 );
typedef struct byte2 { uint8_t x,y; } byte2;
typedef struct byte3 { uint8_t x,y,z; } byte3;
typedef struct byte4 { uint8_t x,y,z,w; } byte4;
typedef struct int2 { int x,y; } int2;
typedef struct int3 { int x,y,z; } int3;
typedef struct int4 { int x,y,z,w; } int4;
typedef struct uint2 { unsigned int x,y; } uint2;
typedef struct uint3 { unsigned int x,y,z; } uint3;
typedef struct uint4 { unsigned int x,y,z,w; } uint4;
typedef struct float2 { float x,y; } float2;
typedef struct float3 { float x,y,z; } float3;
typedef struct float4 { float x,y,z,w; } float4;
typedef struct double2 { double x,y; } double2;
typedef struct double3 { double x,y,z; } double3;
typedef struct double4 { double x,y,z,w; } double4;
char *cc4str(unsigned cc);
char *cc8str(uint64_t cc);
enum {
cc__1 = '1', cc__2, cc__3, cc__4, cc__5, cc__6,cc__7, cc__8, cc__9, cc__0, cc___, cc__ = ' ',
cc__A = 'A', cc__B, cc__C, cc__D, cc__E, cc__F,cc__G, cc__H, cc__I, cc__J, cc__K,cc__L, cc__M, cc__N, cc__O, cc__P,cc__Q, cc__R, cc__S, cc__T, cc__U,cc__V, cc__W, cc__X, cc__Y, cc__Z,
cc__a = 'a', cc__b, cc__c, cc__d, cc__e, cc__f,cc__g, cc__h, cc__i, cc__j, cc__k,cc__l, cc__m, cc__n, cc__o, cc__p,cc__q, cc__r, cc__s, cc__t, cc__u,cc__v, cc__w, cc__x, cc__y, cc__z,
};
char* ftoa1(float v);
char* ftoa2(vec2 v);
char* ftoa3(vec3 v);
char* ftoa4(vec4 v);
float atof1(const char *s);
vec2 atof2(const char *s);
vec3 atof3(const char *s);
vec4 atof4(const char *s);
char* itoa1(int v);
char* itoa2(vec2i v);
char* itoa3(vec3i v);
int atoi1(const char *s);
vec2i atoi2(const char *s);
vec3i atoi3(const char *s);
int is_big();
int is_little();
uint16_t swap16( uint16_t x );
uint32_t swap32( uint32_t x );
uint64_t swap64( uint64_t x );
float swap32f(float n);
double swap64f(double n);
void swapf(float *a, float *b);
void swapf2(vec2 *a, vec2 *b);
void swapf3(vec3 *a, vec3 *b);
void swapf4(vec4 *a, vec4 *b);
uint16_t lil16(uint16_t n);
uint32_t lil32(uint32_t n);
uint64_t lil64(uint64_t n);
float lil32f(float n);
double lil64f(double n);
uint16_t big16(uint16_t n);
uint32_t big32(uint32_t n);
uint64_t big64(uint64_t n);
float big32f(float n);
double big64f(double n);
uint16_t* lil16p(void *p, int sz);
uint32_t* lil32p(void *p, int sz);
uint64_t* lil64p(void *p, int sz);
float * lil32pf(void *p, int sz);
double * lil64pf(void *p, int sz);
uint16_t* big16p(void *p, int sz);
uint32_t* big32p(void *p, int sz);
uint64_t* big64p(void *p, int sz);
float * big32pf(void *p, int sz);
double * big64pf(void *p, int sz);
typedef uint16_t half;
float half_to_float(half value);
half float_to_half(float value);
void pack16i(uint8_t *buf, uint16_t i, int swap);
void pack32i(uint8_t *buf, uint32_t i, int swap);
void pack64i(uint8_t *buf, uint64_t i, int swap);
int16_t unpack16i(const uint8_t *buf, int swap);
int32_t unpack32i(const uint8_t *buf, int swap);
int64_t unpack64i(const uint8_t *buf, int swap);
uint64_t pack754(long double f, unsigned bits, unsigned expbits);
long double unpack754(uint64_t i, unsigned bits, unsigned expbits);
uint64_t pack64uv( uint8_t *buffer, uint64_t value );
uint64_t unpack64uv( const uint8_t *buffer, uint64_t *value );
uint64_t pack64iv( uint8_t *buffer, int64_t value_ );
uint64_t unpack64iv( const uint8_t *buffer, int64_t *value );
int msgpack(const char *fmt, ... );
int msgunpack(const char *fmt, ... );
int msgpack_new(uint8_t *w, size_t l);
int msgpack_nil();
int msgpack_chr(bool n);
int msgpack_uns(uint64_t n);
int msgpack_int(int64_t n);
int msgpack_str(const char *s);
int msgpack_bin(const char *s, size_t n);
int msgpack_flt(double g);
int msgpack_ext(uint8_t key, void *val, size_t n);
int msgpack_arr(uint32_t n);
int msgpack_map(uint32_t n);
int msgpack_eof();
int msgpack_err();
bool msgunpack_new( const void *opaque_or_FILE, size_t bytes );
bool msgunpack_nil();
bool msgunpack_chr(bool *chr);
bool msgunpack_uns(uint64_t *uns);
bool msgunpack_int(int64_t *sig);
bool msgunpack_str(char **str);
bool msgunpack_bin(void **bin, uint64_t *len);
bool msgunpack_flt(float *flt);
bool msgunpack_dbl(double *dbl);
bool msgunpack_ext(uint8_t *key, void **val, uint64_t *len);
bool msgunpack_arr(uint64_t *len);
bool msgunpack_map(uint64_t *len);
bool msgunpack_eof();
bool msgunpack_err();
int savef(FILE *file, const char *format, ...);
int saveb(unsigned char *buf, const char *format, ...);
int loadf(FILE *file, const char *format, ...);
int loadb(const unsigned char *buf, const char *format, ...);
int input_use( int controller_id );
float input( int vk );
vec2 input2( int vk );
@ -2499,97 +2359,169 @@ enum { NETWORK_USERID = 7, NETWORK_COUNT , NETWORK_CAPACITY };
void server_send_bin(int64_t handle, const void *ptr, int len);
void server_drop(int64_t handle);
int64_t client_join(const char *ip, int port);
typedef struct obj { struct { ifdef(debug, const char *objname;) union { uintptr_t objheader; struct { uintptr_t objtype:8; uintptr_t objsizew:8; uintptr_t objrefs:8; uintptr_t objheap:1; uintptr_t objcomps:1; uintptr_t objunused:64-8-8-8-1-1-16-3; uintptr_t objid:16+3; }; }; struct obj** objchildren; }; } obj;
typedef struct entity { struct { struct { ifdef(debug, const char *objname;) union { uintptr_t objheader; struct { uintptr_t objtype:8; uintptr_t objsizew:8; uintptr_t objrefs:8; uintptr_t objheap:1; uintptr_t objcomps:1; uintptr_t objunused:64-8-8-8-1-1-16-3; uintptr_t objid:16+3; }; }; struct obj** objchildren; }; union { struct { uintptr_t objenabled:32, objflagged:32; }; uintptr_t cflags; }; void *c[32]; }; } entity;
obj *objtmp;
void* obj_malloc(unsigned sz);
void* obj_free(void *o);
extern void (*obj_ctor[256])();
extern void (*obj_dtor[256])();
extern char* (*obj_save[256])();
extern bool (*obj_load[256])();
extern int (*obj_test[256])();
extern int (*obj_init[256])();
extern int (*obj_quit[256])();
extern int (*obj_tick[256])();
extern int (*obj_draw[256])();
extern int (*obj_lerp[256])();
extern int (*obj_edit[256])();
extern int (*obj_aabb[256])();
uintptr_t obj_header(const void *o);
uintptr_t obj_id(const void *o);
const char* obj_type(const void *o);
unsigned obj_typeid(const void *o);
int obj_sizeof(const void *o);
int obj_size(const void *o);
char* obj_data(void *o);
const char* obj_datac(const void *o);
void* obj_payload(const void *o);
void* obj_zero(void *o);
void* obj_ref(void *oo);
void* obj_unref(void *oo);
obj* obj_detach(void *c);
obj* obj_attach(void *o, void *c);
obj* obj_root(const void *o);
obj* obj_parent(const void *o);
obj***obj_children(const void *o);
obj***obj_siblings(const void *o);
int obj_dumptree(const void *o);
void* obj_setmeta(void *o, const char *key, const char *value);
const char* obj_meta(const void *o, const char *key);
void* obj_setname(void *o, const char *name);
const char* obj_name(const void *o);
void* obj_swap(void *dst, void *src);
void* obj_copy_fast(void *dst, const void *src);
void* obj_copy(void *dst, const void *src);
int obj_comp_fast(const void *a, const void *b);
int obj_comp(const void *a, const void *b);
int obj_lesser(const void *a, const void *b);
int obj_greater(const void *a, const void *b);
int obj_equal(const void *a, const void *b);
uint64_t obj_hash(const void *o);
bool obj_hexdump(const void *oo);
int obj_print(const void *o);
int obj_printf(const void *o, const char *text);
int obj_console(const void *o);
char* obj_saveini(const void *o);
obj* obj_mergeini(void *o, const char *ini);
obj* obj_loadini(void *o, const char *ini);
char* obj_savejson(const void *o);
obj* obj_mergejson(void *o, const char *json);
obj* obj_loadjson(void *o, const char *json);
char* obj_savebin(const void *o);
obj* obj_mergebin(void *o, const char *sav);
obj* obj_loadbin(void *o, const char *sav);
char* obj_savempack(const void *o);
obj* obj_mergempack(void *o, const char *sav);
obj* obj_loadmpack(void *o, const char *sav);
int obj_push(const void *o);
int obj_pop(void *o);
bool obj_addcomponent(entity *e, unsigned c, void *ptr);
bool obj_hascomponent(entity *e, unsigned c);
void* obj_getcomponent(entity *e, unsigned c);
bool obj_delcomponent(entity *e, unsigned c);
bool obj_usecomponent(entity *e, unsigned c);
bool obj_offcomponent(entity *e, unsigned c);
char* entity_save(entity *self);
void* obj_clone(const void *src);
void* obj_merge(void *dst, const void *src);
void* obj_mutate(void *dst, const void *src);
void* obj_make(const char *str);
typedef enum OBJTYPE_BUILTINS {
OBJTYPE_obj = 0,
OBJTYPE_entity = 1,
OBJTYPE_vec2 = 2,
OBJTYPE_vec3 = 3,
OBJTYPE_vec4 = 4,
OBJTYPE_quat = 5,
OBJTYPE_mat33 = 6,
OBJTYPE_mat34 = 7,
OBJTYPE_mat44 = 8,
OBJTYPE_vec2i = 9,
OBJTYPE_vec3i = 10,
} OBJTYPE_BUILTINS;
enum COMPRESS_FLAGS {
COMPRESS_RAW = 0,
COMPRESS_PPP = (1<<4),
COMPRESS_ULZ = (2<<4),
COMPRESS_LZ4 = (3<<4),
COMPRESS_CRUSH = (4<<4),
COMPRESS_DEFLATE = (5<<4),
COMPRESS_LZP1 = (6<<4),
COMPRESS_LZMA = (7<<4),
COMPRESS_BALZ = (8<<4),
COMPRESS_LZW3 = (9<<4),
COMPRESS_LZSS = (10<<4),
COMPRESS_BCM = (11<<4),
COMPRESS_ZLIB = (12<<4),
};
unsigned zbounds(unsigned inlen, unsigned flags);
unsigned zencode(void *out, unsigned outlen, const void *in, unsigned inlen, unsigned flags);
unsigned zexcess(unsigned flags);
unsigned zdecode(void *out, unsigned outlen, const void *in, unsigned inlen, unsigned flags);
void *interleave( void *out, const void *list, int list_count, int sizeof_item, unsigned columns );
unsigned cobs_bounds(unsigned len);
unsigned cobs_encode(const void *in, unsigned inlen, void *out, unsigned outlen);
unsigned cobs_decode(const void *in, unsigned inlen, void *out, unsigned outlen);
unsigned base92_encode(const void *in, unsigned inlen, void* out, unsigned outlen);
unsigned base92_decode(const void *in, unsigned inlen, void* out, unsigned outlen);
unsigned base92_bounds(unsigned inlen);
unsigned netstring_bounds(unsigned inlen);
unsigned netstring_encode(const char *in, unsigned inlen, char *out, unsigned outlen);
unsigned netstring_decode(const char *in, unsigned inlen, char *out, unsigned outlen);
void delta8_encode(void *buffer, unsigned count);
void delta8_decode(void *buffer, unsigned count);
void delta16_encode(void *buffer, unsigned count);
void delta16_decode(void *buffer, unsigned count);
void delta32_encode(void *buffer, unsigned count);
void delta32_decode(void *buffer, unsigned count);
void delta64_encode(void *buffer, unsigned count);
void delta64_decode(void *buffer, unsigned count);
uint64_t zig64( int64_t value );
int64_t zag64( uint64_t value );
uint32_t enczig32u( int32_t n);
uint64_t enczig64u( int64_t n);
int32_t deczig32i(uint32_t n);
int64_t deczig64i(uint64_t n);
void *arc4( void *buffer, unsigned buflen, const void *pass, unsigned passlen );
uint64_t crc64(uint64_t h, const void *ptr, uint64_t len);
void entropy( void *buf, unsigned n );
int semver( int major, int minor, int patch );
int semvercmp( int v1, int v2 );
typedef struct byte2 { uint8_t x,y; } byte2;
typedef struct byte3 { uint8_t x,y,z; } byte3;
typedef struct byte4 { uint8_t x,y,z,w; } byte4;
typedef struct int2 { int x,y; } int2;
typedef struct int3 { int x,y,z; } int3;
typedef struct int4 { int x,y,z,w; } int4;
typedef struct uint2 { unsigned int x,y; } uint2;
typedef struct uint3 { unsigned int x,y,z; } uint3;
typedef struct uint4 { unsigned int x,y,z,w; } uint4;
typedef struct float2 { float x,y; } float2;
typedef struct float3 { float x,y,z; } float3;
typedef struct float4 { float x,y,z,w; } float4;
typedef struct double2 { double x,y; } double2;
typedef struct double3 { double x,y,z; } double3;
typedef struct double4 { double x,y,z,w; } double4;
char *cc4str(unsigned cc);
char *cc8str(uint64_t cc);
enum {
cc__1 = '1', cc__2, cc__3, cc__4, cc__5, cc__6,cc__7, cc__8, cc__9, cc__0, cc___, cc__ = ' ',
cc__A = 'A', cc__B, cc__C, cc__D, cc__E, cc__F,cc__G, cc__H, cc__I, cc__J, cc__K,cc__L, cc__M, cc__N, cc__O, cc__P,cc__Q, cc__R, cc__S, cc__T, cc__U,cc__V, cc__W, cc__X, cc__Y, cc__Z,
cc__a = 'a', cc__b, cc__c, cc__d, cc__e, cc__f,cc__g, cc__h, cc__i, cc__j, cc__k,cc__l, cc__m, cc__n, cc__o, cc__p,cc__q, cc__r, cc__s, cc__t, cc__u,cc__v, cc__w, cc__x, cc__y, cc__z,
};
char* ftoa1(float v);
char* ftoa2(vec2 v);
char* ftoa3(vec3 v);
char* ftoa4(vec4 v);
float atof1(const char *s);
vec2 atof2(const char *s);
vec3 atof3(const char *s);
vec4 atof4(const char *s);
char* itoa1(int v);
char* itoa2(vec2i v);
char* itoa3(vec3i v);
int atoi1(const char *s);
vec2i atoi2(const char *s);
vec3i atoi3(const char *s);
int is_big();
int is_little();
uint16_t swap16( uint16_t x );
uint32_t swap32( uint32_t x );
uint64_t swap64( uint64_t x );
float swap32f(float n);
double swap64f(double n);
void swapf(float *a, float *b);
void swapf2(vec2 *a, vec2 *b);
void swapf3(vec3 *a, vec3 *b);
void swapf4(vec4 *a, vec4 *b);
uint16_t lil16(uint16_t n);
uint32_t lil32(uint32_t n);
uint64_t lil64(uint64_t n);
float lil32f(float n);
double lil64f(double n);
uint16_t big16(uint16_t n);
uint32_t big32(uint32_t n);
uint64_t big64(uint64_t n);
float big32f(float n);
double big64f(double n);
uint16_t* lil16p(void *p, int sz);
uint32_t* lil32p(void *p, int sz);
uint64_t* lil64p(void *p, int sz);
float * lil32pf(void *p, int sz);
double * lil64pf(void *p, int sz);
uint16_t* big16p(void *p, int sz);
uint32_t* big32p(void *p, int sz);
uint64_t* big64p(void *p, int sz);
float * big32pf(void *p, int sz);
double * big64pf(void *p, int sz);
typedef uint16_t half;
float half_to_float(half value);
half float_to_half(float value);
void pack16i(uint8_t *buf, uint16_t i, int swap);
void pack32i(uint8_t *buf, uint32_t i, int swap);
void pack64i(uint8_t *buf, uint64_t i, int swap);
int16_t unpack16i(const uint8_t *buf, int swap);
int32_t unpack32i(const uint8_t *buf, int swap);
int64_t unpack64i(const uint8_t *buf, int swap);
uint64_t pack754(long double f, unsigned bits, unsigned expbits);
long double unpack754(uint64_t i, unsigned bits, unsigned expbits);
uint64_t pack64uv( uint8_t *buffer, uint64_t value );
uint64_t unpack64uv( const uint8_t *buffer, uint64_t *value );
uint64_t pack64iv( uint8_t *buffer, int64_t value_ );
uint64_t unpack64iv( const uint8_t *buffer, int64_t *value );
int msgpack(const char *fmt, ... );
int msgunpack(const char *fmt, ... );
int msgpack_new(uint8_t *w, size_t l);
int msgpack_nil();
int msgpack_chr(bool n);
int msgpack_uns(uint64_t n);
int msgpack_int(int64_t n);
int msgpack_str(const char *s);
int msgpack_bin(const char *s, size_t n);
int msgpack_flt(double g);
int msgpack_ext(uint8_t key, void *val, size_t n);
int msgpack_arr(uint32_t n);
int msgpack_map(uint32_t n);
int msgpack_eof();
int msgpack_err();
bool msgunpack_new( const void *opaque_or_FILE, size_t bytes );
bool msgunpack_nil();
bool msgunpack_chr(bool *chr);
bool msgunpack_uns(uint64_t *uns);
bool msgunpack_int(int64_t *sig);
bool msgunpack_str(char **str);
bool msgunpack_bin(void **bin, uint64_t *len);
bool msgunpack_flt(float *flt);
bool msgunpack_dbl(double *dbl);
bool msgunpack_ext(uint8_t *key, void **val, uint64_t *len);
bool msgunpack_arr(uint64_t *len);
bool msgunpack_map(uint64_t *len);
bool msgunpack_eof();
bool msgunpack_err();
int savef(FILE *file, const char *format, ...);
int saveb(unsigned char *buf, const char *format, ...);
int loadf(FILE *file, const char *format, ...);
int loadb(const unsigned char *buf, const char *format, ...);
int profiler_enable(bool on);
struct profile_t { double stat; int32_t cost, avg; };
typedef struct { map base; struct { pair p; char * key; struct profile_t val; } tmp, *ptr; struct profile_t* tmpval; int (*typed_cmp)(char *, char *); uint64_t (*typed_hash)(char *); } * profiler_t;
@ -2619,6 +2551,7 @@ unsigned bytes;
void struct_inscribe(const char *T,unsigned Tsz,unsigned OBJTYPEid, const char *infos);
void member_inscribe(const char *T, const char *M,unsigned Msz, const char *infos, const char *type, unsigned bytes);
void function_inscribe(const char *F,void *func,const char *infos);
const char* symbol_naked(const char *s);
int ui_reflect(const char *mask);
typedef unsigned handle;
unsigned rgba( uint8_t r, uint8_t g, uint8_t b, uint8_t a );
@ -3155,13 +3088,6 @@ int u_coefficients_sh;
light_t* scene_spawn_light();
unsigned scene_count_light();
light_t* scene_index_light(unsigned index);
void script_init();
void script_run(const char *script);
void script_runfile(const char *pathfile);
void script_bind_class(const char *objname, int num_methods, const char **c_names, void **c_functions);
void script_bind_function(const char *c_name, void *c_function);
void script_call(const char *lua_function);
bool script_tests();
char* tempvl(const char *fmt, va_list);
char* tempva(const char *fmt, ...);
char* strcatf(char **s, const char *buf);
@ -3211,6 +3137,81 @@ vec2i* entries;
void timer_destroy(unsigned timer_handle);
typedef vec3i guid;
guid guid_create();
float ease_nop(float t);
float ease_linear(float t);
float ease_out_sine(float t);
float ease_out_quad(float t);
float ease_out_cubic(float t);
float ease_out_quart(float t);
float ease_out_quint(float t);
float ease_out_expo(float t);
float ease_out_circ(float t);
float ease_out_back(float t);
float ease_out_elastic(float t);
float ease_out_bounce(float t);
float ease_in_sine(float t);
float ease_in_quad(float t);
float ease_in_cubic(float t);
float ease_in_quart(float t);
float ease_in_quint(float t);
float ease_in_expo(float t);
float ease_in_circ(float t);
float ease_in_back(float t);
float ease_in_elastic(float t);
float ease_in_bounce(float t);
float ease_inout_sine(float t);
float ease_inout_quad(float t);
float ease_inout_cubic(float t);
float ease_inout_quart(float t);
float ease_inout_quint(float t);
float ease_inout_expo(float t);
float ease_inout_circ(float t);
float ease_inout_back(float t);
float ease_inout_elastic(float t);
float ease_inout_bounce(float t);
float ease_inout_perlin(float t);
enum EASE_FLAGS {
EASE_SINE,
EASE_QUAD,
EASE_CUBIC,
EASE_QUART,
EASE_QUINT,
EASE_EXPO,
EASE_CIRC,
EASE_BACK,
EASE_ELASTIC,
EASE_BOUNCE,
EASE_IN,
EASE_OUT = 0,
EASE_INOUT = EASE_IN * 2,
EASE_NOP = EASE_INOUT | (EASE_BOUNCE + 1),
EASE_LINEAR,
EASE_INOUT_PERLIN,
EASE_NUM
};
float ease(float t01, unsigned fn);
float ease_pong(float t01, unsigned fn);
float ease_ping_pong(float t, unsigned fn1, unsigned fn2);
float ease_pong_ping(float t, unsigned fn1, unsigned fn2);
const char *ease_enum(unsigned fn);
const char**ease_enums();
typedef struct tween_keyframe_t {
float t;
vec3 v;
unsigned ease;
} tween_keyframe_t;
typedef struct tween_t {
tween_keyframe_t* keyframes;
vec3 result;
float time;
float duration;
} tween_t;
tween_t tween();
void tween_setkey(tween_t *tw, float t, vec3 v, unsigned easing_mode);
void tween_delkey(tween_t *tw, float t);
float tween_update(tween_t *tw, float dt);
void tween_reset(tween_t *tw);
void tween_destroy(tween_t *tw);
void* thread( int (*thread_func)(void* user_data), void* user_data );
void thread_destroy( void *thd );
int argc();

View File

@ -4,6 +4,7 @@ struct {
float (*ease)(float);
const char *name;
} easings[] = {
{ease_nop, "ease_nop"},
{ease_linear, "ease_linear"},
{ease_out_sine, "ease_out_sine"},
{ease_out_quad, "ease_out_quad"},

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,5 @@
void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
vec2 uv = fragCoord.xy / iResolution.xy;
fragColor = vec4(uv,0.5+0.5*sin(iGlobalTime),1.0);
}

View File

@ -0,0 +1,55 @@
// Created by inigo quilez - iq/2013
// https://www.youtube.com/c/InigoQuilez
// https://iquilezles.org/
// Shows how to use the mouse input (only left button supported):
//
// mouse.xy = mouse position during last button down
// abs(mouse.zw) = mouse position during last button click
// sign(mouze.z) = button is down
// sign(mouze.w) = button is clicked
// See also:
//
// Input - Keyboard : https://www.shadertoy.com/view/lsXGzf
// Input - Microphone : https://www.shadertoy.com/view/llSGDh
// Input - Mouse : https://www.shadertoy.com/view/Mss3zH
// Input - Sound : https://www.shadertoy.com/view/Xds3Rr
// Input - SoundCloud : https://www.shadertoy.com/view/MsdGzn
// Input - Time : https://www.shadertoy.com/view/lsXGz8
// Input - TimeDelta : https://www.shadertoy.com/view/lsKGWV
// Inout - 3D Texture : https://www.shadertoy.com/view/4llcR4
float distanceToSegment( vec2 a, vec2 b, vec2 p )
{
vec2 pa = p - a, ba = b - a;
float h = clamp( dot(pa,ba)/dot(ba,ba), 0.0, 1.0 );
return length( pa - ba*h );
}
void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
vec2 p = fragCoord / iResolution.x;
vec2 cen = 0.5*iResolution.xy/iResolution.x;
vec4 m = iMouse / iResolution.x;
vec3 col = vec3(0.0);
if( m.z>0.0 ) // button is down
{
float d = distanceToSegment( m.xy, abs(m.zw), p );
col = mix( col, vec3(1.0,1.0,0.0), 1.0-smoothstep(.004,0.008, d) );
}
if( m.w>0.0 ) // button click
{
col = mix( col, vec3(1.0,1.0,1.0), 1.0-smoothstep(0.1,0.105, length(p-cen)) );
}
col = mix( col, vec3(1.0,0.0,0.0), 1.0-smoothstep(0.03,0.035, length(p- m.xy )) );
col = mix( col, vec3(0.0,0.0,1.0), 1.0-smoothstep(0.03,0.035, length(p-abs(m.zw))) );
fragColor = vec4( col, 1.0 );
}

File diff suppressed because it is too large Load Diff

View File

@ -518,6 +518,7 @@ nk_glfw3_new_frame(struct nk_glfw* glfw)
nk_input_key(ctx, NK_KEY_SHIFT, 0);
}
if(glfwGetInputMode(win, GLFW_CURSOR) == GLFW_CURSOR_NORMAL) { //< @r-lyeh: do not grab input when mouse is hidden (fps cam)
glfwGetCursorPos(win, &x, &y);
nk_input_motion(ctx, (int)x, (int)y);
#if NK_GLFW_GL3_MOUSE_GRABBING
@ -531,6 +532,7 @@ nk_glfw3_new_frame(struct nk_glfw* glfw)
nk_input_button(ctx, NK_BUTTON_MIDDLE, (int)x, (int)y, glfwGetMouseButton(win, GLFW_MOUSE_BUTTON_MIDDLE) == GLFW_PRESS);
nk_input_button(ctx, NK_BUTTON_RIGHT, (int)x, (int)y, glfwGetMouseButton(win, GLFW_MOUSE_BUTTON_RIGHT) == GLFW_PRESS);
nk_input_button(ctx, NK_BUTTON_DOUBLE, glfw->double_click_pos.x, glfw->double_click_pos.y, glfw->is_double_click_down);
} //< @r-lyeh
nk_input_scroll(ctx, glfw->scroll);
nk_input_end(&glfw->ctx);
glfw->text_len = 0;

View File

@ -121,17 +121,18 @@ API void *ui_handle();
{{FILE:v4k_compat.c}}
{{FILE:v4k_audio.c}}
{{FILE:v4k_ui.c}}
{{FILE:v4k_buffer.c}}
{{FILE:v4k_audio.c}}
{{FILE:v4k_collide.c}}
{{FILE:v4k_cooker.c}}
{{FILE:v4k_cook.c}}
{{FILE:v4k_data.c}}
{{FILE:v4k_dll.c}}
{{FILE:v4k_extend.c}}
{{FILE:v4k_file.c}}
@ -141,8 +142,6 @@ API void *ui_handle();
{{FILE:v4k_math.c}}
{{FILE:v4k_tween.c}}
{{FILE:v4k_memory.c}}
{{FILE:v4k_network.c}}
@ -161,16 +160,10 @@ API void *ui_handle();
{{FILE:v4k_scene.c}}
{{FILE:v4k_script.c}}
{{FILE:v4k_time.c}}
{{FILE:v4k_system.c}}
{{FILE:v4k_id.c}}
{{FILE:v4k_ui.c}}
{{FILE:v4k_profile.c}}
{{FILE:v4k_video.c}}
@ -181,8 +174,6 @@ API void *ui_handle();
{{FILE:v4k_ai.c}}
{{FILE:v4k_bt.c}}
{{FILE:v4k_editor.c}}
// editor is last in place, so it can use all internals from above headers

View File

@ -101,33 +101,26 @@ extern "C" {
{{FILE:v4k_math.h}}
{{FILE:v4k_tween.h}}
{{FILE:v4k_obj.h}}
{{FILE:v4k_ai.h}}
{{FILE:v4k_bt.h}}
{{FILE:v4k_audio.h}}
{{FILE:v4k_buffer.h}}
{{FILE:v4k_collide.h}}
{{FILE:v4k_cooker.h}}
{{FILE:v4k_cook.h}}
{{FILE:v4k_data.h}}
{{FILE:v4k_dll.h}}
{{FILE:v4k_extend.h}}
{{FILE:v4k_editor.h}}
{{FILE:v4k_file.h}}
{{FILE:v4k_font.h}}
{{FILE:v4k_id.h}}
{{FILE:v4k_pack.h}}
{{FILE:v4k_file.h}}
{{FILE:v4k_input.h}}
@ -139,7 +132,7 @@ extern "C" {
{{FILE:v4k_netsync.h}}
{{FILE:v4k_obj.h}}
{{FILE:v4k_pack.h}}
{{FILE:v4k_profile.h}}
@ -151,8 +144,6 @@ extern "C" {
{{FILE:v4k_scene.h}}
{{FILE:v4k_script.h}}
{{FILE:v4k_string.h}}
{{FILE:v4k_time.h}}

View File

@ -571,3 +571,265 @@ int pathfind_astar(int width, int height, const unsigned* map, vec2i src, vec2i
return path_count;
}
// Behavior trees: decision planning and decision making.
// Supersedes finite state-machines (FSM) and hierarchical finite state-machines (HFSM).
// - rlyeh, public domain.
//
// [ref] https://outforafight.wordpress.com/2014/07/15/behaviour-behavior-trees-for-ai-dudes-part-1/
// [ref] https://www.gameaipro.com/GameAIPro/GameAIPro_Chapter06_The_Behavior_Tree_Starter_Kit.pdf
// [ref] https://gitlab.com/NotYetGames/DlgSystem/-/wikis/Dialogue-Manual
// [ref] https://towardsdatascience.com/designing-ai-agents-behaviors-with-behavior-trees-b28aa1c3cf8a
// [ref] https://docs.nvidia.com/isaac/packages/behavior_tree/doc/behavior_trees.html
// [ref] https://docs.unrealengine.com/4.26/en-US/InteractiveExperiences/ArtificialIntelligence/BehaviorTrees/
// [ref] gdc ChampandardDaweHernandezCerpa_BehaviorTrees.pdf @todo debugging
// [ref] https://docs.cocos.com/cocos2d-x/manual/en/actions/
// The nodes in a behavior tree can be broadly categorized into three main types: control nodes, task nodes, and decorator nodes. Here is a brief description of each category:
// Control Nodes: Control nodes are used to control the flow of the behavior tree. They determine the order in which child nodes are executed and how the results of those nodes are combined. They are usually parent nodes.
// Action Nodes: Action nodes are used to perform specific actions or tasks within the behavior tree. They can include actions such as moving, attacking, or interacting with objects in the game world. They are usually leaf nodes.
// Decorator Nodes: Decorator nodes are used to modify the behavior of child nodes in some way. They can be used to repeat child nodes, invert the result of a child node, or add a cooldown period between executions. They are usually located between control and action nodes.
// --- VARIABLES
// Key Prefixes:
// {visiblity:(l)ocal,s(q)uad,(r)ace,(f)action,(g)lobal}
// [persistence:(t)emp,(u)serdata,(s)avegame,(c)loud] + '_' + name.
// [persistence:(tmp),(usr)data,(sav)egame,(net)cloud] + '_' + name.
// Ie, l_health = 123.4, gsav_player = "john"
// --- ACTION NODES
// [ ] * Actions/Behaviors have a common structure:
// [ ] - Entry point (function name) for a C call or Lua script.
// [ ] * Status:
// [ ] - Uninitialized (never run)
// [ ] - Running (in progress)
// [ ] - Suspended (on hold till resumed)
// [ ] - Success (finished and succeeded)
// [ ] - Failure (finished and failed)
// [ ] * Optional callbacks:
// [ ] - on_enter
// [ ] - on_leave
// [ ] - on_success
// [ ] - on_failure
// [ ] - on_suspend
// [ ] - on_resume
// [x] Action Node: This node performs a single action, such as moving to a specific location or attacking a target.
// [ ] Blackboard Node: Node that reads and writes data to a shared memory space known as a blackboard. The blackboard can be used to store information that is relevant to multiple nodes in the behavior tree.
// [ ] SetKey(keyVar,object)
// [ ] HasKey(keyVar)
// [ ] CompareKeys(keyVar1, operator < <= > >= == !=, keyVar2)
// [ ] SetTags(names=blank,cooldownTime=inf,bIsCooldownAdditive=false)
// [ ] HasTags(names=blank,bAllRequired=true)
// [ ] PushToStack(keyVar,itemObj): creates a new stack if one doesnt exist, and stores it in the passed variable name, and then pushes item object onto it.
// [ ] PopFromStack(keyVar,itemVar): pop pops an item off the stack, and stores it in the itemVar variable, failing if the stack is already empty.
// [ ] IsEmptyStack(keyVar): checks if the stack passed is empty and returns success if it is, and failure if its not.
// [ ] Communication Node: This is a type of action node that allows an AI agent to communicate with other agents or entities in the game world. The node takes an input specifying the message to be communicated and the recipient(s) of the message (wildmask,l/p/f/g prefixes). The node then sends the message to the designated recipient(s) and returns success when the communication is completed. This node can be useful for implementing behaviors that require the AI agent to coordinate with other agents or to convey information to the player. It could use a radius argument to specify the maximum allowed distance for the recipients.
// [ ] Condition Node: A leaf node that checks a specific condition, such as the distance to an object, the presence of an enemy, or the status of a health bar.
// [ ] Distance Condition Node: This is a type of condition node that evaluates whether an AI agent is within a specified distance of a target object or location. The node takes two inputs: the current position of the AI agent and the position of the target object or location. If the distance between the two is within a specified range, the node returns success. If the distance is outside of the specified range, the node returns failure. This node can be useful for implementing behaviors that require the AI agent to maintain a certain distance from a target, such as following or avoiding an object. Could use a flag to disambiguate between linear distance and path distance.
// [ ] Query Node: This node checks a condition and returns success or failure based on the result. For example, a query node could check whether an enemy is within range or whether a door is locked.
// [ ] Query Node: A type of decorator node that retrieves information from a database or external system, such as a web service or file system. The Query node can be used to retrieve data that is not available within the game engine, such as weather or traffic conditions.
// [ ] A condition is made of:
// [ ] - Optional [!] negate
// [ ] - Mandatory Value1(Int/Flt/Bool/VarName/FuncName)
// [ ] - Optional operator [< <= > >= == !=] and Value2(Int/Flt/Bool/VarName/FuncName)
// [ ] AllConditions(...) : SUCCESS if ( empty array || all conditions met)
// [ ] AnyConditions(...) : SUCCESS if (!empty array && one conditions met)
// --- DECORATOR NODES
// [ ] Cooldown Node: Decorator node that adds a cooldown period between the execution of a child node, preventing it from being executed again until the cooldown period has elapsed.
// [x] Counter Node: Decorator node that limits the number of times that a child node can execute. For example, if the child node has executed a certain number of times, the Counter node will return a failure.
// [x] Once Node: Decorator node that triggers a specified action when a condition is met for the first time. This can be useful for triggering a one-time event, such as when an AI agent discovers a hidden item or reaches a new area of the game world.
// [x] Inverter Node: Decorator node that inverts the result of a child node, returning success if the child node fails and failure if the child node succeeds. This can be useful for negating the outcome of a particular behavior, such as avoiding a certain area of the game world.
// [x] Repeater Node: Decorator node that repeats the execution of a child node a specified number of times or indefinitely.
// [x] Repeat(times=inf): Runs child node given times. These are often used at the very base of the tree, to make the tree to run continuously.
// [ ] RepeatIf(strong/weak condition): Runs child node as long as the conditions are met.
// [ ] RepeatIfOk(times=inf): Runs child node if it succeedes, max given times.
// [ ] RepeatIfFail(times=inf): Runs child node if it fails, max given times.
// [ ] Branch Node: 2 children [0] for true, [1] for false
// [ ] Resource Node: Decorator node that manages a shared resource, such as a limited supply of ammunition or a limited amount of processing power. The Resource node The node can be used to decide when to use or conserve the resource. For example, if the AI agent is low on ammunition, the node may decide to switch to a melee weapon or to retreat to a safer location. This node can be useful for implementing behaviors that require the AI agent to make strategic decisions about resource use.
// [x] Result Node: Decorator node that tracks the result of a child action node and returns either success or failure depending on the outcome. This can be useful for determining whether an action was successful or not, and then adjusting the AI agent's behavior accordingly.
// [x] Succeeder Node: Decorator node that always returns success, regardless of the result of its child node. This can be useful for ensuring that certain behaviors are always executed, regardless of the overall success or failure of the behavior tree.
// [x] Success(): FAILURE becomes SUCCESS (TRUE).
// [x] Failure(): SUCCESS becomes FAILURE (FALSE).
// [ ] Throttle Node: Decorator node that limits the rate at which a child node can execute. For example, a Throttle node might ensure that an AI agent can only fire its weapon, or using a special ability, only a certain number of times per second.
// [x] Delay Node: Decorator node that adds a delay to the execution of a child node. The delay might be configured to sleep before the execution, after the execution, or both.
// [x] Defer Delay(duration_ss=1): Runs the child node, then sleeps for given seconds.
// [ ] Ease(time,name): Clamps child time to [0,1] range, and applies easing function on it.
// [ ] Dilate(Mul=1,Add=0): Dilates child time
// --- CONTROL NODES
// [x] Root Node: The topmost node in a behavior tree that represents the start of the decision-making process. Returns success if any of its child nodes suceedes.
// [x] Root-Failure Node: Control node that only returns failure if all of its child nodes fail. This can be useful for ensuring that a behavior tree is not prematurely terminated if one or more child nodes fail.
// [ ] Event(name): When name event is raised, it suspends current tree and calls child. "Incoming projectile" -> Evade. Stimulus types: may be disabled by event, or autodisabled.
// [ ] Raise(name): Triggers event name.
// [ ] Checkpoint Node: Control node that saves a state in memory and then continues the tree execution from that point the next time the tree is executed. It can be useful in situations where the behavior tree needs to be interrupted and resumed later.
// [ ] Decision Making Node: Control node that implements a decision-making process for the AI agent. The node takes input specifying the available options for the AI agent and the criteria for evaluating each option. The node then evaluates each option based on the specified criteria and selects the best option. This node can be useful for implementing behaviors that require the AI agent to make strategic decisions, such as choosing a target or selecting a path through the game world.
// [ ] Could be extended with GOAP if dynamically inserting the scores on each update then calling a Probability Selector Node (0.2,0.3,0.5)
// [ ] https://cdn.cloudflare.steamstatic.com/apps/valve/2012/GDC2012_Ruskin_Elan_DynamicDialog.pdf
// [ ] Evaluate Node / Recheck():
// [ ] Actively rechecks all existing sub-conditions on a regular basis after having made decisions about them.
// [ ] Use this feature to dynamically check for risks or opportunities in selected parts of the tree.
// [ ] For example, interrupting a patrol with a search behavior if a disturbance is reported.
// [ ] Interrupt Node: Control node that interrupts the execution of a lower-priority node when a higher-priority node needs to be executed. It can be useful for handling urgent tasks or emergency situations.
// [ ] Monitor Node: Control node that continuously monitors a condition and triggers a specified action when the condition is met. For example, a Monitor node might monitor the distance between an AI agent and a target and trigger a "retreat" behavior when the distance falls below a certain threshold.
// [ ] Observer Node: Control node that monitors the state of the game world and triggers a specified action when certain conditions are met. For example, an Observer node might trigger a "hide" behavior when an enemy is spotted.
// [ ] Parallel Node: Control node that executes multiple child nodes simultaneously. The Parallel node continues to execute even if some of its child nodes fail.
// [ ] Parallel All Node(required_successes=100%): Control node that executes multiple child nodes simultaneously. Returns false when first child fails, and aborts any other running tasks. Returns true if all its children succeed.
// [ ] Parallel One Node(required_successes=1): Control node that executes multiple child nodes simultaneously. Returns true when first child suceedes, and aborts any other running tasks. Returns false if all its children fail.
// [ ] Subsumption Node: Control node that allows multiple behaviors to be executed simultaneously, with higher-priority behaviors taking precedence over lower-priority ones. This can be useful for implementing complex, multi-level behaviors in autonomous systems.
// [ ] Semaphore Node: Control node that blocks the execution of its child nodes until a certain condition is met. For example, a Semaphore node might block the execution of a behavior until a certain object is in range or a certain event occurs.
// [ ] Semaphore Wait Node: Control node that blocks the execution of its child nodes until a resource becomes available. This can be useful for controlling access to shared resources such as a pathfinding system or a communication channel.
// [ ] WaitTags(tags=blank,timeout_ss=inf): Stops execution of child node until the cooldown tag(s) do expire. May return earlier if timed out.
// [ ] WaitEvent(name=blank,timeout_ss=inf): Stops execution of child node until event is raised. May return earlier if timed out.
// [x] Sequence Node(reversed,iterator(From,To)): Control node that executes a series of child nodes in order. If any of the child nodes fail, the Sequence node stops executing and returns a failure.
// [ ] Dynamic Sequence Node: Control node that dynamically changes the order in which its child nodes are executed based on certain conditions. For example, a Dynamic Sequence node might give higher priority to a child node that has recently failed or that is more likely to succeed.
// [ ] Reverse(): iterates children in reversed order (Iterator:(-1,0) equivalent)
// [ ] Iterator(from,to): allows negative indexing. increments if (abs from < abs to), decrements otherwise
// [x] Selector Node: Control node that selects a child node to execute based on a predefined priority or set of conditions. The Selector node stops executing child nodes as soon as one of them succeeds.
// [ ] Priority Selector Node: Control node that executes its child nodes in order of priority. If the first child node fails, it moves on to the next child node in the list until a successful node is found. This can be useful for implementing behaviors that require a specific order of execution, such as a patrol route or a search pattern.
// [ ] Probability Selector Node: Control node that selects a child node to execute based on a probability distribution. For example, if there are three child nodes with probabilities of 0.2, 0.3, and 0.5, the Probability Selector node will execute the third child node 50% of the time.
// [ ] Dynamic Selector Node: Control node that dynamically changes the order in which its child nodes are executed based on certain conditions. For example, a Dynamic Selector node might give higher priority to a child node that has recently succeeded or that is more likely to succeed.
// [ ] Dynamic Selector Node: Control node that dynamically changes the order in which child nodes are executed based on certain conditions. For example, it may prioritize certain nodes when health is low, and other nodes when resources are scarce.
// [ ] Dynamic Priority Node: Control node that dynamically changes the priority of its child nodes based on certain conditions. For example, if a child node is more likely to result in success, the Dynamic Priority node will give it a higher priority.
// [ ] Weighted / Cover Selection Node: This is a type of selector node that evaluates the available cover options in the game world and selects the best cover position for the AI agent. The node takes input specifying the current position of the AI agent and the location of potential cover positions. The node then evaluates the cover positions based on criteria such as distance to enemy positions, line of sight, and available cover points, and selects the best option. This node can be useful for implementing behaviors that require the AI agent to take cover and avoid enemy fire.
// [ ] Random Selector Node: Control node that selects a child node to execute at random. This can be useful for introducing variety and unpredictability into the behavior of an AI agent.
// [ ] Random Weight / Stochastic Node(0.2,0.3,0.5): Control node that introduces randomness into the decision-making process of an AI agent. For example, a Stochastic node might randomly select a child node to execute, with the probability of each child node being proportional to its likelihood of success.
// [ ] Break(bool): breaks parent sequence or selector. may return SUCCESS or FAILURE.
// [ ] Hybrid Node: Control node that combines the functionality of multiple control nodes, such as a sequence node and a selector node. It can be useful for creating complex behavior patterns that require multiple control structures. The Hybrid Node has two modes of operation: strict mode and flexible mode. In strict mode, the child nodes are executed in a fixed order, similar to a Sequence Node. If any child node fails, the entire Hybrid Node fails. In flexible mode, the child nodes can be executed in any order, similar to a Selector Node. If any child node succeeds, the entire Hybrid Node succeeds. The Hybrid Node is often used when you need to create complex behavior patterns that require multiple control structures. For example, you might use a Hybrid Node to implement a search behavior where the AI agent first moves in a fixed direction for a certain distance (strict mode), but then switches to a more exploratory mode where it randomly explores nearby areas (flexible mode).
// [ ] Subtree Node: Control node that calls another behavior tree as a subroutine. This is useful for breaking down complex behaviors into smaller, more manageable parts.
// [ ] Call(name): Calls to the child node with the matching name within behavior tree. Returns FAILURE if name is invalid or if invoked behavior returns FAILURE.
// [ ] Return(boolean): Exits current Call.
// [ ] AttachTree(Name,bDetachAfterUse=true): Attaches subtree to main tree. When a NPC founds the actor, it attaches the behavior to the tree. Ie, Level specific content: Patrols, Initial setups, Story driven events, etc. Ie, DLCs: Behaviors are added to actors in the DLC level (enticers).
// [ ] DetachTree(Name)
// [ ] Switch Node: Control node that evaluates a condition and then selects one of several possible child nodes to execute based on the result of the condition.
// [ ] Switch(name): Jumps to the child node with the matching name within behavior tree. If name is invalid will defer to Fallback child node.
// [ ] Timeout Node: Control node that aborts the execution of its child node after a certain amount of time has elapsed. This can be useful for preventing an AI agent from getting stuck in a loop or waiting indefinitely for a particular event to occur.
// [ ] TimeLimit(timeout_ss=inf): Give the child any amount of time to finish before it gets canceled. The timer is reset every time the node gains focus.
// [ ] Timer Node: Control node which invokes its child node every XX seconds. The timer can repeat the action any given number of times, or indefinitely.
// ## Proposal -----------------------------------------------------------------------------
// BehaviorTrees as case-insensitive INI files. Then,
// - INI -> C INTERPRETER, OR
// - INI -> LUA TRANSPILER -> LUA BYTECODE -> LUA VM.
// ```ini
// [bt]
// recheck
// sequence ;; Approach the player if seen!
// conditions=IsPlayerVisible,
// action=MoveTowardsPlayer
// sequence ;; Attack the player if seen!
// conditions=IsPlayerInRange,
// repeat=3
// action=FireAtPlayer
// sequence ;; Search near last known position
// conditions=HaveWeGotASuspectedLocation,
// action=MoveToPlayersLastKnownPosition
// action=LookAround
// sequence ;; Randomly scanning nearby
// action=MoveToRandomPosition
// action=LookAround
// event=IncomingProjectile
// action=Evade
// ```
map(char*, bt_func) binds;
void bt_addfun(const char *name, int(*func)()){
do_once map_init_str(binds);
map_find_or_add_allocated_key(binds, STRDUP(name), func);
}
bt_func bt_findfun(const char *name) {
bt_func *found = map_find(binds, (char*)name);
return found ? *found : 0;
}
char * bt_funcname(bt_func fn) {
for each_map(binds,char*,k,bt_func,f) {
if( f == fn ) return k;
}
return 0;
}
bt_t bt(const char *ini_file, unsigned flags) {
bt_t z = {0}, root = z;
array(char*) m = strsplit(vfs_read(ini_file), "\r\n");
bt_t *self = &root;
self->type = cc4(r,o,o,t);
//self->parent = self;
for( int i = 0; i < array_count(m); ++i ) {
// parse ini
int level = strspn(m[i], " \t");
char *k = m[i] + level;
if( k[0] == '[' ) {
if( strcmp(k+1, "bt]") ) return z; // we only support [bt]
continue;
}
int sep = strcspn(k, " =:");
char *v = k + sep; if(sep) *v++ = '\0'; else v = k + strlen(k); // v = (char*)"";
array(char*) args = *v ? strsplit(v, " ") : NULL;
// insert node in tree
bt_t *out = self;
while( level-- ) {
out = array_back(out->children);
}
array_push(out->children, ((bt_t){0}));
//array_back(out->children)->parent = out;
out = array_back(out->children);
// config node
out->type = *(uint64_t*)va("%-8s", k);
if( array_count(args) ) out->argf = atof(args[0]);
if( !strcmp(k, "run") ) out->action = bt_findfun(v);
}
return root;
}
int bt_run(bt_t *b) {
int rc = 0;
/**/ if( b->type == cc3( r,u,n) ) { return b->action ? b->action() : 0; }
else if( b->type == cc3( n,o,t) ) { return !bt_run(b->children + 0); }
else if( b->type == cc5( s,l,e,e,p) ) { return sleep_ss(b->argf), bt_run(b->children + 0); }
else if( b->type == cc5( d,e,f,e,r) ) { rc = bt_run(b->children + 0); return sleep_ss(b->argf), rc; }
else if( b->type == cc4( l,o,o,p) ) { int rc; for(int i = 0; i < b->argf; ++i) rc = bt_run(b->children + 0); return rc; }
else if( b->type == cc4( o,n,c,e) ) { return b->argf ? 0 : (b->argf = 1), bt_run(b->children + 0); }
else if( b->type == cc5( c,o,u,n,t) ) { return b->argf <= 0 ? 0 : --b->argf, bt_run(b->children + 0); }
else if( b->type == cc4( p,a,s,s) ) { return bt_run(b->children + 0), 1; }
else if( b->type == cc4( f,a,i,l) ) { return bt_run(b->children + 0), 0; }
else if( b->type == cc6( r,e,s,u,l,t) ) { return bt_run(b->children + 0), !!b->argf; }
else if( b->type == cc3( a,l,l) ) { for( int i = 0; i < array_count(b->children); ++i ) if(!bt_run(b->children+i)) return 0; return 1; }
else if( b->type == cc3( a,n,y) ) { for( int i = 0; i < array_count(b->children); ++i ) if( bt_run(b->children+i)) return 1; return 0; }
else if( b->type == cc4( r,o,o,t) ) { for( int i = 0; i < array_count(b->children); ++i ) rc|=bt_run(b->children+i); return rc; }
else if( b->type == cc8(r,o,o,t,f,a,i,l) ) { rc = 1; for( int i = 0; i < array_count(b->children); ++i ) rc&=~bt_run(b->children+i); return rc; }
return 0;
}
int ui_bt(bt_t *b) {
if( b ) {
char *info = bt_funcname(b->action);
if(!info) info = va("%d", array_count(b->children));
if( ui_collapse(va("%s (%s)", cc8str(b->type), info), va("bt%p",b)) ) {
for( int i = 0; i < array_count(b->children); ++i) {
ui_bt(b->children + i);
}
ui_collapse_end();
}
}
return 0;
}

View File

@ -1,3 +1,39 @@
// AI framework
// - rlyeh, public domain.
//
// [src] original A-star code by @mmozeiko (PD) - https://gist.github.com/mmozeiko/68f0a8459ef2f98bcd879158011cc275
// [src] original swarm/boids code by @Cultrarius (UNLICENSE) - https://github.com/Cultrarius/Swarmz
// pathfinding -----------------------------------------------------------------
API int pathfind_astar(int width, int height, const unsigned* map, vec2i src, vec2i dst, vec2i* path, size_t maxpath);
// ----------------------------------------------------------------------------
// Behavior trees: decision planning and decision making.
// Supersedes finite state-machines (FSM) and hierarchical finite state-machines (HFSM).
typedef int (*bt_func)();
typedef struct bt_t {
uint64_t type;
int (*action)();
union {
int argi;
float argf;
};
array(struct bt_t) children;
} bt_t;
API bt_t bt(const char *ini_file, unsigned flags);
API int bt_run(bt_t *b);
API void bt_addfun(const char *name, int(*func)());
API bt_func bt_findfun(const char *name);
API char *bt_funcname(bt_func fn);
API int ui_bt(bt_t *b);
// boids/swarm -----------------------------------------------------------------
typedef enum SWARM_DISTANCE {
SWARM_DISTANCE_LINEAR,
SWARM_DISTANCE_INVERSE_LINEAR,
@ -44,7 +80,3 @@ API void swarm_update_acceleration_only(swarm_t *self); // acc
API void swarm_update_acceleration_and_velocity_only(swarm_t *self, float delta); // acc,vel
API int ui_swarm(swarm_t *self);
// pathfinding -----------------------------------------------------------------
API int pathfind_astar(int width, int height, const unsigned* map, vec2i src, vec2i dst, vec2i* path, size_t maxpath);

View File

@ -1,261 +0,0 @@
// Behavior trees: decision planning and decision making.
// Supersedes finite state-machines (FSM) and hierarchical finite state-machines (HFSM).
// - rlyeh, public domain.
//
// [ref] https://outforafight.wordpress.com/2014/07/15/behaviour-behavior-trees-for-ai-dudes-part-1/
// [ref] https://www.gameaipro.com/GameAIPro/GameAIPro_Chapter06_The_Behavior_Tree_Starter_Kit.pdf
// [ref] https://gitlab.com/NotYetGames/DlgSystem/-/wikis/Dialogue-Manual
// [ref] https://towardsdatascience.com/designing-ai-agents-behaviors-with-behavior-trees-b28aa1c3cf8a
// [ref] https://docs.nvidia.com/isaac/packages/behavior_tree/doc/behavior_trees.html
// [ref] https://docs.unrealengine.com/4.26/en-US/InteractiveExperiences/ArtificialIntelligence/BehaviorTrees/
// [ref] gdc ChampandardDaweHernandezCerpa_BehaviorTrees.pdf @todo debugging
// [ref] https://docs.cocos.com/cocos2d-x/manual/en/actions/
// The nodes in a behavior tree can be broadly categorized into three main types: control nodes, task nodes, and decorator nodes. Here is a brief description of each category:
// Control Nodes: Control nodes are used to control the flow of the behavior tree. They determine the order in which child nodes are executed and how the results of those nodes are combined. They are usually parent nodes.
// Action Nodes: Action nodes are used to perform specific actions or tasks within the behavior tree. They can include actions such as moving, attacking, or interacting with objects in the game world. They are usually leaf nodes.
// Decorator Nodes: Decorator nodes are used to modify the behavior of child nodes in some way. They can be used to repeat child nodes, invert the result of a child node, or add a cooldown period between executions. They are usually located between control and action nodes.
// --- VARIABLES
// Key Prefixes:
// {visiblity:(l)ocal,s(q)uad,(r)ace,(f)action,(g)lobal}
// [persistence:(t)emp,(u)serdata,(s)avegame,(c)loud] + '_' + name.
// [persistence:(tmp),(usr)data,(sav)egame,(net)cloud] + '_' + name.
// Ie, l_health = 123.4, gsav_player = "john"
// --- ACTION NODES
// [ ] * Actions/Behaviors have a common structure:
// [ ] - Entry point (function name) for a C call or Lua script.
// [ ] * Status:
// [ ] - Uninitialized (never run)
// [ ] - Running (in progress)
// [ ] - Suspended (on hold till resumed)
// [ ] - Success (finished and succeeded)
// [ ] - Failure (finished and failed)
// [ ] * Optional callbacks:
// [ ] - on_enter
// [ ] - on_leave
// [ ] - on_success
// [ ] - on_failure
// [ ] - on_suspend
// [ ] - on_resume
// [x] Action Node: This node performs a single action, such as moving to a specific location or attacking a target.
// [ ] Blackboard Node: Node that reads and writes data to a shared memory space known as a blackboard. The blackboard can be used to store information that is relevant to multiple nodes in the behavior tree.
// [ ] SetKey(keyVar,object)
// [ ] HasKey(keyVar)
// [ ] CompareKeys(keyVar1, operator < <= > >= == !=, keyVar2)
// [ ] SetTags(names=blank,cooldownTime=inf,bIsCooldownAdditive=false)
// [ ] HasTags(names=blank,bAllRequired=true)
// [ ] PushToStack(keyVar,itemObj): creates a new stack if one doesn't exist, and stores it in the passed variable name, and then pushes 'item' object onto it.
// [ ] PopFromStack(keyVar,itemVar): pop pops an item off the stack, and stores it in the itemVar variable, failing if the stack is already empty.
// [ ] IsEmptyStack(keyVar): checks if the stack passed is empty and returns success if it is, and failure if its not.
// [ ] Communication Node: This is a type of action node that allows an AI agent to communicate with other agents or entities in the game world. The node takes an input specifying the message to be communicated and the recipient(s) of the message (wildmask,l/p/f/g prefixes). The node then sends the message to the designated recipient(s) and returns success when the communication is completed. This node can be useful for implementing behaviors that require the AI agent to coordinate with other agents or to convey information to the player. It could use a radius argument to specify the maximum allowed distance for the recipients.
// [ ] Condition Node: A leaf node that checks a specific condition, such as the distance to an object, the presence of an enemy, or the status of a health bar.
// [ ] Distance Condition Node: This is a type of condition node that evaluates whether an AI agent is within a specified distance of a target object or location. The node takes two inputs: the current position of the AI agent and the position of the target object or location. If the distance between the two is within a specified range, the node returns success. If the distance is outside of the specified range, the node returns failure. This node can be useful for implementing behaviors that require the AI agent to maintain a certain distance from a target, such as following or avoiding an object. Could use a flag to disambiguate between linear distance and path distance.
// [ ] Query Node: This node checks a condition and returns success or failure based on the result. For example, a query node could check whether an enemy is within range or whether a door is locked.
// [ ] Query Node: A type of decorator node that retrieves information from a database or external system, such as a web service or file system. The Query node can be used to retrieve data that is not available within the game engine, such as weather or traffic conditions.
// [ ] A condition is made of:
// [ ] - Optional [!] negate
// [ ] - Mandatory Value1(Int/Flt/Bool/VarName/FuncName)
// [ ] - Optional operator [< <= > >= == !=] and Value2(Int/Flt/Bool/VarName/FuncName)
// [ ] AllConditions(...) : SUCCESS if ( empty array || all conditions met)
// [ ] AnyConditions(...) : SUCCESS if (!empty array && one conditions met)
// --- DECORATOR NODES
// [ ] Cooldown Node: Decorator node that adds a cooldown period between the execution of a child node, preventing it from being executed again until the cooldown period has elapsed.
// [x] Counter Node: Decorator node that limits the number of times that a child node can execute. For example, if the child node has executed a certain number of times, the Counter node will return a failure.
// [x] Once Node: Decorator node that triggers a specified action when a condition is met for the first time. This can be useful for triggering a one-time event, such as when an AI agent discovers a hidden item or reaches a new area of the game world.
// [x] Inverter Node: Decorator node that inverts the result of a child node, returning success if the child node fails and failure if the child node succeeds. This can be useful for negating the outcome of a particular behavior, such as avoiding a certain area of the game world.
// [x] Repeater Node: Decorator node that repeats the execution of a child node a specified number of times or indefinitely.
// [x] Repeat(times=inf): Runs child node given times. These are often used at the very base of the tree, to make the tree to run continuously.
// [ ] RepeatIf(strong/weak condition): Runs child node as long as the conditions are met.
// [ ] RepeatIfOk(times=inf): Runs child node if it succeedes, max given times.
// [ ] RepeatIfFail(times=inf): Runs child node if it fails, max given times.
// [ ] Branch Node: 2 children [0] for true, [1] for false
// [ ] Resource Node: Decorator node that manages a shared resource, such as a limited supply of ammunition or a limited amount of processing power. The Resource node The node can be used to decide when to use or conserve the resource. For example, if the AI agent is low on ammunition, the node may decide to switch to a melee weapon or to retreat to a safer location. This node can be useful for implementing behaviors that require the AI agent to make strategic decisions about resource use.
// [x] Result Node: Decorator node that tracks the result of a child action node and returns either success or failure depending on the outcome. This can be useful for determining whether an action was successful or not, and then adjusting the AI agent's behavior accordingly.
// [x] Succeeder Node: Decorator node that always returns success, regardless of the result of its child node. This can be useful for ensuring that certain behaviors are always executed, regardless of the overall success or failure of the behavior tree.
// [x] Success(): FAILURE becomes SUCCESS (TRUE).
// [x] Failure(): SUCCESS becomes FAILURE (FALSE).
// [ ] Throttle Node: Decorator node that limits the rate at which a child node can execute. For example, a Throttle node might ensure that an AI agent can only fire its weapon, or using a special ability, only a certain number of times per second.
// [x] Delay Node: Decorator node that adds a delay to the execution of a child node. The delay might be configured to sleep before the execution, after the execution, or both.
// [x] Defer Delay(duration_ss=1): Runs the child node, then sleeps for given seconds.
// [ ] Ease(time,name): Clamps child time to [0,1] range, and applies easing function on it.
// [ ] Dilate(Mul=1,Add=0): Dilates child time
// --- CONTROL NODES
// [x] Root Node: The topmost node in a behavior tree that represents the start of the decision-making process. Returns success if any of its child nodes suceedes.
// [x] Root-Failure Node: Control node that only returns failure if all of its child nodes fail. This can be useful for ensuring that a behavior tree is not prematurely terminated if one or more child nodes fail.
// [ ] Event(name): When name event is raised, it suspends current tree and calls child. "Incoming projectile" -> Evade. Stimulus types: may be disabled by event, or autodisabled.
// [ ] Raise(name): Triggers event name.
// [ ] Checkpoint Node: Control node that saves a state in memory and then continues the tree execution from that point the next time the tree is executed. It can be useful in situations where the behavior tree needs to be interrupted and resumed later.
// [ ] Decision Making Node: Control node that implements a decision-making process for the AI agent. The node takes input specifying the available options for the AI agent and the criteria for evaluating each option. The node then evaluates each option based on the specified criteria and selects the best option. This node can be useful for implementing behaviors that require the AI agent to make strategic decisions, such as choosing a target or selecting a path through the game world.
// [ ] Could be extended with GOAP if dynamically inserting the scores on each update then calling a Probability Selector Node (0.2,0.3,0.5)
// [ ] https://cdn.cloudflare.steamstatic.com/apps/valve/2012/GDC2012_Ruskin_Elan_DynamicDialog.pdf
// [ ] Evaluate Node / Recheck():
// [ ] Actively rechecks all existing sub-conditions on a regular basis after having made decisions about them.
// [ ] Use this feature to dynamically check for risks or opportunities in selected parts of the tree.
// [ ] For example, interrupting a patrol with a search behavior if a disturbance is reported.
// [ ] Interrupt Node: Control node that interrupts the execution of a lower-priority node when a higher-priority node needs to be executed. It can be useful for handling urgent tasks or emergency situations.
// [ ] Monitor Node: Control node that continuously monitors a condition and triggers a specified action when the condition is met. For example, a Monitor node might monitor the distance between an AI agent and a target and trigger a "retreat" behavior when the distance falls below a certain threshold.
// [ ] Observer Node: Control node that monitors the state of the game world and triggers a specified action when certain conditions are met. For example, an Observer node might trigger a "hide" behavior when an enemy is spotted.
// [ ] Parallel Node: Control node that executes multiple child nodes simultaneously. The Parallel node continues to execute even if some of its child nodes fail.
// [ ] Parallel All Node(required_successes=100%): Control node that executes multiple child nodes simultaneously. Returns false when first child fails, and aborts any other running tasks. Returns true if all its children succeed.
// [ ] Parallel One Node(required_successes=1): Control node that executes multiple child nodes simultaneously. Returns true when first child suceedes, and aborts any other running tasks. Returns false if all its children fail.
// [ ] Subsumption Node: Control node that allows multiple behaviors to be executed simultaneously, with higher-priority behaviors taking precedence over lower-priority ones. This can be useful for implementing complex, multi-level behaviors in autonomous systems.
// [ ] Semaphore Node: Control node that blocks the execution of its child nodes until a certain condition is met. For example, a Semaphore node might block the execution of a behavior until a certain object is in range or a certain event occurs.
// [ ] Semaphore Wait Node: Control node that blocks the execution of its child nodes until a resource becomes available. This can be useful for controlling access to shared resources such as a pathfinding system or a communication channel.
// [ ] WaitTags(tags=blank,timeout_ss=inf): Stops execution of child node until the cooldown tag(s) do expire. May return earlier if timed out.
// [ ] WaitEvent(name=blank,timeout_ss=inf): Stops execution of child node until event is raised. May return earlier if timed out.
// [x] Sequence Node(reversed,iterator(From,To)): Control node that executes a series of child nodes in order. If any of the child nodes fail, the Sequence node stops executing and returns a failure.
// [ ] Dynamic Sequence Node: Control node that dynamically changes the order in which its child nodes are executed based on certain conditions. For example, a Dynamic Sequence node might give higher priority to a child node that has recently failed or that is more likely to succeed.
// [ ] Reverse(): iterates children in reversed order (Iterator:(-1,0) equivalent)
// [ ] Iterator(from,to): allows negative indexing. increments if (abs from < abs to), decrements otherwise
// [x] Selector Node: Control node that selects a child node to execute based on a predefined priority or set of conditions. The Selector node stops executing child nodes as soon as one of them succeeds.
// [ ] Priority Selector Node: Control node that executes its child nodes in order of priority. If the first child node fails, it moves on to the next child node in the list until a successful node is found. This can be useful for implementing behaviors that require a specific order of execution, such as a patrol route or a search pattern.
// [ ] Probability Selector Node: Control node that selects a child node to execute based on a probability distribution. For example, if there are three child nodes with probabilities of 0.2, 0.3, and 0.5, the Probability Selector node will execute the third child node 50% of the time.
// [ ] Dynamic Selector Node: Control node that dynamically changes the order in which its child nodes are executed based on certain conditions. For example, a Dynamic Selector node might give higher priority to a child node that has recently succeeded or that is more likely to succeed.
// [ ] Dynamic Selector Node: Control node that dynamically changes the order in which child nodes are executed based on certain conditions. For example, it may prioritize certain nodes when health is low, and other nodes when resources are scarce.
// [ ] Dynamic Priority Node: Control node that dynamically changes the priority of its child nodes based on certain conditions. For example, if a child node is more likely to result in success, the Dynamic Priority node will give it a higher priority.
// [ ] Weighted / Cover Selection Node: This is a type of selector node that evaluates the available cover options in the game world and selects the best cover position for the AI agent. The node takes input specifying the current position of the AI agent and the location of potential cover positions. The node then evaluates the cover positions based on criteria such as distance to enemy positions, line of sight, and available cover points, and selects the best option. This node can be useful for implementing behaviors that require the AI agent to take cover and avoid enemy fire.
// [ ] Random Selector Node: Control node that selects a child node to execute at random. This can be useful for introducing variety and unpredictability into the behavior of an AI agent.
// [ ] Random Weight / Stochastic Node(0.2,0.3,0.5): Control node that introduces randomness into the decision-making process of an AI agent. For example, a Stochastic node might randomly select a child node to execute, with the probability of each child node being proportional to its likelihood of success.
// [ ] Break(bool): breaks parent sequence or selector. may return SUCCESS or FAILURE.
// [ ] Hybrid Node: Control node that combines the functionality of multiple control nodes, such as a sequence node and a selector node. It can be useful for creating complex behavior patterns that require multiple control structures. The Hybrid Node has two modes of operation: strict mode and flexible mode. In strict mode, the child nodes are executed in a fixed order, similar to a Sequence Node. If any child node fails, the entire Hybrid Node fails. In flexible mode, the child nodes can be executed in any order, similar to a Selector Node. If any child node succeeds, the entire Hybrid Node succeeds. The Hybrid Node is often used when you need to create complex behavior patterns that require multiple control structures. For example, you might use a Hybrid Node to implement a search behavior where the AI agent first moves in a fixed direction for a certain distance (strict mode), but then switches to a more exploratory mode where it randomly explores nearby areas (flexible mode).
// [ ] Subtree Node: Control node that calls another behavior tree as a subroutine. This is useful for breaking down complex behaviors into smaller, more manageable parts.
// [ ] Call(name): Calls to the child node with the matching name within behavior tree. Returns FAILURE if name is invalid or if invoked behavior returns FAILURE.
// [ ] Return(boolean): Exits current Call.
// [ ] AttachTree(Name,bDetachAfterUse=true): Attaches subtree to main tree. When a NPC founds the actor, it attaches the behavior to the tree. Ie, Level specific content: Patrols, Initial setups, Story driven events, etc. Ie, DLCs: Behaviors are added to actors in the DLC level (enticers).
// [ ] DetachTree(Name)
// [ ] Switch Node: Control node that evaluates a condition and then selects one of several possible child nodes to execute based on the result of the condition.
// [ ] Switch(name): Jumps to the child node with the matching name within behavior tree. If name is invalid will defer to Fallback child node.
// [ ] Timeout Node: Control node that aborts the execution of its child node after a certain amount of time has elapsed. This can be useful for preventing an AI agent from getting stuck in a loop or waiting indefinitely for a particular event to occur.
// [ ] TimeLimit(timeout_ss=inf): Give the child any amount of time to finish before it gets canceled. The timer is reset every time the node gains focus.
// [ ] Timer Node: Control node which invokes its child node every XX seconds. The timer can repeat the action any given number of times, or indefinitely.
// ## Proposal -----------------------------------------------------------------------------
// BehaviorTrees as case-insensitive INI files. Then,
// - INI -> C INTERPRETER, OR
// - INI -> LUA TRANSPILER -> LUA BYTECODE -> LUA VM.
// ```ini
// [bt]
// recheck
// sequence ;; Approach the player if seen!
// conditions=IsPlayerVisible,
// action=MoveTowardsPlayer
// sequence ;; Attack the player if seen!
// conditions=IsPlayerInRange,
// repeat=3
// action=FireAtPlayer
// sequence ;; Search near last known position
// conditions=HaveWeGotASuspectedLocation,
// action=MoveToPlayersLastKnownPosition
// action=LookAround
// sequence ;; Randomly scanning nearby
// action=MoveToRandomPosition
// action=LookAround
// event=IncomingProjectile
// action=Evade
// ```
map(char*, bt_func) binds;
void bt_addfun(const char *name, int(*func)()){
do_once map_init_str(binds);
map_find_or_add_allocated_key(binds, STRDUP(name), func);
}
bt_func bt_findfun(const char *name) {
bt_func *found = map_find(binds, (char*)name);
return found ? *found : 0;
}
char * bt_funcname(bt_func fn) {
for each_map(binds,char*,k,bt_func,f) {
if( f == fn ) return k;
}
return 0;
}
bt_t bt(const char *ini_file, unsigned flags) {
bt_t z = {0}, root = z;
array(char*) m = strsplit(vfs_read(ini_file), "\r\n");
bt_t *self = &root;
self->type = cc4(r,o,o,t);
//self->parent = self;
for( int i = 0; i < array_count(m); ++i ) {
// parse ini
int level = strspn(m[i], " \t");
char *k = m[i] + level;
if( k[0] == '[' ) {
if( strcmp(k+1, "bt]") ) return z; // we only support [bt]
continue;
}
int sep = strcspn(k, " =:");
char *v = k + sep; if(sep) *v++ = '\0'; else v = k + strlen(k); // v = (char*)"";
array(char*) args = *v ? strsplit(v, " ") : NULL;
// insert node in tree
bt_t *out = self;
while( level-- ) {
out = array_back(out->children);
}
array_push(out->children, ((bt_t){0}));
//array_back(out->children)->parent = out;
out = array_back(out->children);
// config node
out->type = *(uint64_t*)va("%-8s", k);
if( array_count(args) ) out->argf = atof(args[0]);
if( !strcmp(k, "run") ) out->action = bt_findfun(v);
}
return root;
}
int bt_run(bt_t *b) {
int rc = 0;
/**/ if( b->type == cc3( r,u,n) ) { return b->action ? b->action() : 0; }
else if( b->type == cc3( n,o,t) ) { return !bt_run(b->children + 0); }
else if( b->type == cc5( s,l,e,e,p) ) { return sleep_ss(b->argf), bt_run(b->children + 0); }
else if( b->type == cc5( d,e,f,e,r) ) { rc = bt_run(b->children + 0); return sleep_ss(b->argf), rc; }
else if( b->type == cc4( l,o,o,p) ) { int rc; for(int i = 0; i < b->argf; ++i) rc = bt_run(b->children + 0); return rc; }
else if( b->type == cc4( o,n,c,e) ) { return b->argf ? 0 : (b->argf = 1), bt_run(b->children + 0); }
else if( b->type == cc5( c,o,u,n,t) ) { return b->argf <= 0 ? 0 : --b->argf, bt_run(b->children + 0); }
else if( b->type == cc4( p,a,s,s) ) { return bt_run(b->children + 0), 1; }
else if( b->type == cc4( f,a,i,l) ) { return bt_run(b->children + 0), 0; }
else if( b->type == cc6( r,e,s,u,l,t) ) { return bt_run(b->children + 0), !!b->argf; }
else if( b->type == cc3( a,l,l) ) { for( int i = 0; i < array_count(b->children); ++i ) if(!bt_run(b->children+i)) return 0; return 1; }
else if( b->type == cc3( a,n,y) ) { for( int i = 0; i < array_count(b->children); ++i ) if( bt_run(b->children+i)) return 1; return 0; }
else if( b->type == cc4( r,o,o,t) ) { for( int i = 0; i < array_count(b->children); ++i ) rc|=bt_run(b->children+i); return rc; }
else if( b->type == cc8(r,o,o,t,f,a,i,l) ) { rc = 1; for( int i = 0; i < array_count(b->children); ++i ) rc&=~bt_run(b->children+i); return rc; }
return 0;
}
int ui_bt(bt_t *b) {
if( b ) {
char *info = bt_funcname(b->action);
if(!info) info = va("%d", array_count(b->children));
if( ui_collapse(va("%s (%s)", cc8str(b->type), info), va("bt%p",b)) ) {
for( int i = 0; i < array_count(b->children); ++i) {
ui_bt(b->children + i);
}
ui_collapse_end();
}
}
return 0;
}

View File

@ -1,23 +0,0 @@
// Behavior trees: decision planning and decision making.
// Supersedes finite state-machines (FSM) and hierarchical finite state-machines (HFSM).
// - rlyeh, public domain.
typedef int (*bt_func)();
typedef struct bt_t {
uint64_t type;
int (*action)();
union {
int argi;
float argf;
};
array(struct bt_t) children;
} bt_t;
API bt_t bt(const char *ini_file, unsigned flags);
API int bt_run(bt_t *b);
API void bt_addfun(const char *name, int(*func)());
API bt_func bt_findfun(const char *name);
API char *bt_funcname(bt_func fn);
API int ui_bt(bt_t *b);

View File

@ -1,753 +0,0 @@
// ----------------------------------------------------------------------------
// compression api
static struct zcompressor {
// id of compressor
unsigned enumerator;
// name of compressor
const char name1, *name4, *name;
// returns worst case compression estimation for selected flags
unsigned (*bounds)(unsigned bytes, unsigned flags);
// returns number of bytes written. 0 if error.
unsigned (*encode)(const void *in, unsigned inlen, void *out, unsigned outcap, unsigned flags);
// returns number of excess bytes that will be overwritten when decoding.
unsigned (*excess)(unsigned flags);
// returns number of bytes written. 0 if error.
unsigned (*decode)(const void *in, unsigned inlen, void *out, unsigned outcap);
} zlist[] = {
{ COMPRESS_RAW, '0', "raw", "raw", raw_bounds, raw_encode, raw_excess, raw_decode },
{ COMPRESS_PPP, 'p', "ppp", "ppp", ppp_bounds, ppp_encode, ppp_excess, ppp_decode },
{ COMPRESS_ULZ, 'u', "ulz", "ulz", ulz_bounds, ulz_encode, ulz_excess, ulz_decode },
{ COMPRESS_LZ4, '4', "lz4x", "lz4x", lz4x_bounds, lz4x_encode, lz4x_excess, lz4x_decode },
{ COMPRESS_CRUSH, 'c', "crsh", "crush", crush_bounds, crush_encode, crush_excess, crush_decode },
{ COMPRESS_DEFLATE, 'd', "defl", "deflate", deflate_bounds, deflate_encode, deflate_excess, deflate_decode },
{ COMPRESS_LZP1, '1', "lzp1", "lzp1", lzp1_bounds, lzp1_encode, lzp1_excess, lzp1_decode },
{ COMPRESS_LZMA, 'm', "lzma", "lzma", lzma_bounds, lzma_encode, lzma_excess, lzma_decode },
{ COMPRESS_BALZ, 'b', "balz", "balz", balz_bounds, balz_encode, balz_excess, balz_decode },
{ COMPRESS_LZW3, 'w', "lzw3", "lzrw3a", lzrw3a_bounds, lzrw3a_encode, lzrw3a_excess, lzrw3a_decode },
{ COMPRESS_LZSS, 's', "lzss", "lzss", lzss_bounds, lzss_encode, lzss_excess, lzss_decode },
{ COMPRESS_BCM, 'B', "bcm", "bcm", bcm_bounds, bcm_encode, bcm_excess, bcm_decode },
{ COMPRESS_ZLIB, 'z', "zlib", "zlib", deflate_bounds, deflatez_encode, deflate_excess, deflatez_decode },
};
enum { COMPRESS_NUM = 14 };
static char *znameof(unsigned flags) {
static __thread char buf[16];
snprintf(buf, 16, "%4s.%c", zlist[(flags>>4)&0x0F].name4, "0123456789ABCDEF"[flags&0xF]);
return buf;
}
unsigned zencode(void *out, unsigned outlen, const void *in, unsigned inlen, unsigned flags) {
return zlist[(flags >> 4) % COMPRESS_NUM].encode(in, inlen, (uint8_t*)out, outlen, flags & 0x0F);
}
unsigned zdecode(void *out, unsigned outlen, const void *in, unsigned inlen, unsigned flags) {
return zlist[(flags >> 4) % COMPRESS_NUM].decode((uint8_t*)in, inlen, out, outlen);
}
unsigned zbounds(unsigned inlen, unsigned flags) {
return zlist[(flags >> 4) % COMPRESS_NUM].bounds(inlen, flags & 0x0F);
}
unsigned zexcess(unsigned flags) {
return zlist[(flags >> 4) % COMPRESS_NUM].excess(flags & 0x0F);
}
// ----------------------------------------------------------------------------
// BASE92 en/decoder
// THE BEERWARE LICENSE (Revision 42):
// <thenoviceoof> wrote this file. As long as you retain this notice you
// can do whatever you want with this stuff. If we meet some day, and you
// think this stuff is worth it, you can buy me a beer in return
// - Nathan Hwang (thenoviceoof)
unsigned base92_bounds(unsigned inlen) {
unsigned size = (inlen * 8) % 13, extra_null = 1;
if(size == 0) return 2 * ((inlen * 8) / 13) + extra_null;
if(size < 7) return 2 * ((inlen * 8) / 13) + extra_null + 1;
return 2 * ((inlen * 8) / 13) + extra_null + 2;
}
unsigned base92_encode(const void* in, unsigned inlen, void *out, unsigned size) {
char *res = (char *)out;
const unsigned char *str = (const unsigned char *)in;
unsigned int j = 0; // j for encoded
unsigned long workspace = 0; // bits holding bin
unsigned short wssize = 0; // number of good bits in workspace
unsigned char c;
const unsigned char ENCODE_MAPPING[256] = {
33, 35, 36, 37, 38, 39, 40, 41, 42, 43,
44, 45, 46, 47, 48, 49, 50, 51, 52, 53,
54, 55, 56, 57, 58, 59, 60, 61, 62, 63,
64, 65, 66, 67, 68, 69, 70, 71, 72, 73,
74, 75, 76, 77, 78, 79, 80, 81, 82, 83,
84, 85, 86, 87, 88, 89, 90, 91, 92, 93,
94, 95, 97, 98, 99, 100, 101, 102, 103, 104,
105, 106, 107, 108, 109, 110, 111, 112, 113, 114,
115, 116, 117, 118, 119, 120, 121, 122, 123, 124,
125, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0
};
if (inlen) {
for (unsigned i = 0; i < inlen; i++) {
workspace = workspace << 8 | str[i];
wssize += 8;
if (wssize >= 13) {
int tmp = (workspace >> (wssize - 13)) & 8191;
c = ENCODE_MAPPING[(tmp / 91)];
if (c == 0) return 0; // illegal char
res[j++] = c;
c = ENCODE_MAPPING[(tmp % 91)];
if (c == 0) return 0; // illegal char
res[j++] = c;
wssize -= 13;
}
}
// encode a last byte
if (0 < wssize && wssize < 7) {
int tmp = (workspace << (6 - wssize)) & 63; // pad the right side
c = ENCODE_MAPPING[(tmp)];
if (c == 0) return 0; // illegal char
res[j++] = c;
} else if (7 <= wssize) {
int tmp = (workspace << (13 - wssize)) & 8191; // pad the right side
c = ENCODE_MAPPING[(tmp / 91)];
if (c == 0) return 0; // illegal char
res[j++] = c;
c = ENCODE_MAPPING[(tmp % 91)];
if (c == 0) return 0; // illegal char
res[j++] = c;
}
} else {
res[j++] = '~';
}
// add null byte
res[j] = 0;
return j;
}
// this guy expects a null-terminated string
// gives back a non-null terminated string, and properly filled len
unsigned base92_decode(const void* in, unsigned size, void *out, unsigned outlen_unused) {
const char* str = (const char*)in;
unsigned char *res = (unsigned char *)out;
int i, j = 0, b1, b2;
unsigned long workspace = 0;
unsigned short wssize = 0;
const unsigned char DECODE_MAPPING[256] = {
255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 0, 255, 1, 2, 3, 4, 5,
6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
16, 17, 18, 19, 20, 21, 22, 23, 24, 25,
26, 27, 28, 29, 30, 31, 32, 33, 34, 35,
36, 37, 38, 39, 40, 41, 42, 43, 44, 45,
46, 47, 48, 49, 50, 51, 52, 53, 54, 55,
56, 57, 58, 59, 60, 61, 255, 62, 63, 64,
65, 66, 67, 68, 69, 70, 71, 72, 73, 74,
75, 76, 77, 78, 79, 80, 81, 82, 83, 84,
85, 86, 87, 88, 89, 90, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255
};
// handle small cases first
if (size == 0 || (str[0] == '~' && str[1] == '\0')) {
res[0] = 0;
return 1;
}
// calculate size
int len = ((size/2 * 13) + (size%2 * 6)) / 8;
// handle pairs of chars
for (i = 0; i + 1 < size; i += 2) {
b1 = DECODE_MAPPING[(str[i])];
b2 = DECODE_MAPPING[(str[i+1])];
workspace = (workspace << 13) | (b1 * 91 + b2);
wssize += 13;
while (wssize >= 8) {
res[j++] = (workspace >> (wssize - 8)) & 255;
wssize -= 8;
}
}
// handle single char
if (size % 2 == 1) {
workspace = (workspace << 6) | DECODE_MAPPING[(str[size - 1])];
wssize += 6;
while (wssize >= 8) {
res[j++] = (workspace >> (wssize - 8)) & 255;
wssize -= 8;
}
}
//assert(j == len);
return j;
}
// ----------------------------------------------------------------------------
// COBS en/decoder
// Based on code by Jacques Fortier.
// "Redistribution and use in source and binary forms are permitted, with or without modification."
//
// Consistent Overhead Byte Stuffing is an encoding that removes all 0 bytes from arbitrary binary data.
// The encoded data consists only of bytes with values from 0x01 to 0xFF. This is useful for preparing data for
// transmission over a serial link (RS-232 or RS-485 for example), as the 0 byte can be used to unambiguously indicate
// packet boundaries. COBS also has the advantage of adding very little overhead (at least 1 byte, plus up to an
// additional byte per 254 bytes of data). For messages smaller than 254 bytes, the overhead is constant.
//
// This implementation is designed to be both efficient and robust.
// The decoder is designed to detect malformed input data and report an error upon detection.
//
unsigned cobs_bounds( unsigned len ) {
return len + ceil(len / 254.0) + 1;
}
unsigned cobs_encode(const void *in, unsigned inlen, void *out, unsigned outlen) {
const uint8_t *src = (const uint8_t *)in;
uint8_t *dst = (uint8_t*)out;
size_t srclen = inlen;
uint8_t code = 1;
size_t read_index = 0, write_index = 1, code_index = 0;
while( read_index < srclen ) {
if( src[ read_index ] == 0) {
dst[ code_index ] = code;
code = 1;
code_index = write_index++;
read_index++;
} else {
dst[ write_index++ ] = src[ read_index++ ];
code++;
if( code == 0xFF ) {
dst[ code_index ] = code;
code = 1;
code_index = write_index++;
}
}
}
dst[ code_index ] = code;
return write_index;
}
unsigned cobs_decode(const void *in, unsigned inlen, void *out, unsigned outlen) {
const uint8_t *src = (const uint8_t *)in;
uint8_t *dst = (uint8_t*)out;
size_t srclen = inlen;
uint8_t code, i;
size_t read_index = 0, write_index = 0;
while( read_index < srclen ) {
code = src[ read_index ];
if( ((read_index + code) > srclen) && (code != 1) ) {
return 0;
}
read_index++;
for( i = 1; i < code; i++ ) {
dst[ write_index++ ] = src[ read_index++ ];
}
if( (code != 0xFF) && (read_index != srclen) ) {
dst[ write_index++ ] = '\0';
}
}
return write_index;
}
#if 0
static
void cobs_test( const char *buffer, int buflen ) {
char enc[4096];
int enclen = cobs_encode( buffer, buflen, enc, 4096 );
char dec[4096];
int declen = cobs_decode( enc, enclen, dec, 4096 );
test( enclen >= buflen );
test( declen == buflen );
test( memcmp(dec, buffer, buflen) == 0 );
printf("%d->%d->%d (+%d extra bytes)\n", declen, enclen, declen, enclen - declen);
}
AUTORUN {
const char *null = 0;
cobs_test( null, 0 );
const char empty[] = "";
cobs_test( empty, sizeof(empty) );
const char text[] = "hello world\n";
cobs_test( text, sizeof(text) );
const char bintext[] = "hello\0\0\0world\n";
cobs_test( bintext, sizeof(bintext) );
const char blank[512] = {0};
cobs_test( blank, sizeof(blank) );
char longbintext[1024];
for( int i = 0; i < 1024; ++i ) longbintext[i] = (unsigned char)i;
cobs_test( longbintext, sizeof(longbintext) );
assert(~puts("Ok"));
}
#endif
// ----------------------------------------------------------------------------
// netstring en/decoder
// - rlyeh, public domain.
unsigned netstring_bounds(unsigned inlen) {
return 5 + inlen + 3; // 3 for ;,\0 + 5 if inlen < 100k ; else (unsigned)ceil(log10(inlen + 1))
}
unsigned netstring_encode(const char *in, unsigned inlen, char *out, unsigned outlen) {
// if(outlen < netstring_bounds(inlen)) return 0;
sprintf(out, "%u:%.*s,", inlen, inlen, in);
return strlen(out);
}
unsigned netstring_decode(const char *in, unsigned inlen, char *out, unsigned outlen) {
// if(outlen < inlen) return 0;
const char *bak = in;
sscanf(in, "%u", &outlen);
while( *++in != ':' );
memcpy(out, in+1, outlen), out[outlen-1] = 0;
// return outlen; // number of written bytes
return (outlen + (in+2 - bak)); // number of read bytes
}
#if 0
AUTORUN {
// encode
const char text1[] = "hello world!", text2[] = "abc123";
unsigned buflen = netstring_bounds(strlen(text1) + strlen(text2));
char *buf = malloc(buflen), *ptr = buf;
ptr += netstring_encode(text1, strlen(text1), ptr, buflen -= (ptr - buf));
ptr += netstring_encode(text2, strlen(text2), ptr, buflen -= (ptr - buf));
printf("%s -> ", buf);
// decode
char out[12];
unsigned plen = strlen(ptr = buf);
while(plen > 0) {
int written = netstring_decode(ptr, plen, out, 12);
ptr += written;
plen -= written;
printf("'%s'(%s)(%d), ", out, ptr, plen );
}
puts("");
}
#endif
// ----------------------------------------------------------------------------
// array de/interleaving
// - rlyeh, public domain.
//
// results:
// R0G0B0 R1G1B1 R2G2B2... -> R0R1R2... B0B1B2... G0G1G2...
// R0G0B0A0 R1G1B1A1 R2G2B2A2... -> R0R1R2... A0A1A2... B0B1B2... G0G1G2...
void *interleave( void *out, const void *list, int list_count, int sizeof_item, unsigned columns ) {
void *bak = out;
assert( columns < list_count ); // required
int row_count = list_count / columns;
for( int offset = 0; offset < columns; offset++ ) {
for( int row = 0; row < row_count; row++ ) {
memcpy( out, &((char*)list)[ (offset + row * columns) * sizeof_item ], sizeof_item );
out = ((char*)out) + sizeof_item;
}
}
return bak;
}
#if 0
static
void interleave_test( const char *name, int interleaving, int deinterleaving, const char *original ) {
char interleaved[128] = {0};
interleave( interleaved, original, strlen(original)/2, 2, interleaving );
char deinterleaved[128] = {0};
interleave( deinterleaved, interleaved, strlen(original)/2, 2, deinterleaving );
printf( "\n%s\n", name );
printf( "original:\t%s\n", original );
printf( "interleaved:\t%s\n", interleaved );
printf( "deinterleaved:\t%s\n", deinterleaved );
assert( 0 == strcmp(original, deinterleaved) );
}
AUTORUN {
interleave_test(
"audio 2ch", 2, 3,
"L0R0"
"L1R1"
"L2R2"
);
interleave_test(
"image 3ch", 3, 3,
"R0G0B0"
"R1G1B1"
"R2G2B2"
);
interleave_test(
"image 4ch", 4, 3,
"R0G0B0A0"
"R1G1B1A1"
"R2G2B2A2"
);
interleave_test(
"audio 5ch", 5, 3,
"A0B0C0L0R0"
"A1B1C1L1R1"
"A2B2C2L2R2"
);
interleave_test(
"audio 5.1ch", 6, 3,
"A0B0C0L0R0S0"
"A1B1C1L1R1S1"
"A2B2C2L2R2S2"
);
interleave_test(
"opengl material 9ch", 9, 3,
"X0Y0Z0q0w0e0r0u0v0"
"X1Y1Z1q1w1e1r1u1v1"
"X2Y2Z2q2w2e2r2u2v2"
);
interleave_test(
"opengl material 10ch", 10, 3,
"X0Y0Z0q0w0e0r0s0u0v0"
"X1Y1Z1q1w1e1r1s1u1v1"
"X2Y2Z2q2w2e2r2s2u2v2"
);
assert(~puts("Ok"));
}
#endif
// ----------------------------------------------------------------------------
// delta encoder
#define delta_expand_template(N) \
void delta##N##_encode(void *buffer_, unsigned count) { \
uint##N##_t current, last = 0, *buffer = (uint##N##_t*)buffer_; \
for( unsigned i = 0; i < count; i++ ) { \
current = buffer[i]; \
buffer[i] = current - last; \
last = current; \
} \
} \
void delta##N##_decode(void *buffer_, unsigned count) { \
uint##N##_t delta, last = 0, *buffer = (uint##N##_t*)buffer_; \
for( unsigned i = 0; i < count; i++ ) { \
delta = buffer[i]; \
buffer[i] = delta + last; \
last = buffer[i]; \
} \
}
delta_expand_template(8);
delta_expand_template(16);
delta_expand_template(32);
delta_expand_template(64);
#if 0
AUTORUN {
char buf[] = "1231112223345555";
int buflen = strlen(buf);
char *dt = strdup(buf);
printf(" delta8: ", dt);
for( int i = 0; i < buflen; ++i ) printf("%c", dt[i] );
printf("->");
delta8_encode(dt, buflen);
for( int i = 0; i < buflen; ++i ) printf("%02d,", dt[i] );
printf("->");
delta8_decode(dt, buflen);
for( int i = 0; i < buflen; ++i ) printf("%c", dt[i] );
printf("\r%c\n", 0 == strcmp(buf,dt) ? 'Y':'N');
}
#endif
// ----------------------------------------------------------------------------
// zigzag en/decoder
// - rlyeh, public domain
uint64_t zig64( int64_t value ) { // convert sign|magnitude to magnitude|sign
return (value >> 63) ^ (value << 1);
}
int64_t zag64( uint64_t value ) { // convert magnitude|sign to sign|magnitude
return (value >> 1) ^ -(value & 1);
}
// branchless zigzag encoding 32/64
// sign|magnitude to magnitude|sign and back
// [ref] https://developers.google.com/protocol-buffers/docs/encoding
uint32_t enczig32u( int32_t n) { return ((n << 1) ^ (n >> 31)); }
uint64_t enczig64u( int64_t n) { return ((n << 1) ^ (n >> 63)); }
int32_t deczig32i(uint32_t n) { return ((n >> 1) ^ -(n & 1)); }
int64_t deczig64i(uint64_t n) { return ((n >> 1) ^ -(n & 1)); }
#if 0
AUTORUN {
int16_t x = -1000;
printf("%d -> %llu %llx -> %lld\n", x, zig64(x), zig64(x), zag64(zig64(x)));
}
AUTORUN {
#define CMP32(signedN) do { \
int32_t reconverted = deczig32i( enczig32u(signedN) ); \
int equal = signedN == reconverted; \
printf("[%s] %d vs %d\n", equal ? " OK " : "FAIL", signedN, reconverted ); \
} while(0)
#define CMP64(signedN) do { \
int64_t reconverted = deczig64i( enczig64u(signedN) ); \
int equal = signedN == reconverted; \
printf("[%s] %lld vs %lld\n", equal ? " OK " : "FAIL", signedN, reconverted ); \
} while(0)
CMP32( 0);
CMP32(-1);
CMP32(+1);
CMP32(-2);
CMP32(+2);
CMP32(INT32_MAX - 1);
CMP32(INT32_MIN + 1);
CMP32(INT32_MAX);
CMP32(INT32_MIN);
CMP64( 0ll);
CMP64(-1ll);
CMP64(+1ll);
CMP64(-2ll);
CMP64(+2ll);
CMP64(INT64_MAX - 1);
CMP64(INT64_MIN + 1);
CMP64(INT64_MAX);
CMP64(INT64_MIN);
}
void TESTU( uint64_t N ) {
uint8_t buf[9] = {0};
enczig64i(buf, (N));
uint64_t reconstructed = deczig64i(buf, 0);
if( reconstructed != (N) ) printf("[FAIL] %llu vs %02x %02x %02x %02x %02x %02x %02x %02x %02x\n", (N), buf[0], buf[1], buf[2], buf[3], buf[4], buf[5], buf[6], buf[7], buf[8] );
else if( 0xffffff == ((N) & 0xffffff) ) printf("[ OK ] %llx\n", (N));
}
void TESTI( int64_t N ) {
TESTU( enczig64u(N) );
}
AUTORUN {
TESTU(0LLU);
TESTU(1LLU);
TESTU(2LLU);
TESTU(UINT64_MAX/8);
TESTU(UINT64_MAX/4);
TESTU(UINT64_MAX/2);
TESTU(UINT64_MAX-2);
TESTU(UINT64_MAX-1);
TESTU(UINT64_MAX);
#pragma omp parallel for // compile with /openmp
for( int64_t N = INT64_MIN; N < INT64_MAX; ++N ) {
TESTU(N);
TESTI((int64_t)N);
}
}
#endif
// ----------------------------------------------------------------------------
// ARC4 en/decryptor. Based on code by Mike Shaffer.
// - rlyeh, public domain.
void *arc4( void *buf_, unsigned buflen, const void *pass_, unsigned passlen ) {
// [ref] http://www.4guysfromrolla.com/webtech/code/rc4.inc.html
assert(passlen);
int sbox[256], key[256];
char *buf = (char*)buf_;
const char *pass = (const char*)pass_;
for( unsigned a = 0; a < 256; a++ ) {
key[a] = pass[a % passlen];
sbox[a] = a;
}
for( unsigned a = 0, b = 0; a < 256; a++ ) {
b = (b + sbox[a] + key[a]) % 256;
int swap = sbox[a]; sbox[a] = sbox[b]; sbox[b] = swap;
}
for( unsigned a = 0, b = 0, i = 0; i < buflen; ++i ) {
a = (a + 1) % 256;
b = (b + sbox[a]) % 256;
int swap = sbox[a]; sbox[a] = sbox[b]; sbox[b] = swap;
buf[i] ^= sbox[(sbox[a] + sbox[b]) % 256];
}
return buf_;
}
#if 0
AUTORUN {
char buffer[] = "Hello world."; int buflen = strlen(buffer);
char *password = "abc123"; int passlen = strlen(password);
printf("Original: %s\n", buffer);
printf("Password: %s\n", password);
char *encrypted = arc4( buffer, buflen, password, passlen );
printf("ARC4 Encrypted text: '%s'\n", encrypted);
char *decrypted = arc4( buffer, buflen, password, passlen );
printf("ARC4 Decrypted text: '%s'\n", decrypted);
}
#endif
// ----------------------------------------------------------------------------
// crc64
// - rlyeh, public domain
uint64_t crc64(uint64_t h, const void *ptr, uint64_t len) {
// based on public domain code by Lasse Collin
// also, use poly64 0xC96C5795D7870F42 for crc64-ecma
static uint64_t crc64_table[256];
static uint64_t poly64 = UINT64_C(0x95AC9329AC4BC9B5);
if( poly64 ) {
for( int b = 0; b < 256; ++b ) {
uint64_t r = b;
for( int i = 0; i < 8; ++i ) {
r = r & 1 ? (r >> 1) ^ poly64 : r >> 1;
}
crc64_table[ b ] = r;
//printf("%016llx\n", crc64_table[b]);
}
poly64 = 0;
}
const uint8_t *buf = (const uint8_t *)ptr;
uint64_t crc = ~h; // ~crc;
while( len != 0 ) {
crc = crc64_table[(uint8_t)crc ^ *buf++] ^ (crc >> 8);
--len;
}
return ~crc;
}
#if 0
unsigned crc32(unsigned h, const void *ptr_, unsigned len) {
// based on public domain code by Karl Malbrain
const uint8_t *ptr = (const uint8_t *)ptr_;
if (!ptr) return 0;
const unsigned tbl[16] = {
0x00000000, 0x1db71064, 0x3b6e20c8, 0x26d930ac, 0x76dc4190, 0x6b6b51f4, 0x4db26158, 0x5005713c,
0xedb88320, 0xf00f9344, 0xd6d6a3e8, 0xcb61b38c, 0x9b64c2b0, 0x86d3d2d4, 0xa00ae278, 0xbdbdf21c };
for(h = ~h; len--; ) { uint8_t b = *ptr++; h = (h >> 4) ^ tbl[(h & 15) ^ (b & 15)]; h = (h >> 4) ^ tbl[(h & 15) ^ (b >> 4)]; }
return ~h;
}
#endif
// ----------------------------------------------------------------------------
// entropy encoder
#if is(win32)
#include <winsock2.h>
#include <wincrypt.h>
#pragma comment(lib, "advapi32")
void entropy( void *buf, unsigned n ) {
HCRYPTPROV provider;
if( CryptAcquireContext( &provider, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT ) == 0 ) {
assert(!"CryptAcquireContext failed");
}
int rc = CryptGenRandom( provider, n, (BYTE *)buf );
assert( rc != 0 );
CryptReleaseContext( provider, 0 );
}
#elif is(linux) || is(osx)
void entropy( void *buf, unsigned n ) {
FILE *fp = fopen( "/dev/urandom", "r" );
if( !fp ) assert(!"/dev/urandom open failed");
size_t read = fread( buf, 1, n, fp );
assert( read == n && "/dev/urandom read failed" );
fclose( fp );
}
#else // unused for now. likely emscripten will hit this
// pseudo random number generator with 128 bit internal state... probably not suited for cryptographical usage.
// [src] http://github.com/kokke (UNLICENSE)
// [ref] http://burtleburtle.net/bob/rand/smallprng.html
#include <time.h>
#if is(win32)
#include <process.h>
#else
#include <unistd.h>
#endif
static uint32_t prng_next(void) {
#define prng_rotate(x,k) (x << k) | (x >> (32 - k))
#define prng_shuffle() do { \
uint32_t e = ctx[0] - prng_rotate(ctx[1], 27); \
ctx[0] = ctx[1] ^ prng_rotate(ctx[2], 17); \
ctx[1] = ctx[2] + ctx[3]; \
ctx[2] = ctx[3] + e; \
ctx[3] = e + ctx[0]; } while(0)
static __thread uint32_t ctx[4], *once = 0; if( !once ) {
uint32_t seed = (uint32_t)( ifdef(win32,_getpid,getpid)() + time(0) + ((uintptr_t)once) );
ctx[0] = 0xf1ea5eed;
ctx[1] = ctx[2] = ctx[3] = seed;
for (int i = 0; i < 31; ++i) {
prng_shuffle();
}
once = ctx;
}
prng_shuffle();
return ctx[3];
}
void entropy( void *buf, unsigned n ) {
for( ; n >= 4 ; n -= 4 ) {
uint32_t a = prng_next();
memcpy(buf, &a, 4);
buf = ((char*)buf) + 4;
}
if( n > 0 ) {
uint32_t a = prng_next();
memcpy(buf, &a, n);
}
}
#endif
#if 0
AUTORUN {
unsigned char buf[128];
entropy(buf, 128);
for( int i = 0; i < 128; ++i ) {
printf("%02x", buf[i]);
}
puts("");
}
#endif

View File

@ -1,95 +0,0 @@
// ----------------------------------------------------------------------------
// compression api
enum COMPRESS_FLAGS {
COMPRESS_RAW = 0,
COMPRESS_PPP = (1<<4),
COMPRESS_ULZ = (2<<4),
COMPRESS_LZ4 = (3<<4),
COMPRESS_CRUSH = (4<<4),
COMPRESS_DEFLATE = (5<<4),
COMPRESS_LZP1 = (6<<4),
COMPRESS_LZMA = (7<<4),
COMPRESS_BALZ = (8<<4),
COMPRESS_LZW3 = (9<<4),
COMPRESS_LZSS = (10<<4),
COMPRESS_BCM = (11<<4),
COMPRESS_ZLIB = (12<<4), // same as deflate with header
};
API unsigned zbounds(unsigned inlen, unsigned flags);
API unsigned zencode(void *out, unsigned outlen, const void *in, unsigned inlen, unsigned flags);
API unsigned zexcess(unsigned flags);
API unsigned zdecode(void *out, unsigned outlen, const void *in, unsigned inlen, unsigned flags);
// ----------------------------------------------------------------------------
// array de/interleaving
// - rlyeh, public domain.
//
// results:
// R0G0B0 R1G1B1 R2G2B2... -> R0R1R2... B0B1B2... G0G1G2...
// R0G0B0A0 R1G1B1A1 R2G2B2A2... -> R0R1R2... A0A1A2... B0B1B2... G0G1G2...
API void *interleave( void *out, const void *list, int list_count, int sizeof_item, unsigned columns );
// ----------------------------------------------------------------------------
// cobs en/decoder
API unsigned cobs_bounds(unsigned len);
API unsigned cobs_encode(const void *in, unsigned inlen, void *out, unsigned outlen);
API unsigned cobs_decode(const void *in, unsigned inlen, void *out, unsigned outlen);
// ----------------------------------------------------------------------------
// base92 en/decoder
API unsigned base92_encode(const void *in, unsigned inlen, void* out, unsigned outlen);
API unsigned base92_decode(const void *in, unsigned inlen, void* out, unsigned outlen);
API unsigned base92_bounds(unsigned inlen);
// ----------------------------------------------------------------------------
// netstring en/decoder
API unsigned netstring_bounds(unsigned inlen);
API unsigned netstring_encode(const char *in, unsigned inlen, char *out, unsigned outlen);
API unsigned netstring_decode(const char *in, unsigned inlen, char *out, unsigned outlen);
// ----------------------------------------------------------------------------
// delta en/decoder
API void delta8_encode(void *buffer, unsigned count);
API void delta8_decode(void *buffer, unsigned count);
API void delta16_encode(void *buffer, unsigned count);
API void delta16_decode(void *buffer, unsigned count);
API void delta32_encode(void *buffer, unsigned count);
API void delta32_decode(void *buffer, unsigned count);
API void delta64_encode(void *buffer, unsigned count);
API void delta64_decode(void *buffer, unsigned count);
// ----------------------------------------------------------------------------
// zigzag en/decoder
API uint64_t zig64( int64_t value ); // convert sign|magnitude to magnitude|sign
API int64_t zag64( uint64_t value ); // convert magnitude|sign to sign|magnitude
API uint32_t enczig32u( int32_t n);
API uint64_t enczig64u( int64_t n);
API int32_t deczig32i(uint32_t n);
API int64_t deczig64i(uint64_t n);
// ----------------------------------------------------------------------------
// arc4 en/decryptor
API void *arc4( void *buffer, unsigned buflen, const void *pass, unsigned passlen );
// ----------------------------------------------------------------------------
// crc64
API uint64_t crc64(uint64_t h, const void *ptr, uint64_t len);
// ----------------------------------------------------------------------------
// entropy encoder
API void entropy( void *buf, unsigned n );

View File

@ -1,3 +1,37 @@
// dll ------------------------------------------------------------------------
#if is(win32)
# include <winsock2.h>
# define dlopen(name,mode) (void*)( (name) ? LoadLibraryA(name) : GetModuleHandle(NULL))
# define dlsym(handle,symbol) GetProcAddress((HMODULE)handle, symbol )
# define dlclose(handle) 0
#else
# include <dlfcn.h>
#endif
void* dll(const char *filename, const char *symbol) {
/*
char *buf, *base = file_name(filename);
if( file_exists(buf = va("%s", base)) ||
file_exists(buf = va("%s.dll", base)) ||
file_exists(buf = va("%s.so", base)) ||
file_exists(buf = va("lib%s.so", base)) ||
file_exists(buf = va("%s.dylib", base)) ) {
filename = buf;
}
*/
void *dll = dlopen(filename, RTLD_NOW | RTLD_LOCAL);
dll = dll ? dlsym(dll, symbol) : 0;
return dll;
}
#if 0 // demo: cl demo.c /LD && REM dll
EXPORT int add2(int a, int b) { return a + b; }
int main() { int (*adder)() = dll("demo.dll", "add2"); printf("%d\n", adder(2,3)); }
#endif
// script ---------------------------------------------------------------------
typedef lua_State lua;
// the Lua interpreter
@ -171,7 +205,7 @@ void script_init() {
bool script_tests() {
// script test (lua)
script_run( "-- Bye.lua\nio.write(\"script test: Bye world!, from \", _VERSION, \"\\n\")" );
script_run( "-- Bye.lua\nio.write(\"script test: Bye world!, from \", _VERSION, \"\\n\")" );
return true;
}

View File

@ -0,0 +1,24 @@
// dll ------------------------------------------------------------------------
/// !!! `filename` must contain extension
/// load dynamic library `file` and search for `symbol`
/// return: NULL if not found, found symbol otherwise.
/// filename: path to dynamic library file. must contain extension.
/// symbol: symbol name. must not be NULL.
/// see: dlopen^, dlclose^
/// > bool (*plugin_init)(void) = dll("plugin.dll", "init");
/// > assert(plugin_init());
API void* dll(const char *filename, const char *symbol);
// -----------------------------------------------------------------------------
// script framework
API void script_init();
API void script_run(const char *script);
API void script_runfile(const char *pathfile);
API void script_bind_class(const char *objname, int num_methods, const char **c_names, void **c_functions);
API void script_bind_function(const char *c_name, void *c_function);
API void script_call(const char *lua_function);
API bool script_tests();

View File

@ -1,167 +0,0 @@
// -----------------------------------------------------------------------------
// factory of handle ids, based on code by randy gaul (PD/Zlib licensed)
// - rlyeh, public domain
//
// [src] http://www.randygaul.net/wp-content/uploads/2021/04/handle_table.cpp
// [ref] http://bitsquid.blogspot.com.es/2011/09/managing-decoupling-part-4-id-lookup.html
// [ref] http://glampert.com/2016/05-04/dissecting-idhashindex/
// [ref] https://github.com/nlguillemot/dof/blob/master/viewer/packed_freelist.h
// [ref] https://gist.github.com/pervognsen/ffd89e45b5750e9ce4c6c8589fc7f253
typedef union id64 {
uint64_t h;
struct {
#if (ID_INDEX_BITS+ID_COUNT_BITS) != 64
uint64_t padding : 64-ID_INDEX_BITS-ID_COUNT_BITS;
#endif
uint64_t index : ID_INDEX_BITS;
uint64_t count : ID_COUNT_BITS;
};
} id64;
typedef struct id_factory id_factory;
id_factory id_factory_create(uint64_t capacity /*= 256*/);
id64 id_factory_insert(id_factory *f, uint64_t data);
uint64_t id_factory_getvalue(id_factory *f, id64 handle);
void id_factory_setvalue(id_factory *f, id64 handle, uint64_t data);
void id_factory_erase(id_factory *f, id64 handle);
bool id_factory_isvalid(id_factory *f, id64 handle);
void id_factory_destroy(id_factory *f);
// ---
typedef struct id_factory {
uint64_t freelist;
uint64_t capacity;
uint64_t canary;
union entry* entries;
} id_factory;
typedef union entry {
struct {
uint64_t data : ID_DATA_BITS;
uint64_t count : ID_COUNT_BITS;
};
uint64_t h;
} entry;
id_factory id_factory_create(uint64_t capacity) {
if(!capacity) capacity = 1ULL << ID_INDEX_BITS;
id_factory f = {0};
f.entries = CALLOC(1, sizeof(entry) * capacity);
f.capacity = capacity;
for (int i = 0; i < capacity - 1; ++i) {
f.entries[i].data = i + 1;
f.entries[i].count = 0;
}
f.entries[capacity - 1].h = 0;
f.entries[capacity - 1].data = ~0;
f.entries[capacity - 1].count = ~0;
f.canary = f.entries[capacity - 1].data;
return f;
}
void id_factory_destroy(id_factory *f) {
FREE(f->entries);
}
id64 id_factory_insert(id_factory *f, uint64_t data) {
// pop element off the free list
assert(f->freelist != f->canary && "max alive capacity reached");
uint64_t index = f->freelist;
f->freelist = f->entries[f->freelist].data;
// create new id64
f->entries[index].data = data;
id64 handle = {0};
handle.index = index;
handle.count = f->entries[index].count;
return handle;
}
void id_factory_erase(id_factory *f, id64 handle) {
// push id64 onto the freelist
uint64_t index = handle.index;
f->entries[index].data = f->freelist;
f->freelist = index;
// increment the count. this signifies a change in lifetime (this particular id64 is now dead)
// the next time this particular index is used in alloc, a new `count` will be used to uniquely identify
f->entries[index].count++;
}
uint64_t id_factory_getvalue(id_factory *f, id64 handle) {
uint64_t index = handle.index;
uint64_t count = handle.count;
assert(f->entries[index].count == count);
return f->entries[index].data;
}
void id_factory_setvalue(id_factory *f, id64 handle, uint64_t data) {
uint64_t index = handle.index;
uint64_t count = handle.count;
assert(f->entries[index].count == count);
f->entries[index].data = data;
}
bool id_factory_isvalid(id_factory *f, id64 handle) {
uint64_t index = handle.index;
uint64_t count = handle.count;
if (index >= f->capacity) return false;
return f->entries[index].count == count;
}
#if 0
// monitor history of a single entity by running `id_factory | find " 123."`
AUTORUN {
trap_install();
id_factory f = id_factory_create(optioni("--NUM",256));
array(id64) ids = 0;
for( int i = 0 ; ; ++i, i &= ((1ULL << ID_INDEX_BITS) - 1) ) { // infinite wrap
printf("count_ids(%d) ", array_count(ids));
bool insert = randf() < 0.49; // biased against deletion
if( insert ) {
id64 h = id_factory_insert(&f, i);
array_push(ids, h);
printf("add %llu.%llu\n", h.index, h.count);
} else {
int count = array_count(ids);
if( count ) {
int chosen = randi(0,count);
printf("del %d.\n", chosen);
id64 h = ids[chosen];
id_factory_erase(&f, h);
array_erase(ids, chosen);
}
}
}
}
#endif
// ----------------------------------------------------------------------
// public api
static id_factory fid; // @fixme: threadsafe
uintptr_t id_make(void *ptr) {
do_once fid = id_factory_create(0), id_factory_insert(&fid, 0); // init and reserve id(0)
id64 newid = id_factory_insert(&fid, (uint64_t)(uintptr_t)ptr ); // 48-bit effective addr
return newid.h;
}
void *id_handle(uintptr_t id) {
return (void *)(uintptr_t)id_factory_getvalue(&fid, ((id64){ (uint64_t)id }) );
}
void id_dispose(uintptr_t id) {
id_factory_erase(&fid, ((id64){ (uint64_t)id }) );
}
bool id_valid(uintptr_t id) {
return id_factory_isvalid(&fid, ((id64){ (uint64_t)id }) );
}

View File

@ -1,26 +0,0 @@
// -----------------------------------------------------------------------------
// factory of handle ids, based on code by randy gaul (PD/Zlib licensed)
// - rlyeh, public domain
//
// [src] http://www.randygaul.net/wp-content/uploads/2021/04/handle_table.cpp
// [ref] http://bitsquid.blogspot.com.es/2011/09/managing-decoupling-part-4-id-lookup.html
// [ref] http://glampert.com/2016/05-04/dissecting-idhashindex/
// [ref] https://github.com/nlguillemot/dof/blob/master/viewer/packed_freelist.h
// [ref] https://gist.github.com/pervognsen/ffd89e45b5750e9ce4c6c8589fc7f253
// convert between hard refs (pointers) and weak refs (ids)
uintptr_t id_make(void *ptr);
void * id_handle(uintptr_t id);
void id_dispose(uintptr_t id);
bool id_valid(uintptr_t id);
// configuration:
// ideally, these two should be 32 each. they were changed to fit our OBJHEADER bits
#ifndef ID_INDEX_BITS
#define ID_INDEX_BITS 16
#endif
#ifndef ID_COUNT_BITS
#define ID_COUNT_BITS 3
#endif
// you cannot change this one: the number of ID_DATA_BITS you can store in a handle depends on ID_COUNT_BITS
#define ID_DATA_BITS (64-ID_COUNT_BITS)

View File

@ -1,3 +1,175 @@
// -----------------------------------------------------------------------------
// factory of handle ids, based on code by randy gaul (PD/Zlib licensed)
// - rlyeh, public domain
//
// [src] http://www.randygaul.net/wp-content/uploads/2021/04/handle_table.cpp
// [ref] http://bitsquid.blogspot.com.es/2011/09/managing-decoupling-part-4-id-lookup.html
// [ref] http://glampert.com/2016/05-04/dissecting-idhashindex/
// [ref] https://github.com/nlguillemot/dof/blob/master/viewer/packed_freelist.h
// [ref] https://gist.github.com/pervognsen/ffd89e45b5750e9ce4c6c8589fc7f253
// you cannot change this one: the number of ID_DATA_BITS you can store in a handle depends on ID_COUNT_BITS
#define ID_DATA_BITS (64-ID_COUNT_BITS)
typedef union id64 {
uint64_t h;
struct {
#if (ID_INDEX_BITS+ID_COUNT_BITS) != 64
uint64_t padding : 64-ID_INDEX_BITS-ID_COUNT_BITS;
#endif
uint64_t index : ID_INDEX_BITS;
uint64_t count : ID_COUNT_BITS;
};
} id64;
typedef struct id_factory id_factory;
id_factory id_factory_create(uint64_t capacity /*= 256*/);
id64 id_factory_insert(id_factory *f, uint64_t data);
uint64_t id_factory_getvalue(id_factory *f, id64 handle);
void id_factory_setvalue(id_factory *f, id64 handle, uint64_t data);
void id_factory_erase(id_factory *f, id64 handle);
bool id_factory_isvalid(id_factory *f, id64 handle);
void id_factory_destroy(id_factory *f);
// ---
typedef struct id_factory {
uint64_t freelist;
uint64_t capacity;
uint64_t canary;
union entry* entries;
} id_factory;
typedef union entry {
struct {
uint64_t data : ID_DATA_BITS;
uint64_t count : ID_COUNT_BITS;
};
uint64_t h;
} entry;
id_factory id_factory_create(uint64_t capacity) {
if(!capacity) capacity = 1ULL << ID_INDEX_BITS;
id_factory f = {0};
f.entries = CALLOC(1, sizeof(entry) * capacity);
f.capacity = capacity;
for (int i = 0; i < capacity - 1; ++i) {
f.entries[i].data = i + 1;
f.entries[i].count = 0;
}
f.entries[capacity - 1].h = 0;
f.entries[capacity - 1].data = ~0;
f.entries[capacity - 1].count = ~0;
f.canary = f.entries[capacity - 1].data;
return f;
}
void id_factory_destroy(id_factory *f) {
FREE(f->entries);
}
id64 id_factory_insert(id_factory *f, uint64_t data) {
// pop element off the free list
assert(f->freelist != f->canary && "max alive capacity reached");
uint64_t index = f->freelist;
f->freelist = f->entries[f->freelist].data;
// create new id64
f->entries[index].data = data;
id64 handle = {0};
handle.index = index;
handle.count = f->entries[index].count;
return handle;
}
void id_factory_erase(id_factory *f, id64 handle) {
// push id64 onto the freelist
uint64_t index = handle.index;
f->entries[index].data = f->freelist;
f->freelist = index;
// increment the count. this signifies a change in lifetime (this particular id64 is now dead)
// the next time this particular index is used in alloc, a new `count` will be used to uniquely identify
f->entries[index].count++;
}
uint64_t id_factory_getvalue(id_factory *f, id64 handle) {
uint64_t index = handle.index;
uint64_t count = handle.count;
assert(f->entries[index].count == count);
return f->entries[index].data;
}
void id_factory_setvalue(id_factory *f, id64 handle, uint64_t data) {
uint64_t index = handle.index;
uint64_t count = handle.count;
assert(f->entries[index].count == count);
f->entries[index].data = data;
}
bool id_factory_isvalid(id_factory *f, id64 handle) {
uint64_t index = handle.index;
uint64_t count = handle.count;
if (index >= f->capacity) return false;
return f->entries[index].count == count;
}
#if 0
// monitor history of a single entity by running `id_factory | find " 123."`
AUTORUN {
trap_install();
id_factory f = id_factory_create(optioni("--NUM",256));
array(id64) ids = 0;
for( int i = 0 ; ; ++i, i &= ((1ULL << ID_INDEX_BITS) - 1) ) { // infinite wrap
printf("count_ids(%d) ", array_count(ids));
bool insert = randf() < 0.49; // biased against deletion
if( insert ) {
id64 h = id_factory_insert(&f, i);
array_push(ids, h);
printf("add %llu.%llu\n", h.index, h.count);
} else {
int count = array_count(ids);
if( count ) {
int chosen = randi(0,count);
printf("del %d.\n", chosen);
id64 h = ids[chosen];
id_factory_erase(&f, h);
array_erase(ids, chosen);
}
}
}
}
#endif
// ----------------------------------------------------------------------
// public api
static id_factory fid; // @fixme: threadsafe
uintptr_t id_make(void *ptr) {
do_once fid = id_factory_create(0), id_factory_insert(&fid, 0); // init and reserve id(0)
id64 newid = id_factory_insert(&fid, (uint64_t)(uintptr_t)ptr ); // 48-bit effective addr
return newid.h;
}
void *id_handle(uintptr_t id) {
return (void *)(uintptr_t)id_factory_getvalue(&fid, ((id64){ (uint64_t)id }) );
}
void id_dispose(uintptr_t id) {
id_factory_erase(&fid, ((id64){ (uint64_t)id }) );
}
bool id_valid(uintptr_t id) {
return id_factory_isvalid(&fid, ((id64){ (uint64_t)id }) );
}
// ----------------------------------------------------------------------
// C objects framework
// - rlyeh, public domain.

View File

@ -1,3 +1,21 @@
// -----------------------------------------------------------------------------
// factory of handle ids
// convert between hard refs (pointers) and weak refs (ids)
uintptr_t id_make(void *ptr);
void * id_handle(uintptr_t id);
void id_dispose(uintptr_t id);
bool id_valid(uintptr_t id);
// configuration:
// ideally, these two should be 32 each. they were changed to fit our OBJHEADER bits
#ifndef ID_INDEX_BITS
#define ID_INDEX_BITS 16
#endif
#ifndef ID_COUNT_BITS
#define ID_COUNT_BITS 3
#endif
// C objects framework
// - rlyeh, public domain.
//
@ -142,17 +160,29 @@ void* obj_free(void *o);
// --- syntax sugars
#define EXTEND obj_extend
#define obj_extend(T,func) (obj_##func[OBJTYPE(T)] = (void*)T##_##func)
#define obj_method(method,o,...) (obj_##method[((obj*)(o))->objtype](o,##__VA_ARGS__)) // (obj_##method[((obj*)(o))->objtype]((o), ##__VA_ARGS__))
#define obj_extend(T,method) (obj_##method[OBJTYPE(T)] = (void*)T##_##method)
#define obj_method(method,o,...) (obj_##method[((struct obj*)(o))->objtype](o,##__VA_ARGS__)) // (obj_##method[((struct obj*)(o))->objtype]((o), ##__VA_ARGS__))
#define obj_hasmethod(o,method) (obj_typeid(o)[obj_##method])
#define obj_vtable(func,RC,...) RC macro(obj_##func)(){ __VA_ARGS__ }; RC (*obj_##func[256])() = { REPEAT256(macro(obj_##func)) };
#define obj_vtable_null(func,RC) RC (*obj_##func[256])() = { 0 }; // null virtual table. will crash unless obj_extend'ed
#define obj_vtable(method,RC,...) RC macro(obj_##method)(){ __VA_ARGS__ }; RC (*obj_##method[256])() = { REPEAT256(macro(obj_##method)) };
#define obj_vtable_null(method,RC) RC (*obj_##method[256])() = { 0 }; // null virtual table. will crash unless obj_extend'ed
#define REPEAT16(f) f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f
#define REPEAT64(f) REPEAT16(f),REPEAT16(f),REPEAT16(f),REPEAT16(f)
#define REPEAT256(f) REPEAT64(f),REPEAT64(f),REPEAT64(f),REPEAT64(f)
#undef EXTEND
#define EXTEND(...) EXPAND(EXTEND, __VA_ARGS__)
#define EXTEND2(o,F1) obj_extend(o,F1)
#define EXTEND3(o,F1,F2) obj_extend(o,F1), obj_extend(o,F2)
#define EXTEND4(o,F1,F2,F3) obj_extend(o,F1), obj_extend(o,F2), obj_extend(o,F3)
#define EXTEND5(o,F1,F2,F3,F4) obj_extend(o,F1), obj_extend(o,F2), obj_extend(o,F3), obj_extend(o,F4)
#define EXTEND6(o,F1,F2,F3,F4,F5) obj_extend(o,F1), obj_extend(o,F2), obj_extend(o,F3), obj_extend(o,F4), obj_extend(o,F5)
#define EXTEND7(o,F1,F2,F3,F4,F5,F6) obj_extend(o,F1), obj_extend(o,F2), obj_extend(o,F3), obj_extend(o,F4), obj_extend(o,F5), obj_extend(o,F6)
#define EXTEND8(o,F1,F2,F3,F4,F5,F6,F7) obj_extend(o,F1), obj_extend(o,F2), obj_extend(o,F3), obj_extend(o,F4), obj_extend(o,F5), obj_extend(o,F6), obj_extend(o,F7)
#define EXTEND9(o,F1,F2,F3,F4,F5,F6,F7,F8) obj_extend(o,F1), obj_extend(o,F2), obj_extend(o,F3), obj_extend(o,F4), obj_extend(o,F5), obj_extend(o,F6), obj_extend(o,F7), obj_extend(o,F8)
#define EXTEND10(o,F1,F2,F3,F4,F5,F6,F7,F8,F9) obj_extend(o,F1), obj_extend(o,F2), obj_extend(o,F3), obj_extend(o,F4), obj_extend(o,F5), obj_extend(o,F6), obj_extend(o,F7), obj_extend(o,F8), obj_extend(o,F9)
// --- declare vtables
API extern void (*obj_ctor[256])(); ///-
@ -199,7 +229,7 @@ API void* obj_unref(void *oo);
// scene tree
#define each_objchild(p,T,o) /*non-recursive*/ \
(array(obj*)* children = obj_children(p); children; children = 0) \
(array(struct obj*)* children = obj_children(p); children; children = 0) \
for(int _i = 1, _end = array_count(*children); _i < _end; ++_i) \
for(T o = (T)((*children)[_i]); o && (obj_parent(o) == p); o = 0)

View File

@ -1614,3 +1614,757 @@ AUTORUN {
hexdump(&fat, sizeof(struct bootsector));
}
#endif
// ----------------------------------------------------------------------------
// compression api
static struct zcompressor {
// id of compressor
unsigned enumerator;
// name of compressor
const char name1, *name4, *name;
// returns worst case compression estimation for selected flags
unsigned (*bounds)(unsigned bytes, unsigned flags);
// returns number of bytes written. 0 if error.
unsigned (*encode)(const void *in, unsigned inlen, void *out, unsigned outcap, unsigned flags);
// returns number of excess bytes that will be overwritten when decoding.
unsigned (*excess)(unsigned flags);
// returns number of bytes written. 0 if error.
unsigned (*decode)(const void *in, unsigned inlen, void *out, unsigned outcap);
} zlist[] = {
{ COMPRESS_RAW, '0', "raw", "raw", raw_bounds, raw_encode, raw_excess, raw_decode },
{ COMPRESS_PPP, 'p', "ppp", "ppp", ppp_bounds, ppp_encode, ppp_excess, ppp_decode },
{ COMPRESS_ULZ, 'u', "ulz", "ulz", ulz_bounds, ulz_encode, ulz_excess, ulz_decode },
{ COMPRESS_LZ4, '4', "lz4x", "lz4x", lz4x_bounds, lz4x_encode, lz4x_excess, lz4x_decode },
{ COMPRESS_CRUSH, 'c', "crsh", "crush", crush_bounds, crush_encode, crush_excess, crush_decode },
{ COMPRESS_DEFLATE, 'd', "defl", "deflate", deflate_bounds, deflate_encode, deflate_excess, deflate_decode },
{ COMPRESS_LZP1, '1', "lzp1", "lzp1", lzp1_bounds, lzp1_encode, lzp1_excess, lzp1_decode },
{ COMPRESS_LZMA, 'm', "lzma", "lzma", lzma_bounds, lzma_encode, lzma_excess, lzma_decode },
{ COMPRESS_BALZ, 'b', "balz", "balz", balz_bounds, balz_encode, balz_excess, balz_decode },
{ COMPRESS_LZW3, 'w', "lzw3", "lzrw3a", lzrw3a_bounds, lzrw3a_encode, lzrw3a_excess, lzrw3a_decode },
{ COMPRESS_LZSS, 's', "lzss", "lzss", lzss_bounds, lzss_encode, lzss_excess, lzss_decode },
{ COMPRESS_BCM, 'B', "bcm", "bcm", bcm_bounds, bcm_encode, bcm_excess, bcm_decode },
{ COMPRESS_ZLIB, 'z', "zlib", "zlib", deflate_bounds, deflatez_encode, deflate_excess, deflatez_decode },
};
enum { COMPRESS_NUM = 14 };
static char *znameof(unsigned flags) {
static __thread char buf[16];
snprintf(buf, 16, "%4s.%c", zlist[(flags>>4)&0x0F].name4, "0123456789ABCDEF"[flags&0xF]);
return buf;
}
unsigned zencode(void *out, unsigned outlen, const void *in, unsigned inlen, unsigned flags) {
return zlist[(flags >> 4) % COMPRESS_NUM].encode(in, inlen, (uint8_t*)out, outlen, flags & 0x0F);
}
unsigned zdecode(void *out, unsigned outlen, const void *in, unsigned inlen, unsigned flags) {
return zlist[(flags >> 4) % COMPRESS_NUM].decode((uint8_t*)in, inlen, out, outlen);
}
unsigned zbounds(unsigned inlen, unsigned flags) {
return zlist[(flags >> 4) % COMPRESS_NUM].bounds(inlen, flags & 0x0F);
}
unsigned zexcess(unsigned flags) {
return zlist[(flags >> 4) % COMPRESS_NUM].excess(flags & 0x0F);
}
// ----------------------------------------------------------------------------
// BASE92 en/decoder
// THE BEERWARE LICENSE (Revision 42):
// <thenoviceoof> wrote this file. As long as you retain this notice you
// can do whatever you want with this stuff. If we meet some day, and you
// think this stuff is worth it, you can buy me a beer in return
// - Nathan Hwang (thenoviceoof)
unsigned base92_bounds(unsigned inlen) {
unsigned size = (inlen * 8) % 13, extra_null = 1;
if(size == 0) return 2 * ((inlen * 8) / 13) + extra_null;
if(size < 7) return 2 * ((inlen * 8) / 13) + extra_null + 1;
return 2 * ((inlen * 8) / 13) + extra_null + 2;
}
unsigned base92_encode(const void* in, unsigned inlen, void *out, unsigned size) {
char *res = (char *)out;
const unsigned char *str = (const unsigned char *)in;
unsigned int j = 0; // j for encoded
unsigned long workspace = 0; // bits holding bin
unsigned short wssize = 0; // number of good bits in workspace
unsigned char c;
const unsigned char ENCODE_MAPPING[256] = {
33, 35, 36, 37, 38, 39, 40, 41, 42, 43,
44, 45, 46, 47, 48, 49, 50, 51, 52, 53,
54, 55, 56, 57, 58, 59, 60, 61, 62, 63,
64, 65, 66, 67, 68, 69, 70, 71, 72, 73,
74, 75, 76, 77, 78, 79, 80, 81, 82, 83,
84, 85, 86, 87, 88, 89, 90, 91, 92, 93,
94, 95, 97, 98, 99, 100, 101, 102, 103, 104,
105, 106, 107, 108, 109, 110, 111, 112, 113, 114,
115, 116, 117, 118, 119, 120, 121, 122, 123, 124,
125, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0
};
if (inlen) {
for (unsigned i = 0; i < inlen; i++) {
workspace = workspace << 8 | str[i];
wssize += 8;
if (wssize >= 13) {
int tmp = (workspace >> (wssize - 13)) & 8191;
c = ENCODE_MAPPING[(tmp / 91)];
if (c == 0) return 0; // illegal char
res[j++] = c;
c = ENCODE_MAPPING[(tmp % 91)];
if (c == 0) return 0; // illegal char
res[j++] = c;
wssize -= 13;
}
}
// encode a last byte
if (0 < wssize && wssize < 7) {
int tmp = (workspace << (6 - wssize)) & 63; // pad the right side
c = ENCODE_MAPPING[(tmp)];
if (c == 0) return 0; // illegal char
res[j++] = c;
} else if (7 <= wssize) {
int tmp = (workspace << (13 - wssize)) & 8191; // pad the right side
c = ENCODE_MAPPING[(tmp / 91)];
if (c == 0) return 0; // illegal char
res[j++] = c;
c = ENCODE_MAPPING[(tmp % 91)];
if (c == 0) return 0; // illegal char
res[j++] = c;
}
} else {
res[j++] = '~';
}
// add null byte
res[j] = 0;
return j;
}
// this guy expects a null-terminated string
// gives back a non-null terminated string, and properly filled len
unsigned base92_decode(const void* in, unsigned size, void *out, unsigned outlen_unused) {
const char* str = (const char*)in;
unsigned char *res = (unsigned char *)out;
int i, j = 0, b1, b2;
unsigned long workspace = 0;
unsigned short wssize = 0;
const unsigned char DECODE_MAPPING[256] = {
255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 0, 255, 1, 2, 3, 4, 5,
6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
16, 17, 18, 19, 20, 21, 22, 23, 24, 25,
26, 27, 28, 29, 30, 31, 32, 33, 34, 35,
36, 37, 38, 39, 40, 41, 42, 43, 44, 45,
46, 47, 48, 49, 50, 51, 52, 53, 54, 55,
56, 57, 58, 59, 60, 61, 255, 62, 63, 64,
65, 66, 67, 68, 69, 70, 71, 72, 73, 74,
75, 76, 77, 78, 79, 80, 81, 82, 83, 84,
85, 86, 87, 88, 89, 90, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255
};
// handle small cases first
if (size == 0 || (str[0] == '~' && str[1] == '\0')) {
res[0] = 0;
return 1;
}
// calculate size
int len = ((size/2 * 13) + (size%2 * 6)) / 8;
// handle pairs of chars
for (i = 0; i + 1 < size; i += 2) {
b1 = DECODE_MAPPING[(str[i])];
b2 = DECODE_MAPPING[(str[i+1])];
workspace = (workspace << 13) | (b1 * 91 + b2);
wssize += 13;
while (wssize >= 8) {
res[j++] = (workspace >> (wssize - 8)) & 255;
wssize -= 8;
}
}
// handle single char
if (size % 2 == 1) {
workspace = (workspace << 6) | DECODE_MAPPING[(str[size - 1])];
wssize += 6;
while (wssize >= 8) {
res[j++] = (workspace >> (wssize - 8)) & 255;
wssize -= 8;
}
}
//assert(j == len);
return j;
}
// ----------------------------------------------------------------------------
// COBS en/decoder
// Based on code by Jacques Fortier.
// "Redistribution and use in source and binary forms are permitted, with or without modification."
//
// Consistent Overhead Byte Stuffing is an encoding that removes all 0 bytes from arbitrary binary data.
// The encoded data consists only of bytes with values from 0x01 to 0xFF. This is useful for preparing data for
// transmission over a serial link (RS-232 or RS-485 for example), as the 0 byte can be used to unambiguously indicate
// packet boundaries. COBS also has the advantage of adding very little overhead (at least 1 byte, plus up to an
// additional byte per 254 bytes of data). For messages smaller than 254 bytes, the overhead is constant.
//
// This implementation is designed to be both efficient and robust.
// The decoder is designed to detect malformed input data and report an error upon detection.
//
unsigned cobs_bounds( unsigned len ) {
return len + ceil(len / 254.0) + 1;
}
unsigned cobs_encode(const void *in, unsigned inlen, void *out, unsigned outlen) {
const uint8_t *src = (const uint8_t *)in;
uint8_t *dst = (uint8_t*)out;
size_t srclen = inlen;
uint8_t code = 1;
size_t read_index = 0, write_index = 1, code_index = 0;
while( read_index < srclen ) {
if( src[ read_index ] == 0) {
dst[ code_index ] = code;
code = 1;
code_index = write_index++;
read_index++;
} else {
dst[ write_index++ ] = src[ read_index++ ];
code++;
if( code == 0xFF ) {
dst[ code_index ] = code;
code = 1;
code_index = write_index++;
}
}
}
dst[ code_index ] = code;
return write_index;
}
unsigned cobs_decode(const void *in, unsigned inlen, void *out, unsigned outlen) {
const uint8_t *src = (const uint8_t *)in;
uint8_t *dst = (uint8_t*)out;
size_t srclen = inlen;
uint8_t code, i;
size_t read_index = 0, write_index = 0;
while( read_index < srclen ) {
code = src[ read_index ];
if( ((read_index + code) > srclen) && (code != 1) ) {
return 0;
}
read_index++;
for( i = 1; i < code; i++ ) {
dst[ write_index++ ] = src[ read_index++ ];
}
if( (code != 0xFF) && (read_index != srclen) ) {
dst[ write_index++ ] = '\0';
}
}
return write_index;
}
#if 0
static
void cobs_test( const char *buffer, int buflen ) {
char enc[4096];
int enclen = cobs_encode( buffer, buflen, enc, 4096 );
char dec[4096];
int declen = cobs_decode( enc, enclen, dec, 4096 );
test( enclen >= buflen );
test( declen == buflen );
test( memcmp(dec, buffer, buflen) == 0 );
printf("%d->%d->%d (+%d extra bytes)\n", declen, enclen, declen, enclen - declen);
}
AUTORUN {
const char *null = 0;
cobs_test( null, 0 );
const char empty[] = "";
cobs_test( empty, sizeof(empty) );
const char text[] = "hello world\n";
cobs_test( text, sizeof(text) );
const char bintext[] = "hello\0\0\0world\n";
cobs_test( bintext, sizeof(bintext) );
const char blank[512] = {0};
cobs_test( blank, sizeof(blank) );
char longbintext[1024];
for( int i = 0; i < 1024; ++i ) longbintext[i] = (unsigned char)i;
cobs_test( longbintext, sizeof(longbintext) );
assert(~puts("Ok"));
}
#endif
// ----------------------------------------------------------------------------
// netstring en/decoder
// - rlyeh, public domain.
unsigned netstring_bounds(unsigned inlen) {
return 5 + inlen + 3; // 3 for ;,\0 + 5 if inlen < 100k ; else (unsigned)ceil(log10(inlen + 1))
}
unsigned netstring_encode(const char *in, unsigned inlen, char *out, unsigned outlen) {
// if(outlen < netstring_bounds(inlen)) return 0;
sprintf(out, "%u:%.*s,", inlen, inlen, in);
return strlen(out);
}
unsigned netstring_decode(const char *in, unsigned inlen, char *out, unsigned outlen) {
// if(outlen < inlen) return 0;
const char *bak = in;
sscanf(in, "%u", &outlen);
while( *++in != ':' );
memcpy(out, in+1, outlen), out[outlen-1] = 0;
// return outlen; // number of written bytes
return (outlen + (in+2 - bak)); // number of read bytes
}
#if 0
AUTORUN {
// encode
const char text1[] = "hello world!", text2[] = "abc123";
unsigned buflen = netstring_bounds(strlen(text1) + strlen(text2));
char *buf = malloc(buflen), *ptr = buf;
ptr += netstring_encode(text1, strlen(text1), ptr, buflen -= (ptr - buf));
ptr += netstring_encode(text2, strlen(text2), ptr, buflen -= (ptr - buf));
printf("%s -> ", buf);
// decode
char out[12];
unsigned plen = strlen(ptr = buf);
while(plen > 0) {
int written = netstring_decode(ptr, plen, out, 12);
ptr += written;
plen -= written;
printf("'%s'(%s)(%d), ", out, ptr, plen );
}
puts("");
}
#endif
// ----------------------------------------------------------------------------
// array de/interleaving
// - rlyeh, public domain.
//
// results:
// R0G0B0 R1G1B1 R2G2B2... -> R0R1R2... B0B1B2... G0G1G2...
// R0G0B0A0 R1G1B1A1 R2G2B2A2... -> R0R1R2... A0A1A2... B0B1B2... G0G1G2...
void *interleave( void *out, const void *list, int list_count, int sizeof_item, unsigned columns ) {
void *bak = out;
assert( columns < list_count ); // required
int row_count = list_count / columns;
for( int offset = 0; offset < columns; offset++ ) {
for( int row = 0; row < row_count; row++ ) {
memcpy( out, &((char*)list)[ (offset + row * columns) * sizeof_item ], sizeof_item );
out = ((char*)out) + sizeof_item;
}
}
return bak;
}
#if 0
static
void interleave_test( const char *name, int interleaving, int deinterleaving, const char *original ) {
char interleaved[128] = {0};
interleave( interleaved, original, strlen(original)/2, 2, interleaving );
char deinterleaved[128] = {0};
interleave( deinterleaved, interleaved, strlen(original)/2, 2, deinterleaving );
printf( "\n%s\n", name );
printf( "original:\t%s\n", original );
printf( "interleaved:\t%s\n", interleaved );
printf( "deinterleaved:\t%s\n", deinterleaved );
assert( 0 == strcmp(original, deinterleaved) );
}
AUTORUN {
interleave_test(
"audio 2ch", 2, 3,
"L0R0"
"L1R1"
"L2R2"
);
interleave_test(
"image 3ch", 3, 3,
"R0G0B0"
"R1G1B1"
"R2G2B2"
);
interleave_test(
"image 4ch", 4, 3,
"R0G0B0A0"
"R1G1B1A1"
"R2G2B2A2"
);
interleave_test(
"audio 5ch", 5, 3,
"A0B0C0L0R0"
"A1B1C1L1R1"
"A2B2C2L2R2"
);
interleave_test(
"audio 5.1ch", 6, 3,
"A0B0C0L0R0S0"
"A1B1C1L1R1S1"
"A2B2C2L2R2S2"
);
interleave_test(
"opengl material 9ch", 9, 3,
"X0Y0Z0q0w0e0r0u0v0"
"X1Y1Z1q1w1e1r1u1v1"
"X2Y2Z2q2w2e2r2u2v2"
);
interleave_test(
"opengl material 10ch", 10, 3,
"X0Y0Z0q0w0e0r0s0u0v0"
"X1Y1Z1q1w1e1r1s1u1v1"
"X2Y2Z2q2w2e2r2s2u2v2"
);
assert(~puts("Ok"));
}
#endif
// ----------------------------------------------------------------------------
// delta encoder
#define delta_expand_template(N) \
void delta##N##_encode(void *buffer_, unsigned count) { \
uint##N##_t current, last = 0, *buffer = (uint##N##_t*)buffer_; \
for( unsigned i = 0; i < count; i++ ) { \
current = buffer[i]; \
buffer[i] = current - last; \
last = current; \
} \
} \
void delta##N##_decode(void *buffer_, unsigned count) { \
uint##N##_t delta, last = 0, *buffer = (uint##N##_t*)buffer_; \
for( unsigned i = 0; i < count; i++ ) { \
delta = buffer[i]; \
buffer[i] = delta + last; \
last = buffer[i]; \
} \
}
delta_expand_template(8);
delta_expand_template(16);
delta_expand_template(32);
delta_expand_template(64);
#if 0
AUTORUN {
char buf[] = "1231112223345555";
int buflen = strlen(buf);
char *dt = strdup(buf);
printf(" delta8: ", dt);
for( int i = 0; i < buflen; ++i ) printf("%c", dt[i] );
printf("->");
delta8_encode(dt, buflen);
for( int i = 0; i < buflen; ++i ) printf("%02d,", dt[i] );
printf("->");
delta8_decode(dt, buflen);
for( int i = 0; i < buflen; ++i ) printf("%c", dt[i] );
printf("\r%c\n", 0 == strcmp(buf,dt) ? 'Y':'N');
}
#endif
// ----------------------------------------------------------------------------
// zigzag en/decoder
// - rlyeh, public domain
uint64_t zig64( int64_t value ) { // convert sign|magnitude to magnitude|sign
return (value >> 63) ^ (value << 1);
}
int64_t zag64( uint64_t value ) { // convert magnitude|sign to sign|magnitude
return (value >> 1) ^ -(value & 1);
}
// branchless zigzag encoding 32/64
// sign|magnitude to magnitude|sign and back
// [ref] https://developers.google.com/protocol-buffers/docs/encoding
uint32_t enczig32u( int32_t n) { return ((n << 1) ^ (n >> 31)); }
uint64_t enczig64u( int64_t n) { return ((n << 1) ^ (n >> 63)); }
int32_t deczig32i(uint32_t n) { return ((n >> 1) ^ -(n & 1)); }
int64_t deczig64i(uint64_t n) { return ((n >> 1) ^ -(n & 1)); }
#if 0
AUTORUN {
int16_t x = -1000;
printf("%d -> %llu %llx -> %lld\n", x, zig64(x), zig64(x), zag64(zig64(x)));
}
AUTORUN {
#define CMP32(signedN) do { \
int32_t reconverted = deczig32i( enczig32u(signedN) ); \
int equal = signedN == reconverted; \
printf("[%s] %d vs %d\n", equal ? " OK " : "FAIL", signedN, reconverted ); \
} while(0)
#define CMP64(signedN) do { \
int64_t reconverted = deczig64i( enczig64u(signedN) ); \
int equal = signedN == reconverted; \
printf("[%s] %lld vs %lld\n", equal ? " OK " : "FAIL", signedN, reconverted ); \
} while(0)
CMP32( 0);
CMP32(-1);
CMP32(+1);
CMP32(-2);
CMP32(+2);
CMP32(INT32_MAX - 1);
CMP32(INT32_MIN + 1);
CMP32(INT32_MAX);
CMP32(INT32_MIN);
CMP64( 0ll);
CMP64(-1ll);
CMP64(+1ll);
CMP64(-2ll);
CMP64(+2ll);
CMP64(INT64_MAX - 1);
CMP64(INT64_MIN + 1);
CMP64(INT64_MAX);
CMP64(INT64_MIN);
}
void TESTU( uint64_t N ) {
uint8_t buf[9] = {0};
enczig64i(buf, (N));
uint64_t reconstructed = deczig64i(buf, 0);
if( reconstructed != (N) ) printf("[FAIL] %llu vs %02x %02x %02x %02x %02x %02x %02x %02x %02x\n", (N), buf[0], buf[1], buf[2], buf[3], buf[4], buf[5], buf[6], buf[7], buf[8] );
else if( 0xffffff == ((N) & 0xffffff) ) printf("[ OK ] %llx\n", (N));
}
void TESTI( int64_t N ) {
TESTU( enczig64u(N) );
}
AUTORUN {
TESTU(0LLU);
TESTU(1LLU);
TESTU(2LLU);
TESTU(UINT64_MAX/8);
TESTU(UINT64_MAX/4);
TESTU(UINT64_MAX/2);
TESTU(UINT64_MAX-2);
TESTU(UINT64_MAX-1);
TESTU(UINT64_MAX);
#pragma omp parallel for // compile with /openmp
for( int64_t N = INT64_MIN; N < INT64_MAX; ++N ) {
TESTU(N);
TESTI((int64_t)N);
}
}
#endif
// ----------------------------------------------------------------------------
// ARC4 en/decryptor. Based on code by Mike Shaffer.
// - rlyeh, public domain.
void *arc4( void *buf_, unsigned buflen, const void *pass_, unsigned passlen ) {
// [ref] http://www.4guysfromrolla.com/webtech/code/rc4.inc.html
assert(passlen);
int sbox[256], key[256];
char *buf = (char*)buf_;
const char *pass = (const char*)pass_;
for( unsigned a = 0; a < 256; a++ ) {
key[a] = pass[a % passlen];
sbox[a] = a;
}
for( unsigned a = 0, b = 0; a < 256; a++ ) {
b = (b + sbox[a] + key[a]) % 256;
int swap = sbox[a]; sbox[a] = sbox[b]; sbox[b] = swap;
}
for( unsigned a = 0, b = 0, i = 0; i < buflen; ++i ) {
a = (a + 1) % 256;
b = (b + sbox[a]) % 256;
int swap = sbox[a]; sbox[a] = sbox[b]; sbox[b] = swap;
buf[i] ^= sbox[(sbox[a] + sbox[b]) % 256];
}
return buf_;
}
#if 0
AUTORUN {
char buffer[] = "Hello world."; int buflen = strlen(buffer);
char *password = "abc123"; int passlen = strlen(password);
printf("Original: %s\n", buffer);
printf("Password: %s\n", password);
char *encrypted = arc4( buffer, buflen, password, passlen );
printf("ARC4 Encrypted text: '%s'\n", encrypted);
char *decrypted = arc4( buffer, buflen, password, passlen );
printf("ARC4 Decrypted text: '%s'\n", decrypted);
}
#endif
// ----------------------------------------------------------------------------
// crc64
// - rlyeh, public domain
uint64_t crc64(uint64_t h, const void *ptr, uint64_t len) {
// based on public domain code by Lasse Collin
// also, use poly64 0xC96C5795D7870F42 for crc64-ecma
static uint64_t crc64_table[256];
static uint64_t poly64 = UINT64_C(0x95AC9329AC4BC9B5);
if( poly64 ) {
for( int b = 0; b < 256; ++b ) {
uint64_t r = b;
for( int i = 0; i < 8; ++i ) {
r = r & 1 ? (r >> 1) ^ poly64 : r >> 1;
}
crc64_table[ b ] = r;
//printf("%016llx\n", crc64_table[b]);
}
poly64 = 0;
}
const uint8_t *buf = (const uint8_t *)ptr;
uint64_t crc = ~h; // ~crc;
while( len != 0 ) {
crc = crc64_table[(uint8_t)crc ^ *buf++] ^ (crc >> 8);
--len;
}
return ~crc;
}
#if 0
unsigned crc32(unsigned h, const void *ptr_, unsigned len) {
// based on public domain code by Karl Malbrain
const uint8_t *ptr = (const uint8_t *)ptr_;
if (!ptr) return 0;
const unsigned tbl[16] = {
0x00000000, 0x1db71064, 0x3b6e20c8, 0x26d930ac, 0x76dc4190, 0x6b6b51f4, 0x4db26158, 0x5005713c,
0xedb88320, 0xf00f9344, 0xd6d6a3e8, 0xcb61b38c, 0x9b64c2b0, 0x86d3d2d4, 0xa00ae278, 0xbdbdf21c };
for(h = ~h; len--; ) { uint8_t b = *ptr++; h = (h >> 4) ^ tbl[(h & 15) ^ (b & 15)]; h = (h >> 4) ^ tbl[(h & 15) ^ (b >> 4)]; }
return ~h;
}
#endif
// ----------------------------------------------------------------------------
// entropy encoder
#if is(win32)
#include <winsock2.h>
#include <wincrypt.h>
#pragma comment(lib, "advapi32")
void entropy( void *buf, unsigned n ) {
HCRYPTPROV provider;
if( CryptAcquireContext( &provider, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT ) == 0 ) {
assert(!"CryptAcquireContext failed");
}
int rc = CryptGenRandom( provider, n, (BYTE *)buf );
assert( rc != 0 );
CryptReleaseContext( provider, 0 );
}
#elif is(linux) || is(osx)
void entropy( void *buf, unsigned n ) {
FILE *fp = fopen( "/dev/urandom", "r" );
if( !fp ) assert(!"/dev/urandom open failed");
size_t read = fread( buf, 1, n, fp );
assert( read == n && "/dev/urandom read failed" );
fclose( fp );
}
#else // unused for now. likely emscripten will hit this
// pseudo random number generator with 128 bit internal state... probably not suited for cryptographical usage.
// [src] http://github.com/kokke (UNLICENSE)
// [ref] http://burtleburtle.net/bob/rand/smallprng.html
#include <time.h>
#if is(win32)
#include <process.h>
#else
#include <unistd.h>
#endif
static uint32_t prng_next(void) {
#define prng_rotate(x,k) (x << k) | (x >> (32 - k))
#define prng_shuffle() do { \
uint32_t e = ctx[0] - prng_rotate(ctx[1], 27); \
ctx[0] = ctx[1] ^ prng_rotate(ctx[2], 17); \
ctx[1] = ctx[2] + ctx[3]; \
ctx[2] = ctx[3] + e; \
ctx[3] = e + ctx[0]; } while(0)
static __thread uint32_t ctx[4], *once = 0; if( !once ) {
uint32_t seed = (uint32_t)( ifdef(win32,_getpid,getpid)() + time(0) + ((uintptr_t)once) );
ctx[0] = 0xf1ea5eed;
ctx[1] = ctx[2] = ctx[3] = seed;
for (int i = 0; i < 31; ++i) {
prng_shuffle();
}
once = ctx;
}
prng_shuffle();
return ctx[3];
}
void entropy( void *buf, unsigned n ) {
for( ; n >= 4 ; n -= 4 ) {
uint32_t a = prng_next();
memcpy(buf, &a, 4);
buf = ((char*)buf) + 4;
}
if( n > 0 ) {
uint32_t a = prng_next();
memcpy(buf, &a, n);
}
}
#endif
#if 0
AUTORUN {
unsigned char buf[128];
entropy(buf, 128);
for( int i = 0; i < 128; ++i ) {
printf("%02x", buf[i]);
}
puts("");
}
#endif

View File

@ -1,10 +1,100 @@
// ----------------------------------------------------------------------------
// compression api
enum COMPRESS_FLAGS {
COMPRESS_RAW = 0,
COMPRESS_PPP = (1<<4),
COMPRESS_ULZ = (2<<4),
COMPRESS_LZ4 = (3<<4),
COMPRESS_CRUSH = (4<<4),
COMPRESS_DEFLATE = (5<<4),
COMPRESS_LZP1 = (6<<4),
COMPRESS_LZMA = (7<<4),
COMPRESS_BALZ = (8<<4),
COMPRESS_LZW3 = (9<<4),
COMPRESS_LZSS = (10<<4),
COMPRESS_BCM = (11<<4),
COMPRESS_ZLIB = (12<<4), // same as deflate with header
};
API unsigned zbounds(unsigned inlen, unsigned flags);
API unsigned zencode(void *out, unsigned outlen, const void *in, unsigned inlen, unsigned flags);
API unsigned zexcess(unsigned flags);
API unsigned zdecode(void *out, unsigned outlen, const void *in, unsigned inlen, unsigned flags);
// ----------------------------------------------------------------------------
// array de/interleaving
//
// results:
// R0G0B0 R1G1B1 R2G2B2... -> R0R1R2... B0B1B2... G0G1G2...
// R0G0B0A0 R1G1B1A1 R2G2B2A2... -> R0R1R2... A0A1A2... B0B1B2... G0G1G2...
API void *interleave( void *out, const void *list, int list_count, int sizeof_item, unsigned columns );
// ----------------------------------------------------------------------------
// cobs en/decoder
API unsigned cobs_bounds(unsigned len);
API unsigned cobs_encode(const void *in, unsigned inlen, void *out, unsigned outlen);
API unsigned cobs_decode(const void *in, unsigned inlen, void *out, unsigned outlen);
// ----------------------------------------------------------------------------
// base92 en/decoder
API unsigned base92_encode(const void *in, unsigned inlen, void* out, unsigned outlen);
API unsigned base92_decode(const void *in, unsigned inlen, void* out, unsigned outlen);
API unsigned base92_bounds(unsigned inlen);
// ----------------------------------------------------------------------------
// netstring en/decoder
API unsigned netstring_bounds(unsigned inlen);
API unsigned netstring_encode(const char *in, unsigned inlen, char *out, unsigned outlen);
API unsigned netstring_decode(const char *in, unsigned inlen, char *out, unsigned outlen);
// ----------------------------------------------------------------------------
// delta en/decoder
API void delta8_encode(void *buffer, unsigned count);
API void delta8_decode(void *buffer, unsigned count);
API void delta16_encode(void *buffer, unsigned count);
API void delta16_decode(void *buffer, unsigned count);
API void delta32_encode(void *buffer, unsigned count);
API void delta32_decode(void *buffer, unsigned count);
API void delta64_encode(void *buffer, unsigned count);
API void delta64_decode(void *buffer, unsigned count);
// ----------------------------------------------------------------------------
// zigzag en/decoder
API uint64_t zig64( int64_t value ); // convert sign|magnitude to magnitude|sign
API int64_t zag64( uint64_t value ); // convert magnitude|sign to sign|magnitude
API uint32_t enczig32u( int32_t n);
API uint64_t enczig64u( int64_t n);
API int32_t deczig32i(uint32_t n);
API int64_t deczig64i(uint64_t n);
// ----------------------------------------------------------------------------
// arc4 en/decryptor
API void *arc4( void *buffer, unsigned buflen, const void *pass, unsigned passlen );
// ----------------------------------------------------------------------------
// crc64
API uint64_t crc64(uint64_t h, const void *ptr, uint64_t len);
// ----------------------------------------------------------------------------
// entropy encoder
API void entropy( void *buf, unsigned n );
// -----------------------------------------------------------------------------
// semantic versioning in a single byte (octal)
// - rlyeh, public domain.
//
// - single octal byte that represents semantic versioning (major.minor.patch).
// - allowed range [0000..0377] ( <-> [0..255] decimal )
// - comparison checks only major.minor tuple as per convention.
API int semver( int major, int minor, int patch );
API int semvercmp( int v1, int v2 );

View File

@ -15,8 +15,7 @@ AUTORUN {
reflect_init();
}
static
const char* symbol(const char *s) {
const char* symbol_naked(const char *s) {
if( strbeg(s, "const ") ) s += 6;
if( strbeg(s, "union ") ) s += 6;
if( strbeg(s, "struct ") ) s += 7;
@ -28,39 +27,40 @@ const char* symbol(const char *s) {
void type_inscribe(const char *TY,unsigned TYsz,const char *infos) {
reflect_init();
unsigned TYid = intern(TY = symbol(TY));
unsigned TYid = intern(TY = symbol_naked(TY));
map_find_or_add(reflects, TYid, ((reflect_t){TYid, 0, TYsz, STRDUP(TY), infos})); // @leak
}
void enum_inscribe(const char *E,unsigned Eval,const char *infos) {
reflect_init();
unsigned Eid = intern(E = symbol(E));
unsigned Eid = intern(E = symbol_naked(E));
map_find_or_add(reflects, Eid, ((reflect_t){Eid,0, Eval, STRDUP(E),infos})); // @leak
}
unsigned enum_find(const char *E) {
reflect_init();
E = symbol(E);
E = symbol_naked(E);
return map_find(reflects, intern(E))->sz;
}
void function_inscribe(const char *F,void *func,const char *infos) {
reflect_init();
unsigned Fid = intern(F = symbol(F));
unsigned Fid = intern(F = symbol_naked(F));
map_find_or_add(reflects, Fid, ((reflect_t){Fid,0, 0, STRDUP(F),infos, func})); // @leak
reflect_t *found = map_find(reflects,Fid);
}
void *function_find(const char *F) {
reflect_init();
F = symbol(F);
F = symbol_naked(F);
return map_find(reflects, intern(F))->addr;
}
void struct_inscribe(const char *T,unsigned Tsz,unsigned OBJTYPEid, const char *infos) {
reflect_init();
unsigned Tid = intern(T = symbol(T));
unsigned Tid = intern(T = symbol_naked(T));
map_find_or_add(reflects, Tid, ((reflect_t){Tid, OBJTYPEid, Tsz, STRDUP(T), infos})); // @leak
}
void member_inscribe(const char *T, const char *M,unsigned Msz, const char *infos, const char *TYPE, unsigned bytes) {
reflect_init();
unsigned Tid = intern(T = symbol(T));
unsigned Mid = intern(M = symbol(M));
unsigned Tid = intern(T = symbol_naked(T));
unsigned Mid = intern(M = symbol_naked(M));
unsigned Xid = intern(TYPE = symbol_naked(TYPE));
map_find_or_add(reflects, (Mid<<16)|Tid, ((reflect_t){Mid, 0, Msz, STRDUP(M), infos, NULL, Tid, STRDUP(TYPE) })); // @leak
// add member separately as well
if(!members) map_init_int(members);
@ -83,19 +83,19 @@ void member_inscribe(const char *T, const char *M,unsigned Msz, const char *info
}
reflect_t member_find(const char *T, const char *M) {
reflect_init();
T = symbol(T);
M = symbol(M);
T = symbol_naked(T);
M = symbol_naked(M);
return *map_find(reflects, (intern(M)<<16)|intern(T));
}
void *member_findptr(void *obj, const char *T, const char *M) {
reflect_init();
T = symbol(T);
M = symbol(M);
T = symbol_naked(T);
M = symbol_naked(M);
return (char*)obj + member_find(T,M).sz;
}
array(reflect_t)* members_find(const char *T) {
reflect_init();
T = symbol(T);
T = symbol_naked(T);
return map_find(members, intern(T));
}

View File

@ -59,5 +59,6 @@ API void enum_inscribe(const char *E,unsigned Eval,const char *inf
API void struct_inscribe(const char *T,unsigned Tsz,unsigned OBJTYPEid, const char *infos);
API void member_inscribe(const char *T, const char *M,unsigned Msz, const char *infos, const char *type, unsigned bytes);
API void function_inscribe(const char *F,void *func,const char *infos);
API const char* symbol_naked(const char *s);
API int ui_reflect(const char *mask); // *, model* or NULL

View File

@ -1,13 +0,0 @@
// -----------------------------------------------------------------------------
// script framework
// - rlyeh, public domain
API void script_init();
API void script_run(const char *script);
API void script_runfile(const char *pathfile);
API void script_bind_class(const char *objname, int num_methods, const char **c_names, void **c_functions);
API void script_bind_function(const char *c_name, void *c_function);
API void script_call(const char *lua_function);
API bool script_tests();

View File

@ -246,3 +246,241 @@ guid guid_create() {
return c.v3;
}
// ----------------------------------------------------------------------------
// ease
float ease_nop(float t) { return 0; }
float ease_linear(float t) { return t; }
float ease_out_sine(float t) { return sinf(t*(C_PI*0.5f)); }
float ease_out_quad(float t) { return -(t*(t-2)); }
float ease_out_cubic(float t) { float f=t-1; return f*f*f+1; }
float ease_out_quart(float t) { float f=t-1; return f*f*f*(1-t)+1; }
float ease_out_quint(float t) { float f=(t-1); return f*f*f*f*f+1; }
float ease_out_expo(float t) { return (t >= 1) ? t : 1-powf(2,-10*t); }
float ease_out_circ(float t) { return sqrtf((2-t)*t); }
float ease_out_back(float t) { float f=1-t; return 1-(f*f*f-f*sinf(f*C_PI)); }
float ease_out_elastic(float t) { return sinf(-13*(C_PI*0.5f)*(t+1))*powf(2,-10*t)+1; }
float ease_out_bounce(float t) { return (t < 4.f/11) ? (121.f*t*t)/16 : (t < 8.f/11) ? (363.f/40*t*t)-(99.f/10*t)+17.f/5 : (t < 9.f/10) ? (4356.f/361*t*t)-(35442.f/1805*t)+16061.f/1805 : (54.f/5*t*t)-(513.f/25*t)+268.f/25; }
float ease_in_sine(float t) { return 1+sinf((t-1)*(C_PI*0.5f)); }
float ease_in_quad(float t) { return t*t; }
float ease_in_cubic(float t) { return t*t*t; }
float ease_in_quart(float t) { return t*t*t*t; }
float ease_in_quint(float t) { return t*t*t*t*t; }
float ease_in_expo(float t) { return (t <= 0) ? t : powf(2,10*(t-1)); }
float ease_in_circ(float t) { return 1-sqrtf(1-(t*t)); }
float ease_in_back(float t) { return t*t*t-t*sinf(t*C_PI); }
float ease_in_elastic(float t) { return sinf(13*(C_PI*0.5f)*t)*powf(2,10*(t-1)); }
float ease_in_bounce(float t) { return 1-ease_out_bounce(1-t); }
float ease_inout_sine(float t) { return 0.5f*(1-cosf(t*C_PI)); }
float ease_inout_quad(float t) { return (t < 0.5f) ? 2*t*t : (-2*t*t)+(4*t)-1; }
float ease_inout_cubic(float t) { float f; return (t < 0.5f) ? 4*t*t*t : (f=(2*t)-2,0.5f*f*f*f+1); }
float ease_inout_quart(float t) { float f; return (t < 0.5f) ? 8*t*t*t*t : (f=(t-1),-8*f*f*f*f+1); }
float ease_inout_quint(float t) { float f; return (t < 0.5f) ? 16*t*t*t*t*t : (f=((2*t)-2),0.5f*f*f*f*f*f+1); }
float ease_inout_expo(float t) { return (t <= 0 || t >= 1) ? t : t < 0.5f ? 0.5f*powf(2,(20*t)-10) : -0.5f*powf(2,(-20*t)+10)+1; }
float ease_inout_circ(float t) { return t < 0.5f ? 0.5f*(1-sqrtf(1-4*(t*t))) : 0.5f*(sqrtf(-((2*t)-3)*((2*t)-1))+1); }
float ease_inout_back(float t) { float f; return t < 0.5f ? (f=2*t,0.5f*(f*f*f-f*sinf(f*C_PI))) : (f=(1-(2*t-1)),0.5f*(1-(f*f*f-f*sinf(f*C_PI)))+0.5f); }
float ease_inout_elastic(float t) { return t < 0.5f ? 0.5f*sinf(13*(C_PI*0.5f)*(2*t))*powf(2,10*((2*t)-1)) : 0.5f*(sinf(-13*(C_PI*0.5f)*((2*t-1)+1))*powf(2,-10*(2*t-1))+2); }
float ease_inout_bounce(float t) { return t < 0.5f ? 0.5f*ease_in_bounce(t*2) : 0.5f*ease_out_bounce(t*2-1)+0.5f; }
float ease_inout_perlin(float t) { float t3=t*t*t,t4=t3*t,t5=t4*t; return 6*t5-15*t4+10*t3; }
float ease(float t01, unsigned mode) {
typedef float (*easing)(float);
easing modes[] = {
ease_out_sine,
ease_out_quad,
ease_out_cubic,
ease_out_quart,
ease_out_quint,
ease_out_expo,
ease_out_circ,
ease_out_back,
ease_out_elastic,
ease_out_bounce,
ease_in_sine,
ease_in_quad,
ease_in_cubic,
ease_in_quart,
ease_in_quint,
ease_in_expo,
ease_in_circ,
ease_in_back,
ease_in_elastic,
ease_in_bounce,
ease_inout_sine,
ease_inout_quad,
ease_inout_cubic,
ease_inout_quart,
ease_inout_quint,
ease_inout_expo,
ease_inout_circ,
ease_inout_back,
ease_inout_elastic,
ease_inout_bounce,
ease_nop,
ease_linear,
ease_inout_perlin,
};
return modes[clampi(mode, 0, countof(modes))](clampf(t01,0,1));
}
float ease_pong(float t, unsigned fn) { return 1 - ease(t, fn); }
float ease_ping_pong(float t, unsigned fn1, unsigned fn2) { return t < 0.5 ? ease(t*2,fn1) : ease(1-(t-0.5)*2,fn2); }
float ease_pong_ping(float t, unsigned fn1, unsigned fn2) { return 1 - ease_ping_pong(t,fn1,fn2); }
const char **ease_enums() {
static const char *list[] = {
"ease_out_sine",
"ease_out_quad",
"ease_out_cubic",
"ease_out_quart",
"ease_out_quint",
"ease_out_expo",
"ease_out_circ",
"ease_out_back",
"ease_out_elastic",
"ease_out_bounce",
"ease_in_sine",
"ease_in_quad",
"ease_in_cubic",
"ease_in_quart",
"ease_in_quint",
"ease_in_expo",
"ease_in_circ",
"ease_in_back",
"ease_in_elastic",
"ease_in_bounce",
"ease_inout_sine",
"ease_inout_quad",
"ease_inout_cubic",
"ease_inout_quart",
"ease_inout_quint",
"ease_inout_expo",
"ease_inout_circ",
"ease_inout_back",
"ease_inout_elastic",
"ease_inout_bounce",
"ease_nop",
"ease_linear",
"ease_inout_perlin",
0
};
return list;
}
const char *ease_enum(unsigned mode) {
return mode[ ease_enums() ];
}
/*AUTORUN {
ENUM(EASE_LINEAR|EASE_OUT);
ENUM(EASE_SINE|EASE_OUT);
ENUM(EASE_QUAD|EASE_OUT);
ENUM(EASE_CUBIC|EASE_OUT);
ENUM(EASE_QUART|EASE_OUT);
ENUM(EASE_QUINT|EASE_OUT);
ENUM(EASE_EXPO|EASE_OUT);
ENUM(EASE_CIRC|EASE_OUT);
ENUM(EASE_BACK|EASE_OUT);
ENUM(EASE_ELASTIC|EASE_OUT);
ENUM(EASE_BOUNCE|EASE_OUT);
ENUM(EASE_SINE|EASE_IN);
ENUM(EASE_QUAD|EASE_IN);
ENUM(EASE_CUBIC|EASE_IN);
ENUM(EASE_QUART|EASE_IN);
ENUM(EASE_QUINT|EASE_IN);
ENUM(EASE_EXPO|EASE_IN);
ENUM(EASE_CIRC|EASE_IN);
ENUM(EASE_BACK|EASE_IN);
ENUM(EASE_ELASTIC|EASE_IN);
ENUM(EASE_BOUNCE|EASE_IN);
ENUM(EASE_SINE|EASE_INOUT);
ENUM(EASE_QUAD|EASE_INOUT);
ENUM(EASE_CUBIC|EASE_INOUT);
ENUM(EASE_QUART|EASE_INOUT);
ENUM(EASE_QUINT|EASE_INOUT);
ENUM(EASE_EXPO|EASE_INOUT);
ENUM(EASE_CIRC|EASE_INOUT);
ENUM(EASE_BACK|EASE_INOUT);
ENUM(EASE_ELASTIC|EASE_INOUT);
ENUM(EASE_BOUNCE|EASE_INOUT);
ENUM(EASE_NOP);
ENUM(EASE_LINEAR);
ENUM(EASE_INOUT_PERLIN);
};*/
// ----------------------------------------------------------------------------
// tween
tween_t tween() {
tween_t tw = {0};
return tw;
}
float tween_update(tween_t *tw, float dt) {
if( !array_count(tw->keyframes) ) return 0.0f;
for( int i = 0, end = array_count(tw->keyframes) - 1; i < end; ++i ) {
tween_keyframe_t *kf1 = &tw->keyframes[i];
tween_keyframe_t *kf2 = &tw->keyframes[i + 1];
if (tw->time >= kf1->t && tw->time <= kf2->t) {
float localT = (tw->time - kf1->t) / (kf2->t - kf1->t);
float easedT = ease(localT, kf1->ease);
tw->result = mix3(kf1->v, kf2->v, easedT);
break;
}
}
float done = (tw->time / tw->duration);
tw->time += dt;
return clampf(done, 0.0f, 1.0f);
}
void tween_reset(tween_t *tw) {
tw->time = 0.0f;
}
void tween_destroy(tween_t *tw) {
tween_t tw_ = {0};
array_free(tw->keyframes);
*tw = tw_;
}
static INLINE
int tween_comp_keyframes(const void *a, const void *b) {
float t1 = ((const tween_keyframe_t*)a)->t;
float t2 = ((const tween_keyframe_t*)b)->t;
return (t1 > t2) - (t1 < t2);
}
void tween_setkey(tween_t *tw, float t, vec3 v, unsigned mode) {
tween_keyframe_t keyframe = { t, v, mode };
array_push(tw->keyframes, keyframe);
array_sort(tw->keyframes, tween_comp_keyframes);
tw->duration = array_back(tw->keyframes)->t;
}
void tween_delkey(tween_t *tw, float t) { // @todo: untested
for( int i = 0, end = array_count(tw->keyframes); i < end; i++ ) {
if( tw->keyframes[i].t == t ) {
array_erase_slow(tw->keyframes, i);
tw->duration = array_back(tw->keyframes)->t;
return;
}
}
}

View File

@ -1,6 +1,5 @@
// -----------------------------------------------------------------------------
// time framework utils
// - rlyeh, public domain.
API uint64_t date(); // YYYYMMDDhhmmss
API uint64_t date_epoch(); // linux epoch
@ -42,3 +41,99 @@ AUTORUN {
hexdump(&g2, sizeof(g2));
}
*/
// ----------------------------------------------------------------------------
// ease
API float ease_nop(float t);
API float ease_linear(float t);
API float ease_out_sine(float t);
API float ease_out_quad(float t);
API float ease_out_cubic(float t);
API float ease_out_quart(float t);
API float ease_out_quint(float t);
API float ease_out_expo(float t);
API float ease_out_circ(float t);
API float ease_out_back(float t);
API float ease_out_elastic(float t);
API float ease_out_bounce(float t);
API float ease_in_sine(float t);
API float ease_in_quad(float t);
API float ease_in_cubic(float t);
API float ease_in_quart(float t);
API float ease_in_quint(float t);
API float ease_in_expo(float t);
API float ease_in_circ(float t);
API float ease_in_back(float t);
API float ease_in_elastic(float t);
API float ease_in_bounce(float t);
API float ease_inout_sine(float t);
API float ease_inout_quad(float t);
API float ease_inout_cubic(float t);
API float ease_inout_quart(float t);
API float ease_inout_quint(float t);
API float ease_inout_expo(float t);
API float ease_inout_circ(float t);
API float ease_inout_back(float t);
API float ease_inout_elastic(float t);
API float ease_inout_bounce(float t);
API float ease_inout_perlin(float t);
enum EASE_FLAGS {
EASE_SINE,
EASE_QUAD,
EASE_CUBIC,
EASE_QUART,
EASE_QUINT,
EASE_EXPO,
EASE_CIRC,
EASE_BACK,
EASE_ELASTIC,
EASE_BOUNCE,
EASE_IN,
EASE_OUT = 0,
EASE_INOUT = EASE_IN * 2,
EASE_NOP = EASE_INOUT | (EASE_BOUNCE + 1),
EASE_LINEAR,
EASE_INOUT_PERLIN,
EASE_NUM
};
API float ease(float t01, unsigned fn); // / 0-to-1
API float ease_pong(float t01, unsigned fn); // \ 1-to-0
API float ease_ping_pong(float t, unsigned fn1, unsigned fn2); // /\ 0-to-1-to-0
API float ease_pong_ping(float t, unsigned fn1, unsigned fn2); // \/ 1-to-0-to-1
API const char *ease_enum(unsigned fn);
API const char**ease_enums();
// ----------------------------------------------------------------------------
// tween
typedef struct tween_keyframe_t {
float t;
vec3 v;
unsigned ease;
} tween_keyframe_t;
typedef struct tween_t {
array(tween_keyframe_t) keyframes;
vec3 result;
float time;
float duration;
} tween_t;
API tween_t tween();
API void tween_setkey(tween_t *tw, float t, vec3 v, unsigned easing_mode);
API void tween_delkey(tween_t *tw, float t);
API float tween_update(tween_t *tw, float dt);
API void tween_reset(tween_t *tw);
API void tween_destroy(tween_t *tw);

View File

@ -1,236 +0,0 @@
// ----------------------------------------------------------------------------
// ease
float ease_nop(float t) { return 0; }
float ease_linear(float t) { return t; }
float ease_out_sine(float t) { return sinf(t*(C_PI*0.5f)); }
float ease_out_quad(float t) { return -(t*(t-2)); }
float ease_out_cubic(float t) { float f=t-1; return f*f*f+1; }
float ease_out_quart(float t) { float f=t-1; return f*f*f*(1-t)+1; }
float ease_out_quint(float t) { float f=(t-1); return f*f*f*f*f+1; }
float ease_out_expo(float t) { return (t >= 1) ? t : 1-powf(2,-10*t); }
float ease_out_circ(float t) { return sqrtf((2-t)*t); }
float ease_out_back(float t) { float f=1-t; return 1-(f*f*f-f*sinf(f*C_PI)); }
float ease_out_elastic(float t) { return sinf(-13*(C_PI*0.5f)*(t+1))*powf(2,-10*t)+1; }
float ease_out_bounce(float t) { return (t < 4.f/11) ? (121.f*t*t)/16 : (t < 8.f/11) ? (363.f/40*t*t)-(99.f/10*t)+17.f/5 : (t < 9.f/10) ? (4356.f/361*t*t)-(35442.f/1805*t)+16061.f/1805 : (54.f/5*t*t)-(513.f/25*t)+268.f/25; }
float ease_in_sine(float t) { return 1+sinf((t-1)*(C_PI*0.5f)); }
float ease_in_quad(float t) { return t*t; }
float ease_in_cubic(float t) { return t*t*t; }
float ease_in_quart(float t) { return t*t*t*t; }
float ease_in_quint(float t) { return t*t*t*t*t; }
float ease_in_expo(float t) { return (t <= 0) ? t : powf(2,10*(t-1)); }
float ease_in_circ(float t) { return 1-sqrtf(1-(t*t)); }
float ease_in_back(float t) { return t*t*t-t*sinf(t*C_PI); }
float ease_in_elastic(float t) { return sinf(13*(C_PI*0.5f)*t)*powf(2,10*(t-1)); }
float ease_in_bounce(float t) { return 1-ease_out_bounce(1-t); }
float ease_inout_sine(float t) { return 0.5f*(1-cosf(t*C_PI)); }
float ease_inout_quad(float t) { return (t < 0.5f) ? 2*t*t : (-2*t*t)+(4*t)-1; }
float ease_inout_cubic(float t) { float f; return (t < 0.5f) ? 4*t*t*t : (f=(2*t)-2,0.5f*f*f*f+1); }
float ease_inout_quart(float t) { float f; return (t < 0.5f) ? 8*t*t*t*t : (f=(t-1),-8*f*f*f*f+1); }
float ease_inout_quint(float t) { float f; return (t < 0.5f) ? 16*t*t*t*t*t : (f=((2*t)-2),0.5f*f*f*f*f*f+1); }
float ease_inout_expo(float t) { return (t <= 0 || t >= 1) ? t : t < 0.5f ? 0.5f*powf(2,(20*t)-10) : -0.5f*powf(2,(-20*t)+10)+1; }
float ease_inout_circ(float t) { return t < 0.5f ? 0.5f*(1-sqrtf(1-4*(t*t))) : 0.5f*(sqrtf(-((2*t)-3)*((2*t)-1))+1); }
float ease_inout_back(float t) { float f; return t < 0.5f ? (f=2*t,0.5f*(f*f*f-f*sinf(f*C_PI))) : (f=(1-(2*t-1)),0.5f*(1-(f*f*f-f*sinf(f*C_PI)))+0.5f); }
float ease_inout_elastic(float t) { return t < 0.5f ? 0.5f*sinf(13*(C_PI*0.5f)*(2*t))*powf(2,10*((2*t)-1)) : 0.5f*(sinf(-13*(C_PI*0.5f)*((2*t-1)+1))*powf(2,-10*(2*t-1))+2); }
float ease_inout_bounce(float t) { return t < 0.5f ? 0.5f*ease_in_bounce(t*2) : 0.5f*ease_out_bounce(t*2-1)+0.5f; }
float ease_inout_perlin(float t) { float t3=t*t*t,t4=t3*t,t5=t4*t; return 6*t5-15*t4+10*t3; }
float ease(float t01, unsigned mode) {
typedef float (*easing)(float);
easing modes[] = {
ease_out_sine,
ease_out_quad,
ease_out_cubic,
ease_out_quart,
ease_out_quint,
ease_out_expo,
ease_out_circ,
ease_out_back,
ease_out_elastic,
ease_out_bounce,
ease_in_sine,
ease_in_quad,
ease_in_cubic,
ease_in_quart,
ease_in_quint,
ease_in_expo,
ease_in_circ,
ease_in_back,
ease_in_elastic,
ease_in_bounce,
ease_inout_sine,
ease_inout_quad,
ease_inout_cubic,
ease_inout_quart,
ease_inout_quint,
ease_inout_expo,
ease_inout_circ,
ease_inout_back,
ease_inout_elastic,
ease_inout_bounce,
ease_nop,
ease_linear,
ease_inout_perlin,
};
return modes[clampi(mode, 0, countof(modes))](clampf(t01,0,1));
}
float ease_pong(float t, unsigned fn) { return 1 - ease(t, fn); }
float ease_ping_pong(float t, unsigned fn1, unsigned fn2) { return t < 0.5 ? ease(t*2,fn1) : ease(1-(t-0.5)*2,fn2); }
float ease_pong_ping(float t, unsigned fn1, unsigned fn2) { return 1 - ease_ping_pong(t,fn1,fn2); }
const char **ease_enums() {
static const char *list[] = {
"ease_out_sine",
"ease_out_quad",
"ease_out_cubic",
"ease_out_quart",
"ease_out_quint",
"ease_out_expo",
"ease_out_circ",
"ease_out_back",
"ease_out_elastic",
"ease_out_bounce",
"ease_in_sine",
"ease_in_quad",
"ease_in_cubic",
"ease_in_quart",
"ease_in_quint",
"ease_in_expo",
"ease_in_circ",
"ease_in_back",
"ease_in_elastic",
"ease_in_bounce",
"ease_inout_sine",
"ease_inout_quad",
"ease_inout_cubic",
"ease_inout_quart",
"ease_inout_quint",
"ease_inout_expo",
"ease_inout_circ",
"ease_inout_back",
"ease_inout_elastic",
"ease_inout_bounce",
"ease_nop",
"ease_linear",
"ease_inout_perlin",
0
};
return list;
}
const char *ease_enum(unsigned mode) {
return mode[ ease_enums() ];
}
/*AUTORUN {
ENUM(EASE_LINEAR|EASE_OUT);
ENUM(EASE_SINE|EASE_OUT);
ENUM(EASE_QUAD|EASE_OUT);
ENUM(EASE_CUBIC|EASE_OUT);
ENUM(EASE_QUART|EASE_OUT);
ENUM(EASE_QUINT|EASE_OUT);
ENUM(EASE_EXPO|EASE_OUT);
ENUM(EASE_CIRC|EASE_OUT);
ENUM(EASE_BACK|EASE_OUT);
ENUM(EASE_ELASTIC|EASE_OUT);
ENUM(EASE_BOUNCE|EASE_OUT);
ENUM(EASE_SINE|EASE_IN);
ENUM(EASE_QUAD|EASE_IN);
ENUM(EASE_CUBIC|EASE_IN);
ENUM(EASE_QUART|EASE_IN);
ENUM(EASE_QUINT|EASE_IN);
ENUM(EASE_EXPO|EASE_IN);
ENUM(EASE_CIRC|EASE_IN);
ENUM(EASE_BACK|EASE_IN);
ENUM(EASE_ELASTIC|EASE_IN);
ENUM(EASE_BOUNCE|EASE_IN);
ENUM(EASE_SINE|EASE_INOUT);
ENUM(EASE_QUAD|EASE_INOUT);
ENUM(EASE_CUBIC|EASE_INOUT);
ENUM(EASE_QUART|EASE_INOUT);
ENUM(EASE_QUINT|EASE_INOUT);
ENUM(EASE_EXPO|EASE_INOUT);
ENUM(EASE_CIRC|EASE_INOUT);
ENUM(EASE_BACK|EASE_INOUT);
ENUM(EASE_ELASTIC|EASE_INOUT);
ENUM(EASE_BOUNCE|EASE_INOUT);
ENUM(EASE_NOP);
ENUM(EASE_LINEAR);
ENUM(EASE_INOUT_PERLIN);
};*/
// ----------------------------------------------------------------------------
// tween
tween_t tween() {
tween_t tw = {0};
return tw;
}
float tween_update(tween_t *tw, float dt) {
if (!array_count(tw->keyframes)) return 0.0f;
for( int i = 0, end = array_count(tw->keyframes) - 1; i < end; ++i ) {
tween_keyframe_t *kf1 = &tw->keyframes[i];
tween_keyframe_t *kf2 = &tw->keyframes[i + 1];
if (tw->time >= kf1->t && tw->time <= kf2->t) {
float localT = (tw->time - kf1->t) / (kf2->t - kf1->t);
float easedT = ease(localT, kf1->ease);
tw->result = mix3(kf1->v, kf2->v, easedT);
break;
}
}
float done = (tw->time / tw->duration);
tw->time += dt;
return clampf(done, 0.0f, 1.0f);
}
void tween_reset(tween_t *tw) {
tw->time = 0.0f;
}
void tween_destroy(tween_t *tw) {
tween_t tw_ = {0};
array_free(tw->keyframes);
*tw = tw_;
}
static INLINE
int tween_comp_keyframes(const void *a, const void *b) {
float t1 = ((const tween_keyframe_t*)a)->t;
float t2 = ((const tween_keyframe_t*)b)->t;
return (t1 > t2) - (t1 < t2);
}
void tween_setkey(tween_t *tw, float t, vec3 v, unsigned mode) {
tween_keyframe_t keyframe = { t, v, mode };
array_push(tw->keyframes, keyframe);
array_sort(tw->keyframes, tween_comp_keyframes);
tw->duration = array_back(tw->keyframes)->t;
}
void tween_delkey(tween_t *tw, float t) { // @todo: untested
for( int i = 0, end = array_count(tw->keyframes); i < end; i++ ) {
if (tw->keyframes[i].t == t) {
array_erase_slow(tw->keyframes, i);
tw->duration = array_back(tw->keyframes)->t;
return;
}
}
}

View File

@ -1,96 +0,0 @@
// ----------------------------------------------------------------------------
// ease
API float ease_nop(float t);
API float ease_linear(float t);
API float ease_out_sine(float t);
API float ease_out_quad(float t);
API float ease_out_cubic(float t);
API float ease_out_quart(float t);
API float ease_out_quint(float t);
API float ease_out_expo(float t);
API float ease_out_circ(float t);
API float ease_out_back(float t);
API float ease_out_elastic(float t);
API float ease_out_bounce(float t);
API float ease_in_sine(float t);
API float ease_in_quad(float t);
API float ease_in_cubic(float t);
API float ease_in_quart(float t);
API float ease_in_quint(float t);
API float ease_in_expo(float t);
API float ease_in_circ(float t);
API float ease_in_back(float t);
API float ease_in_elastic(float t);
API float ease_in_bounce(float t);
API float ease_inout_sine(float t);
API float ease_inout_quad(float t);
API float ease_inout_cubic(float t);
API float ease_inout_quart(float t);
API float ease_inout_quint(float t);
API float ease_inout_expo(float t);
API float ease_inout_circ(float t);
API float ease_inout_back(float t);
API float ease_inout_elastic(float t);
API float ease_inout_bounce(float t);
API float ease_inout_perlin(float t);
enum EASE_FLAGS {
EASE_SINE,
EASE_QUAD,
EASE_CUBIC,
EASE_QUART,
EASE_QUINT,
EASE_EXPO,
EASE_CIRC,
EASE_BACK,
EASE_ELASTIC,
EASE_BOUNCE,
EASE_IN,
EASE_OUT = 0,
EASE_INOUT = EASE_IN * 2,
EASE_NOP = EASE_INOUT | (EASE_BOUNCE + 1),
EASE_LINEAR,
EASE_INOUT_PERLIN,
EASE_NUM
};
API float ease(float t01, unsigned fn); // / 0-to-1
API float ease_pong(float t01, unsigned fn); // \ 1-to-0
API float ease_ping_pong(float t, unsigned fn1, unsigned fn2); // /\ 0-to-1-to-0
API float ease_pong_ping(float t, unsigned fn1, unsigned fn2); // \/ 1-to-0-to-1
API const char *ease_enum(unsigned fn);
API const char**ease_enums();
// ----------------------------------------------------------------------------
// tween
typedef struct tween_keyframe_t {
float t;
vec3 v;
unsigned ease;
} tween_keyframe_t;
typedef struct tween_t {
array(tween_keyframe_t) keyframes;
vec3 result;
float time;
float duration;
} tween_t;
API tween_t tween();
API void tween_setkey(tween_t *tw, float t, vec3 v, unsigned easing_mode);
API void tween_delkey(tween_t *tw, float t);
API float tween_update(tween_t *tw, float dt);
API void tween_reset(tween_t *tw);
API void tween_destroy(tween_t *tw);

View File

@ -962,13 +962,10 @@ void window_focus() {
int window_has_focus() {
return !!glfwGetWindowAttrib(window, GLFW_FOCUSED);
}
void window_cursor(int visible) {
glfwSetInputMode(window, GLFW_CURSOR, visible ? GLFW_CURSOR_NORMAL : GLFW_CURSOR_DISABLED);
}
int window_has_cursor() {
return glfwGetInputMode(window, GLFW_CURSOR) == GLFW_CURSOR_NORMAL;
}
static int cursorshape = 1;
void window_cursor_shape(unsigned mode) {
cursorshape = (mode &= 7);
static GLFWcursor* cursors[7] = { 0 };
static unsigned enums[7] = {
0,
@ -996,6 +993,14 @@ void window_cursor_shape(unsigned mode) {
glfwSetCursor(window, mode < countof(enums) ? cursors[mode] : NULL);
}
}
void window_cursor(int visible) {
(cursorshape == CURSOR_SW_AUTO && visible ? nk_style_show_cursor : nk_style_hide_cursor)(ui_handle());
glfwSetInputMode(window, GLFW_CURSOR, visible ? GLFW_CURSOR_NORMAL : GLFW_CURSOR_DISABLED);
}
int window_has_cursor() {
return glfwGetInputMode(window, GLFW_CURSOR) == GLFW_CURSOR_NORMAL;
}
void window_visible(int visible) {
if(!window) return;
//if(window) (visible ? glfwRestoreWindow : glfwIconifyWindow)(window);

View File

@ -222169,6 +222169,7 @@ nk_glfw3_new_frame(struct nk_glfw* glfw)
nk_input_key(ctx, NK_KEY_SHIFT, 0);
}
if(glfwGetInputMode(win, GLFW_CURSOR) == GLFW_CURSOR_NORMAL) { //< @r-lyeh: do not grab input when mouse is hidden (fps cam)
glfwGetCursorPos(win, &x, &y);
nk_input_motion(ctx, (int)x, (int)y);
#if NK_GLFW_GL3_MOUSE_GRABBING
@ -222182,6 +222183,7 @@ nk_glfw3_new_frame(struct nk_glfw* glfw)
nk_input_button(ctx, NK_BUTTON_MIDDLE, (int)x, (int)y, glfwGetMouseButton(win, GLFW_MOUSE_BUTTON_MIDDLE) == GLFW_PRESS);
nk_input_button(ctx, NK_BUTTON_RIGHT, (int)x, (int)y, glfwGetMouseButton(win, GLFW_MOUSE_BUTTON_RIGHT) == GLFW_PRESS);
nk_input_button(ctx, NK_BUTTON_DOUBLE, glfw->double_click_pos.x, glfw->double_click_pos.y, glfw->is_double_click_down);
} //< @r-lyeh
nk_input_scroll(ctx, glfw->scroll);
nk_input_end(&glfw->ctx);
glfw->text_len = 0;

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,4 @@
// playground tests for V4K
// playground tests for FWK
// - rlyeh, public domain
//
// # quickstart

View File

@ -30,7 +30,7 @@ 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 gizmo, grid, snap, splats (@todo: fixed screen size gizmos)");
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)");
@ -68,9 +68,147 @@ static __thread char mpoutbuf[256];
#include "3rd_icon_mdi.h"
#include "v4k_editor.h"
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;
@ -118,6 +256,23 @@ 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.x);
@ -126,13 +281,6 @@ void kid_menu(kid *obj, const char *argv) {
obj->rgba_ = rgbaf( obj->color.x/255.0, obj->color.y/255.0, obj->color.z/255.0, obj->color.w/255.0 );
}
const char* kid_icon(kid *obj) {
if(obj->controllerid == 0) {
editor_symbol(obj->pos.x+16,obj->pos.y-16,ICON_MD_VIDEOGAME_ASSET);
return ICON_MD_VIDEOGAME_ASSET;
}
return NULL;
}
OBJTYPEDEF(kid,201);
@ -144,7 +292,7 @@ AUTORUN {
STRUCT(kid, float, angle, "Tilt degrees");
STRUCT(kid, vec4, color, "Tint color");
STRUCT(kid, char*, filename, "Filename" );
EXTEND(kid, ctor,tick,draw,aabb,menu,icon);
EXTEND(kid, ctor,tick,draw,aabb,edit,menu);
}
void game(unsigned frame, float dt, double t) {
@ -184,7 +332,11 @@ void game(unsigned frame, float dt, double t) {
obj_setname(o1, "o1");
obj_setname(o2, "o2");
obj*o3 = obj_new_ext(obj, "o3");
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");
@ -214,6 +366,20 @@ void game(unsigned frame, float dt, double t) {
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);
@ -233,5 +399,6 @@ int main(){
while( window_swap() ) {
editor_frame(game);
editor_gizmos(2);
}
}

View File

@ -1,23 +1,3 @@
#undef EXTEND
#define EXTEND(...) EXPAND(EXTEND, __VA_ARGS__)
#define EXTEND2(o,F1) obj_extend(o,F1)
#define EXTEND3(o,F1,F2) obj_extend(o,F1), obj_extend(o,F2)
#define EXTEND4(o,F1,F2,F3) obj_extend(o,F1), obj_extend(o,F2), obj_extend(o,F3)
#define EXTEND5(o,F1,F2,F3,F4) obj_extend(o,F1), obj_extend(o,F2), obj_extend(o,F3), obj_extend(o,F4)
#define EXTEND6(o,F1,F2,F3,F4,F5) obj_extend(o,F1), obj_extend(o,F2), obj_extend(o,F3), obj_extend(o,F4), obj_extend(o,F5)
#define EXTEND7(o,F1,F2,F3,F4,F5,F6) obj_extend(o,F1), obj_extend(o,F2), obj_extend(o,F3), obj_extend(o,F4), obj_extend(o,F5), obj_extend(o,F6)
#define EXTEND8(o,F1,F2,F3,F4,F5,F6,F7) obj_extend(o,F1), obj_extend(o,F2), obj_extend(o,F3), obj_extend(o,F4), obj_extend(o,F5), obj_extend(o,F6), obj_extend(o,F7)
#define EXTEND9(o,F1,F2,F3,F4,F5,F6,F7,F8) obj_extend(o,F1), obj_extend(o,F2), obj_extend(o,F3), obj_extend(o,F4), obj_extend(o,F5), obj_extend(o,F6), obj_extend(o,F7), obj_extend(o,F8)
#define EXTEND10(o,F1,F2,F3,F4,F5,F6,F7,F8,F9) obj_extend(o,F1), obj_extend(o,F2), obj_extend(o,F3), obj_extend(o,F4), obj_extend(o,F5), obj_extend(o,F6), obj_extend(o,F7), obj_extend(o,F8), obj_extend(o,F9)
#define obj_hasmethod(o,func) (obj_typeid(o)[obj_##func])
#ifndef ICON_MD_LIGHT_OFF
#define ICON_MD_LIGHT_OFF "\xEE\xA6\xB8"
#endif
// ## Editor long-term plan
// - editor = tree of nodes. levels and objects are nodes, and their widgets are also nodes
// - you can perform actions on nodes, with or without descendants, top-bottom or bottom-top
@ -186,8 +166,8 @@ int editor_begin(const char *title, int mode) {
if( mode == 0 ) return ui_panel(title, PANEL_OPEN);
if( mode == 1 ) return ui_window(title, 0);
int ww = window_width(); int w = ww * 0.66;
int hh = window_height(); int h = hh * 0.66;
int ww = window_width(), w = ww * 0.66;
int hh = window_height(), h = hh * 0.66;
struct nk_rect position = { (ww-w)/2,(hh-h)/2, w,h };
nk_flags win_flags = NK_WINDOW_TITLE | NK_WINDOW_BORDER |
@ -200,7 +180,7 @@ int editor_begin(const char *title, int mode) {
if( mode == 3 ) {
mode = 2, position.x = input(MOUSE_X), position.w = w/3, win_flags =
NK_WINDOW_TITLE|NK_WINDOW_CLOSABLE|
NK_WINDOW_SCALABLE|NK_WINDOW_MOVABLE| //< nuklear requires these two to `remember` popup pos
NK_WINDOW_SCALABLE|NK_WINDOW_MOVABLE| //< nuklear requires these two to `remember` popup rects
0;
}
@ -277,8 +257,8 @@ void editor_select_aabb(aabb box) {
aabb item = {0};
for each_set_ptr( editor.world, obj*, o ) {
if( obj_hasmethod(*o,aabb) ) {
if( obj_aabb(*o, &item) && aabb_test_aabb(item, box) ) {
if( obj_hasmethod(*o,aabb) && obj_aabb(*o, &item) ) {
if( aabb_test_aabb(item, box) ) {
if( is_inv )
editor_altselected(*o);
else
@ -431,6 +411,7 @@ void editor_inspect(obj *o) {
char *label = va(ICON_MD_UNDO "%s", NAME);
int changed = 0;
/**/ if( !strcmp(TYPE,"float") ) changed = ui_float(label, PTR);
else if( !strcmp(TYPE,"int") ) changed = ui_int(label, PTR);
else if( !strcmp(TYPE,"vec2") ) changed = ui_float2(label, PTR);
else if( !strcmp(TYPE,"vec3") ) changed = ui_float3(label, PTR);
else if( !strcmp(TYPE,"vec4") ) changed = ui_float4(label, PTR);
@ -546,7 +527,7 @@ void editor_symbol(int x, int y, const char *sym) {
#define FONT_ORANGE FONT_COLOR3
#define FONT_CYAN FONT_COLOR4
// style: atlas size, unicode ranges and 6 font faces max
do_once font_face(FONT_SYMBOLS, "MaterialIconsSharp-Regular.otf", 24.f, FONT_EM|FONT_1024);
do_once font_face(FONT_SYMBOLS, "MaterialIconsSharp-Regular.otf", 24.f, FONT_EM|FONT_2048);
// style: 10 colors max
do_once font_color(FONT_WHITE, WHITE);
do_once font_color(FONT_YELLOW, YELLOW);
@ -556,71 +537,6 @@ void editor_symbol(int x, int y, const char *sym) {
font_print(va(FONT_SYMBOLS FONT_WHITE FONT_H1 "%s", sym));
}
#if 0
void editor_gizmos() {
// draw gizmos, aabbs, markers, etc
for each_map_ptr(*editor_selected_map(),void*,o,int,selected) {
if(*selected && obj_draw[obj_typeid(*o)]) {} else continue;
void *obj = *o;
// get transform
vec3 *p = NULL;
vec3 *r = NULL;
vec3 *s = NULL;
aabb *a = NULL;
for each_objmember(o,TYPE,NAME,PTR) {
/**/ if( !strcmp(NAME, "position") ) p = PTR;
else if( !strcmp(NAME, "rotation") ) r = PTR;
else if( !strcmp(NAME, "scale") ) s = PTR;
else if( !strcmp(NAME, "aabb") ) a = PTR;
}
// debugdraw
ddraw_ontop_push(0);
#if 0
// bounding box
aabb *box = obj_aabb(obj);
if( box ) {
ddraw_color_push(YELLOW);
ddraw_aabb(box->min, box->max);
ddraw_color_pop();
}
#endif
// 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();
}
}
#endif
void editor_frame( void (*game)(unsigned, float, double) ) {
do_once {
set_init_ptr(editor.world);
@ -684,22 +600,23 @@ void editor_frame( void (*game)(unsigned, float, double) ) {
const char *ICON_SKIP = window_has_pause() ? ICON_MDI_STEP_FORWARD/*ICON_MDI_SKIP_NEXT*/ : ICON_MDI_FAST_FORWARD;
int ingame = !editor.active;
UI_MENU(13, \
UI_MENU(14, \
if(ingame) ui_disable(); \
UI_MENU_POPUP(ICON_MD_SETTINGS, vec2(0.33,1.00), ui_debug()) \
UI_MENU_ITEM(ICON_MDI_FILE_TREE, editor_send("scene")) \
if(ingame) ui_enable(); \
UI_MENU_ITEM(ICON_PL4Y, if(editor.t == 0) editor_send("eject"); editor_send(window_has_pause() ? "play" : "pause")) \
UI_MENU_ITEM(ICON_SKIP, editor_send(window_has_pause() ? "frame" : "slomo")) \
UI_MENU_ITEM(ICON_MDI_STOP, editor_send("stop")) \
UI_MENU_ITEM(ICON_MDI_EJECT, editor_send("eject")) \
UI_MENU_ITEM(STATS, stats_mode = (++stats_mode) % 3) \
UI_MENU_ALIGN_RIGHT(32+32+32+32+32+34) \
UI_MENU_ALIGN_RIGHT(32+32+32+32+32+32+34) \
if(ingame) ui_disable(); \
UI_MENU_ITEM(ICON_MDI_FILE_TREE, editor_send("scene")) \
UI_MENU_ITEM(ICON_MD_FOLDER_SPECIAL, editor_send("browser")) \
UI_MENU_ITEM(ICON_MDI_CHART_TIMELINE, editor_send("timeline")) \
UI_MENU_ITEM(ICON_MDI_CONSOLE, editor_send("console")) \
UI_MENU_ITEM(ICON_MDI_GRAPH, editor_send("nodes")) \
UI_MENU_ITEM(ICON_MD_SEARCH, editor_send("filter")) \
UI_MENU_POPUP(ICON_MD_SETTINGS, vec2(0.33,1.00), ui_debug()) \
if(ingame) ui_enable(); \
UI_MENU_ITEM(ICON_MD_CLOSE, editor_send("quit")) \
);
@ -711,42 +628,22 @@ void editor_frame( void (*game)(unsigned, float, double) ) {
if( obj_hasmethod(*o,edit) ) {
obj_edit(*o);
}
if( obj_hasmethod(*o,icon) ) {
obj_icon(*o);
}
}
#if 0
font_print(FONT_CENTER FONT_MIDDLE FONT_H1 FONT_SYMBOLS FONT_WHITE ""
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
);
#endif
// draw silhouettes
sprite_flush();
fx_begin();
for each_map_ptr(*editor_selected_map(),void*,o,int,selected) {
if( *selected && obj_hasmethod(*o,draw) ) {
fx_begin();
if( !*selected ) continue;
if( obj_hasmethod(*o,draw) ) {
obj_draw(*o);
sprite_flush();
fx_end();
}
if( obj_hasmethod(*o,edit) ) {
obj_edit(*o);
}
}
sprite_flush();
fx_end();
// draw box selection
if( !ui_active() ) { //< check that we're not moving a window
@ -772,35 +669,34 @@ void editor_frame( void (*game)(unsigned, float, double) ) {
}
}
//
aabb box = {0};
// draw mouse aabb
aabb mouse = { vec3(input(MOUSE_X),input(MOUSE_Y),0), vec3(input(MOUSE_X),input(MOUSE_Y),1)};
if( 1 ) {
ddraw_color_push(YELLOW);
ddraw_push_2d();
ddraw_aabb(mouse.min, mouse.max);
ddraw_pop_2d();
ddraw_color_pop();
}
// tick mouse aabb selection and contextual tab (RMB)
aabb box = {0};
for each_set(editor.world,obj*,o) {
if( !obj_hasmethod(o, aabb) ) continue;
if( !obj_aabb(o, &box) ) continue;
// draw aabb
if( 1 ) {
ddraw_color_push(YELLOW);
ddraw_push_2d();
ddraw_aabb(box.min, box.max);
ddraw_aabb(mouse.min, mouse.max);
ddraw_pop_2d();
ddraw_color_pop();
}
// trigger contextual inspector
if( input_down(MOUSE_R) ) {
int selected = editor_selected(o);
editor_setpopup(o, selected);
int is_selected = editor_selected(o);
editor_setpopup(o, is_selected);
}
// draw contextual inspector
if( editor_popup(o) ) {
if( editor_begin(va("%s (%s)", obj_name(o), obj_type(o)),3) ) {
if( editor_begin(va("%s (%s)", obj_name(o), obj_type(o)),EDITOR_WINDOW_NK_SMALL) ) {
ui_label2(obj_name(o), obj_type(o));
editor_inspect(o);
editor_end(3);
editor_end(EDITOR_WINDOW_NK_SMALL);
} else {
editor_setpopup(o, 0);
}
@ -809,7 +705,7 @@ void editor_frame( void (*game)(unsigned, float, double) ) {
// draw subeditors
static int preferred_window_mode = 1; // panel(0), window(1), nk_window(2), nk_popup(3)
static int preferred_window_mode = EDITOR_WINDOW;
static struct nk_color bak, *on = 0; do_once bak = ui_ctx->style.window.fixed_background.data.color; // ui_ctx->style.window.fixed_background.data.color = !!(on = (on ? NULL : &bak)) ? AS_NKCOLOR(0) : bak; };
if( editor.transparent ) ui_ctx->style.window.fixed_background.data.color = AS_NKCOLOR(0);
for each_array(editor.subeditors, subeditor, fn) {
@ -825,3 +721,4 @@ void editor_frame( void (*game)(unsigned, float, double) ) {
#include "v4k_editor2_browser.h"
#include "v4k_editor3_timeline.h"
#include "v4k_editor4_console.h"
#include "v4k_editor5_nodes.h"

View File

@ -138,7 +138,7 @@ void editor_scene_(obj *o, unsigned flags) {
}
int editor_scene(int window_mode) {
window_mode = 1; // force window
window_mode = EDITOR_WINDOW; // force window
if( editor_begin(SCENE_TITLE, window_mode)) {
// #define HELP ICON_MDI_INFORMATION_OUTLINE "@-A\n-B\n-C\n" ";"

View File

@ -4,7 +4,7 @@
EDITOR_BIND(browser, "held(CTRL)&down(2)", { ui_show(BROWSER_TITLE, ui_visible(BROWSER_TITLE) ^ true); });
int editor_browser(int window_mode) {
window_mode = 1; // force window
window_mode = EDITOR_WINDOW; // force window
if( editor_begin(BROWSER_TITLE, window_mode) ) {
const char *file = 0;
if( ui_browse(&file, NULL) ) {

View File

@ -8,7 +8,7 @@ int ui_tween(const char *label, tween_t *t) {
int expand_keys = label[0] == '!'; label += expand_keys;
const char *id = label;
if( strchr(id, '@') ) *strchr(((char*)id = va("%s", label)), '@') = '\0';
if( strchr(id, '@') ) *strchr((char*)(id = (const char*)va("%s", label)), '@') = '\0';
enum { LABEL_SPACING = 250 };
enum { ROUNDING = 0 };

View File

@ -0,0 +1,881 @@
#define NODES_ICON ICON_MDI_GRAPH
#define NODES_TITLE "Nodes " NODES_ICON
EDITOR_BIND(nodes, "held(CTRL)&down(5)", { ui_show(NODES_TITLE, ui_visible(NODES_TITLE) ^ true); });
/*
A basic node-based UI built with Nuklear.
Builds on the node editor example included in Nuklear v1.00, with the aim of
being used as a prototype for implementing a functioning node editor.
Features:
- Nodes of different types. Currently their implementations are #included in
the main file, but they could easily be turned into eg. a plugin system.
- Pins/pins of different types -- currently float values and colors.
- Adding and removing nodes.
- Linking nodes, with validation (one link per input, only link similar pins).
- Detaching and moving links.
- Evaluation of output values of connected nodes.
- Memory management based on fixed size arrays for links and node pointers
- Multiple node types
- Multiple pin types
- Linking between pins of the same type
- Detaching and reattaching links
- Getting value from linked node if pin is connected
Todo:
- Complete pin types.
- Allow dragging from output to input pin.
- Cut link by CTRL+clicking input pin.
- Cut link by drawing intersect line on a link.
- Group elemnts together with mouse, or LSHIFT+clicking.
- Drag groups.
- DEL elements.
- DEL groups.
- CTRL-C/CTRL-V/CTRL-X elements.
- CTRL-C/CTRL-V/CTRL-X groups.
- CTRL-Z,CTRL-Y.
- CTRL-N.
- CTRL-L,CTRL-S.
- CTRL-F.
- CTRL-Wheel Zooming.
- Allow to extend node types from Lua.
Extra todo:
- Execution Flow (see: nk_stroke_triangle, nk_fill_triangle)
- Complete missing nodes (see: nk_draw_image, nk_draw_text)
- Right-click could visualize node/board diagram as Lua script.
- Once that done, copy/pasting scripts should work within editor.
Sources:
- https://github.com/Immediate-Mode-UI/Nuklear/pull/561
- https://github.com/vurtun/nuklear/blob/master/demo/node_editor.c
*/
typedef enum pin_type_t {
type_flow,
type_int,type_float,
type_block,type_texture,type_image,
type_color,
/*
type_bool,
type_char, type_string,
type_int2, type_int3, type_int4,
type_float2, type_float3, type_float4,
type_array, type_map,
*/
type_total
} pin_type_t;
struct node_pin {
pin_type_t pin_type;
nk_bool is_connected;
struct node* connected_node;
int connected_pin;
};
struct node {
int ID;
char name[32];
struct nk_rect bounds;
int input_count;
int output_count;
struct node_pin *inputs;
struct node_pin *outputs;
struct {
float in_padding_x;
float in_padding_y;
float in_spacing_y;
float out_padding_x;
float out_padding_y;
float out_spacing_y;
} pin_spacing; /* Maybe this should be called "node_layout" and include the bounds? */
struct node *next; /* Z ordering only */
struct node *prev; /* Z ordering only */
void* (*eval_func)(struct node*, int oIndex);
void (*display_func)(struct nk_context*, struct node*);
};
struct node_link {
struct node* input_node;
int input_pin;
struct node* output_node;
int output_pin;
nk_bool is_active;
};
struct node_linking {
int active;
struct node *node;
int input_id;
int input_pin;
};
struct node_editor {
int initialized;
struct node *node_buf[32];
struct node_link links[64];
struct node *output_node;
struct node *begin;
struct node *end;
int node_count;
int link_count;
struct nk_rect bounds;
struct node *selected;
int show_grid;
struct nk_vec2 scrolling;
struct node_linking linking;
};
/* === PROTOTYPES === */
/* The node implementations need these two functions. */
/* These could/should go in a header file along with the node and node_pin structs and be #included in the node implementations */
struct node* node_editor_add(struct node_editor *editor, size_t nodeSize, const char *name, struct nk_rect bounds, int in_count, int out_count);
void* node_editor_eval_connected(struct node *node, int input_pin_number);
/* ================== */
/* === NODE TYPE IMPLEMENTATIONS === */
#define NODE_DEFAULT_ROW_HEIGHT 25
// ----------------------------------------------------------------------------------------------------
// #include "node_output.h"
struct node_type_output {
struct node node;
struct nk_colorf input_val;
};
struct nk_colorf *node_output_get(struct node* node) {
struct node_type_output *output_node = (struct node_type_output*)node;
if (!node->inputs[0].is_connected) {
struct nk_colorf black = {0.0f, 0.0f, 0.0f, 0.0f};
output_node->input_val = black;
}
return &output_node->input_val;
}
static void node_output_display(struct nk_context *ctx, struct node *node) {
if (node->inputs[0].is_connected) {
struct node_type_output *output_node = (struct node_type_output*)node;
output_node->input_val = *(struct nk_colorf*)node_editor_eval_connected(node, 0);
nk_layout_row_dynamic(ctx, 60, 1);
nk_button_color(ctx, nk_rgba_cf(output_node->input_val));
}
}
struct node* node_output_create(struct node_editor *editor, struct nk_vec2 position) {
struct node_type_output *output_node = (struct node_type_output*)node_editor_add(editor, sizeof(struct node_type_output), "Output", nk_rect(position.x, position.y, 100, 100), 1, 0);
if (output_node){
output_node->node.inputs[0].pin_type = type_color;
output_node->node.display_func = node_output_display;
}
return (struct node*)output_node;
}
// ----------------------------------------------------------------------------------------------------
// #include "node_float.h"
struct node_type_float {
struct node node;
float output_val;
};
static float *node_float_eval(struct node* node, int oIndex) {
struct node_type_float *float_node = (struct node_type_float*)node;
NK_ASSERT(oIndex == 0);
return &float_node->output_val;
}
static void node_float_draw(struct nk_context *ctx, struct node *node) {
struct node_type_float *float_node = (struct node_type_float*)node;
nk_layout_row_dynamic(ctx, NODE_DEFAULT_ROW_HEIGHT, 1);
float_node->output_val = nk_propertyf(ctx, "#Value:", 0.0f, float_node->output_val, 1.0f, 0.01f, 0.01f);
}
void node_float_create(struct node_editor *editor, struct nk_vec2 position) {
struct node_type_float *float_node = (struct node_type_float*)node_editor_add(editor, sizeof(struct node_type_float), "Float", nk_rect(position.x, position.y, 180, 75), 0, 1);
if (float_node)
{
float_node->output_val = 1.0f;
float_node->node.display_func = node_float_draw;
float_node->node.eval_func = (void*(*)(struct node*, int)) node_float_eval;
}
}
// ----------------------------------------------------------------------------------------------------
// #include "node_color.h"
struct node_type_color {
struct node node;
float input_val[4];
struct nk_colorf output_val;
};
static struct nk_colorf *node_color_eval(struct node* node, int oIndex)
{
struct node_type_color *color_node = (struct node_type_color*)node;
NK_ASSERT(oIndex == 0); /* only one output connector */
return &color_node->output_val;
}
static void node_color_draw(struct nk_context *ctx, struct node *node)
{
struct node_type_color *color_node = (struct node_type_color*)node;
float eval_result; /* Get the values from connected nodes into this so the inputs revert on disconnect */
const char* labels[4] = {"#R:","#G:","#B:","#A:"};
float color_val[4]; /* Because we can't just loop through the struct... */
nk_layout_row_dynamic(ctx, NODE_DEFAULT_ROW_HEIGHT, 1);
nk_button_color(ctx, nk_rgba_cf(color_node->output_val));
for (int i = 0; i < 4; i++)
{
if (color_node->node.inputs[i].is_connected) {
eval_result = *(float*)node_editor_eval_connected(node, i);
eval_result = nk_propertyf(ctx, labels[i], eval_result, eval_result, eval_result, 0.01f, 0.01f);
color_val[i] = eval_result;
}
else {
color_node->input_val[i] = nk_propertyf(ctx, labels[i], 0.0f, color_node->input_val[i], 1.0f, 0.01f, 0.01f);
color_val[i] = color_node->input_val[i];
}
}
color_node->output_val.r = color_val[0];
color_node->output_val.g = color_val[1];
color_node->output_val.b = color_val[2];
color_node->output_val.a = color_val[3];
}
void node_color_create(struct node_editor *editor, struct nk_vec2 position)
{
struct node_type_color *color_node = (struct node_type_color*)node_editor_add(editor, sizeof(struct node_type_color), "Color", nk_rect(position.x, position.y, 180, 190), 4, 1);
if (color_node)
{
const struct nk_colorf black = {0.0f, 0.0f, 0.0f, 1.0f};
for (int i = 0; i < color_node->node.input_count; i++)
color_node->node.inputs[i].pin_type = type_float;
color_node->node.outputs[0].pin_type = type_color;
color_node->node.pin_spacing.in_padding_y += NODE_DEFAULT_ROW_HEIGHT;
color_node->input_val[0] = 0.0f;
color_node->input_val[1] = 0.0f;
color_node->input_val[2] = 0.0f;
color_node->input_val[3] = 1.0f;
color_node->output_val = black;
color_node->node.display_func = node_color_draw;
color_node->node.eval_func = (void*(*)(struct node*, int)) node_color_eval;
}
}
// ----------------------------------------------------------------------------------------------------
// #include "node_blend.h"
struct node_type_blend {
struct node node;
struct nk_colorf input_val[2];
struct nk_colorf output_val;
float blend_val;
};
static struct nk_colorf *node_blend_eval(struct node *node, int oIndex) {
struct node_type_blend* blend_node = (struct node_type_blend*)node;
return &blend_node->output_val;
}
static void node_blend_display(struct nk_context *ctx, struct node *node) {
struct node_type_blend *blend_node = (struct node_type_blend*)node;
const struct nk_colorf blank = {0.0f, 0.0f, 0.0f, 0.0f};
float blend_amnt;
nk_layout_row_dynamic(ctx, NODE_DEFAULT_ROW_HEIGHT, 1);
for (int i = 0; i < 2; i++){
if(node->inputs[i].is_connected) {
blend_node->input_val[i] = *(struct nk_colorf*)node_editor_eval_connected(node, i);
}
else {
blend_node->input_val[i] = blank;
}
nk_button_color(ctx, nk_rgba_cf(blend_node->input_val[i]));
}
if (node->inputs[2].is_connected) {
blend_amnt = *(float*)node_editor_eval_connected(node, 2);
blend_amnt = nk_propertyf(ctx, "#Blend", blend_amnt, blend_amnt, blend_amnt, 0.01f, 0.01f);
}
else {
blend_node->blend_val = nk_propertyf(ctx, "#Blend", 0.0f, blend_node->blend_val, 1.0f, 0.01f, 0.01f);
blend_amnt = blend_node->blend_val;
}
if(node->inputs[0].is_connected && node->inputs[1].is_connected) {
blend_node->output_val.r = blend_node->input_val[0].r * (1.0f-blend_amnt) + blend_node->input_val[1].r * blend_amnt;
blend_node->output_val.g = blend_node->input_val[0].g * (1.0f-blend_amnt) + blend_node->input_val[1].g * blend_amnt;
blend_node->output_val.b = blend_node->input_val[0].b * (1.0f-blend_amnt) + blend_node->input_val[1].b * blend_amnt;
blend_node->output_val.a = blend_node->input_val[0].a * (1.0f-blend_amnt) + blend_node->input_val[1].a * blend_amnt;
}
else {
blend_node->output_val = blank;
}
}
void node_blend_create(struct node_editor *editor, struct nk_vec2 position) {
struct node_type_blend* blend_node = (struct node_type_blend*)node_editor_add(editor, sizeof(struct node_type_blend), "Blend", nk_rect(position.x, position.y, 180, 130), 3, 1);
if (blend_node) {
const struct nk_colorf blank = {0.0f, 0.0f, 0.0f, 0.0f};
for (int i = 0; i < (int)NK_LEN(blend_node->input_val); i++)
blend_node->node.inputs[i].pin_type = type_color;
blend_node->node.outputs[0].pin_type = type_color;
// blend_node->node.pin_spacing.in_padding_y = 42.0f;
// blend_node->node.pin_spacing.in_spacing_y = 29.0f;
for (int i = 0; i < (int)NK_LEN(blend_node->input_val); i++)
blend_node->input_val[i] = blank;
blend_node->output_val = blank;
blend_node->blend_val = 0.5f;
blend_node->node.display_func = node_blend_display;
blend_node->node.eval_func = (void*(*)(struct node*, int)) node_blend_eval;
}
}
/* ================================= */
#define NK_RGB3(r,g,b) {r,g,b,255}
#define BG_COLOR ((struct nk_color){60,60,60,192}) // nk_rgba(0,0,0,192)
static
struct editor_node_style {
int pin_type;
const char *shape;
struct nk_color color_idle;
struct nk_color color_hover;
} styles[] = {
// order matters:
{ type_flow, "triangle_right", NK_RGB3(200,200,200), NK_RGB3(255,255,255) }, // if .num_links == 0
{ type_int, "circle", NK_RGB3(33,227,175), NK_RGB3(135,239,195) },
{ type_float, "circle", NK_RGB3(156,253,65), NK_RGB3(144,225,137) },
{ type_block, "circle", NK_RGB3(6,165,239), NK_RGB3(137,196,247) },
{ type_texture, "circle", NK_RGB3(148,0,0), NK_RGB3(183,137,137) },
{ type_image, "circle", NK_RGB3(200,130,255), NK_RGB3(220,170,255) },
{ type_color, "circle", NK_RGB3(252,200,35), NK_RGB3(255,217,140) },
};
#define COLOR_FLOW_HI styles[type_flow].color_hover
#define COLOR_FLOW_LO styles[type_flow].color_idle
#define GRID_SIZE 64.0f
#define GRID_COLOR ((struct nk_color)NK_RGB3(80,80,120))
#define GRID_THICKNESS 1.0f
// 4 colors: top-left, top-right, bottom-right, bottom-left
#define GRID_BG_COLORS ((struct nk_color){30,30,30,255}), ((struct nk_color){40,20,0,255}), ((struct nk_color){30,30,30,255}), ((struct nk_color){20,30,40,255})
#define LINK_THICKNESS 1.0f
#define LINK_DRAW(POINT_A,POINT_B,COLOR) do { \
vec2 a = (POINT_A); \
vec2 b = (POINT_B); \
nk_stroke_line(canvas, a.x, a.y, b.x, b.y, LINK_THICKNESS, COLOR); \
} while(0)
#undef LINK_DRAW
#define LINK_DRAW(POINT_A,POINT_B,COLOR) do { \
vec2 a = (POINT_A); \
vec2 b = (POINT_B); \
nk_stroke_curve(canvas, a.x, a.y, a.x+50, a.y, b.x-50, b.y, b.x, b.y, LINK_THICKNESS, COLOR); \
} while(0)
#undef LINK_DRAW
#define LINK_DRAW(POINT_A,POINT_B,COLOR) do { \
vec2 a = (POINT_A); \
vec2 b = (POINT_B); \
float dist2 = len2( sub2( ptr2(&b.x), ptr2(&a.x) ) ); \
vec2 mid_a = mix2( ptr2(&a.x), ptr2(&b.x), 0.25 ); mid_a.y += dist2/2; \
vec2 mid_b = mix2( ptr2(&a.x), ptr2(&b.x), 0.75 ); mid_b.y += dist2/3; \
nk_stroke_curve(canvas, a.x, a.y, mid_a.x, mid_a.y, mid_b.x, mid_b.y, b.x, b.y, LINK_THICKNESS, COLOR); \
} while(0)
#define PIN_RADIUS 12
#define PIN_THICKNESS 1.0f
#define PIN_DRAW(PIN_ADDR,POINT,RADIUS) do { \
circle.x = (POINT).x - (RADIUS) / 2; \
circle.y = (POINT).y - (RADIUS) / 2; \
circle.w = circle.h = (RADIUS); \
struct nk_color color = node_get_type_color((PIN_ADDR).pin_type); \
if((PIN_ADDR).is_connected) \
nk_fill_circle(canvas, circle, color); \
else \
nk_stroke_circle(canvas, circle, PIN_THICKNESS, color); \
} while(0)
static struct nk_color node_get_type_color(unsigned pin_type) {
for( int i = 0; i < type_total; ++i )
if( styles[i].pin_type == pin_type )
return styles[i].color_idle;
return ((struct nk_color)NK_RGB3(255,0,255));
}
static void node_editor_push(struct node_editor *editor, struct node *node) {
if (!editor->begin) {
node->next = NULL;
node->prev = NULL;
editor->begin = node;
editor->end = node;
} else {
node->prev = editor->end;
if (editor->end)
editor->end->next = node;
node->next = NULL;
editor->end = node;
}
}
static void node_editor_pop(struct node_editor *editor, struct node *node) {
if (node->next)
node->next->prev = node->prev;
if (node->prev)
node->prev->next = node->next;
if (editor->end == node)
editor->end = node->prev;
if (editor->begin == node)
editor->begin = node->next;
node->next = NULL;
node->prev = NULL;
}
static struct node* node_editor_find_by_id(struct node_editor *editor, int ID) {
struct node *iter = editor->begin;
while (iter) {
if (iter->ID == ID)
return iter;
iter = iter->next;
}
return NULL;
}
static struct node_link* node_editor_find_link_by_output(struct node_editor *editor, struct node *output_node, int node_input_connector) {
for( int i = 0; i < editor->link_count; i++ ) {
if (editor->links[i].output_node == output_node &&
editor->links[i].output_pin == node_input_connector &&
editor->links[i].is_active == nk_true) {
return &editor->links[i];
}
}
return NULL;
}
static struct node_link* node_editor_find_link_by_input(struct node_editor *editor, struct node *input_node, int node_output_connector) {
for( int i = 0; i < editor->link_count; i++ ) {
if (editor->links[i].input_node == input_node &&
editor->links[i].input_pin == node_output_connector &&
editor->links[i].is_active == nk_true) {
return &editor->links[i];
}
}
return NULL;
}
static void node_editor_delete_link(struct node_link *link) {
link->is_active = nk_false;
link->input_node->outputs[link->input_pin].is_connected = nk_false;
link->output_node->inputs[link->output_pin].is_connected = nk_false;
}
struct node* node_editor_add(struct node_editor *editor, size_t nodeSize, const char *name, struct nk_rect bounds, int in_count, int out_count) {
static int IDs = 0;
struct node *node = NULL;
if ((nk_size)editor->node_count < NK_LEN(editor->node_buf)) {
/* node_buf has unused pins */
node = MALLOC(nodeSize);
editor->node_buf[editor->node_count++] = node;
node->ID = IDs++;
} else {
/* check for freed up pins in node_buf */
for (int i = 0; i < editor->node_count; i++) {
if (editor->node_buf[i] == NULL) {
node = MALLOC(nodeSize);
editor->node_buf[i] = node;
node->ID = i;
break;
}
}
}
if (node == NULL) {
puts("Node creation failed");
return NULL;
}
node->bounds = bounds;
node->input_count = in_count;
node->output_count = out_count;
node->inputs = MALLOC(node->input_count * sizeof(struct node_pin));
node->outputs = MALLOC(node->output_count * sizeof(struct node_pin));
for (int i = 0; i < node->input_count; i++) {
node->inputs[i].is_connected = nk_false;
node->inputs[i].pin_type = type_float; /* default pin type */
}
for (int i = 0; i < node->output_count; i++) {
node->outputs[i].is_connected = nk_false;
node->outputs[i].pin_type = type_float; /* default pin type */
}
/* default pin spacing */
node->pin_spacing.in_padding_x = 2;
node->pin_spacing.in_padding_y = 32 + 25/2 + 6; // titlebar height + next half row + adjust
node->pin_spacing.in_spacing_y = 25; // row height+border
node->pin_spacing.out_padding_x = 3;
node->pin_spacing.out_padding_y = 32 + 25/2 + 6; // titlebar height + next half row + adjust
node->pin_spacing.out_spacing_y = 25; // row height+border
strcpy(node->name, name);
node_editor_push(editor, node);
return node;
}
void *node_editor_eval_connected(struct node* node, int input_pin_number) {
NK_ASSERT(node->inputs[input_pin_number].is_connected);
return node->inputs[input_pin_number].connected_node->eval_func(node->inputs[input_pin_number].connected_node, node->inputs[input_pin_number].connected_pin);
}
static void node_editor_link(struct node_editor *editor, struct node *in_node, int in_pin, struct node *out_node, int out_pin) {
/* Confusingly, in and out nodes/pins here refer to the inputs and outputs OF THE LINK ITSELF, not the nodes */
struct node_link *link = NULL;
if ((nk_size)editor->link_count < NK_LEN(editor->links)) {
link = &editor->links[editor->link_count++];
} else {
for (int i = 0; i < (int)NK_LEN(editor->links); i++)
{
if (editor->links[i].is_active == nk_false) {
link = &editor->links[i];
break;
}
}
}
if (link) {
out_node->inputs[out_pin].is_connected = nk_true;
in_node->outputs[in_pin].is_connected = nk_true;
out_node->inputs[out_pin].connected_node = in_node;
out_node->inputs[out_pin].connected_pin = in_pin;
link->input_node = in_node;
link->input_pin = in_pin;
link->output_node = out_node;
link->output_pin = out_pin;
link->is_active = nk_true;
} else {
puts("Too many links");
}
}
static void node_editor_init(struct node_editor *editor) {
if (editor->initialized) return;
struct nk_rect total_space = nk_window_get_content_region(ui_ctx);
struct nk_vec2 output_node_position = { total_space.w*2/3, total_space.h/3 };
struct nk_vec2 color_node_position = { total_space.w*1/4, total_space.h/3 };
memset(editor, 0, sizeof(*editor));
editor->output_node = node_output_create(editor, output_node_position);
node_color_create(editor, color_node_position);
editor->show_grid = nk_true;
editor->initialized = 1;
}
static int node_editor(struct node_editor *editor) {
int n = 0;
struct nk_rect total_space;
const struct nk_input *in = &ui_ctx->input;
struct nk_command_buffer *canvas = nk_window_get_canvas(ui_ctx);
struct node *updated = 0;
node_editor_init(editor);
{
/* allocate complete window space */
total_space = nk_window_get_content_region(ui_ctx);
nk_layout_space_begin(ui_ctx, NK_STATIC, total_space.h, editor->node_count);
{
struct node *it = editor->begin;
struct nk_rect size = nk_layout_space_bounds(ui_ctx);
struct nk_panel *nodePanel = 0;
//nk_fill_rect(canvas, size, 0/*rounding*/, ((struct nk_color){30,30,30,255})); // 20,30,40,255
nk_fill_rect_multi_color(canvas, size, GRID_BG_COLORS);
if (editor->show_grid) {
/* display grid */
for (float x = (float)fmod(size.x - editor->scrolling.x, GRID_SIZE); x < size.w; x += GRID_SIZE)
nk_stroke_line(canvas, x+size.x, size.y, x+size.x, size.y+size.h, GRID_THICKNESS, GRID_COLOR);
for (float y = (float)fmod(size.y - editor->scrolling.y, GRID_SIZE); y < size.h; y += GRID_SIZE)
nk_stroke_line(canvas, size.x, y+size.y, size.x+size.w, y+size.y, GRID_THICKNESS, GRID_COLOR);
}
/* execute each node as a movable group */
/* loop through nodes */
while (it) {
/* Output node window should not have a close button */
nk_flags nodePanel_flags = NK_WINDOW_MOVABLE|NK_WINDOW_NO_SCROLLBAR|NK_WINDOW_BORDER|NK_WINDOW_TITLE;
if (it != editor->output_node)
nodePanel_flags |= NK_WINDOW_CLOSABLE;
/* calculate scrolled node window position and size */
nk_layout_space_push(ui_ctx, nk_rect(it->bounds.x - editor->scrolling.x,
it->bounds.y - editor->scrolling.y, it->bounds.w, it->bounds.h));
/* execute node window */
char *name = va(" " ICON_MD_MENU " %s",it->name); //< @r-lyeh added some spacing+icon because of our UI customizations
struct nk_color bak = ui_ctx->style.window.fixed_background.data.color;
ui_ctx->style.window.fixed_background.data.color = BG_COLOR;
if (nk_group_begin(ui_ctx, name, nodePanel_flags))
{
/* always have last selected node on top */
nodePanel = nk_window_get_panel(ui_ctx);
if (nk_input_mouse_clicked(in, NK_BUTTON_LEFT, nodePanel->bounds) &&
(!(it->prev && nk_input_mouse_clicked(in, NK_BUTTON_LEFT,
nk_layout_space_rect_to_screen(ui_ctx, nodePanel->bounds)))) &&
editor->end != it)
{
updated = it;
}
if ((nodePanel->flags & NK_WINDOW_HIDDEN)) /* Node close button has been clicked */
{
/* Delete node */
struct node_link *link_remove;
node_editor_pop(editor, it);
for (int n = 0; n < it->input_count; n++) {
if ((link_remove = node_editor_find_link_by_output(editor, it, n)))
{
node_editor_delete_link(link_remove);
}
}
for (int n = 0; n < it -> output_count; n++) {
while((link_remove = node_editor_find_link_by_input(editor, it, n)))
{
node_editor_delete_link(link_remove);
}
}
NK_ASSERT(editor->node_buf[it->ID] == it);
editor->node_buf[it->ID] = NULL;
FREE(it->inputs);
FREE(it->outputs);
FREE(it);
}
else {
/* ================= NODE CONTENT ===================== */
it->display_func(ui_ctx, it);
/* ==================================================== */
}
nk_group_end(ui_ctx);
}
ui_ctx->style.window.fixed_background.data.color = bak;
if (!(nodePanel->flags & NK_WINDOW_HIDDEN))
{
/* node pin and linking */
struct nk_rect bounds;
bounds = nk_layout_space_rect_to_local(ui_ctx, nodePanel->bounds);
bounds.x += editor->scrolling.x;
bounds.y += editor->scrolling.y;
it->bounds = bounds;
/* output pins */
for (int n = 0; n < it->output_count; ++n) {
struct nk_rect circle;
struct nk_vec2 pt = {nodePanel->bounds.x, nodePanel->bounds.y};
pt.x += nodePanel->bounds.w - PIN_RADIUS / 2 + it->pin_spacing.out_padding_x;
pt.y += it->pin_spacing.out_padding_y + it->pin_spacing.out_spacing_y * (n);
PIN_DRAW(it->outputs[n],pt,PIN_RADIUS);
/* start linking process */
/* set linking active */
if (nk_input_has_mouse_click_down_in_rect(in, NK_BUTTON_LEFT, circle, nk_true)) {
editor->linking.active = nk_true;
editor->linking.node = it;
editor->linking.input_id = it->ID;
editor->linking.input_pin = n;
}
/* draw link being dragged (from linked pin to mouse position) */
if (editor->linking.active && editor->linking.node == it &&
editor->linking.input_pin == n) {
LINK_DRAW(vec2(circle.x+3,circle.y+3),ptr2(&in->mouse.pos.x),COLOR_FLOW_HI);
}
}
/* input pins */
for (int n = 0; n < it->input_count; ++n) {
struct nk_rect circle;
struct nk_vec2 pt = {nodePanel->bounds.x, nodePanel->bounds.y};
pt.x += it->pin_spacing.in_padding_x;
pt.y += it->pin_spacing.in_padding_y + it->pin_spacing.in_spacing_y * (n);
PIN_DRAW(it->inputs[n],pt,PIN_RADIUS);
/* Detach link */
if (nk_input_has_mouse_click_down_in_rect(in, NK_BUTTON_LEFT, circle, nk_true) &&
editor->linking.active == nk_false &&
it->inputs[n].is_connected == nk_true) {
struct node_link *node_relink = node_editor_find_link_by_output(editor, it, n);
editor->linking.active = nk_true;
editor->linking.node = node_relink->input_node;
editor->linking.input_id = node_relink->input_node->ID;
editor->linking.input_pin = node_relink->input_pin;
node_editor_delete_link(node_relink);
}
/* Create link */
if (nk_input_is_mouse_released(in, NK_BUTTON_LEFT) &&
nk_input_is_mouse_hovering_rect(in, circle) &&
editor->linking.active &&
editor->linking.node != it &&
it->inputs[n].pin_type == editor->linking.node->outputs[editor->linking.input_pin].pin_type &&
it->inputs[n].is_connected != nk_true) {
editor->linking.active = nk_false;
node_editor_link(editor, editor->linking.node,
editor->linking.input_pin, it, n);
}
}
}
it = it->next;
}
/* reset (output) linking connection */
if (editor->linking.active && (!!input(KEY_LCTRL) || !!input(KEY_RCTRL) || nk_input_is_mouse_released(in, NK_BUTTON_LEFT))) {
editor->linking.active = nk_false;
editor->linking.node = NULL;
}
/* draw each static link */
for (int n = 0; n < editor->link_count; ++n) {
struct node_link *link = &editor->links[n];
if (link->is_active == nk_true){
struct node *ni = link->input_node;
struct node *no = link->output_node;
struct nk_vec2 l0 = nk_layout_space_to_screen(ui_ctx, nk_vec2(ni->bounds.x + ni->bounds.w + ni->pin_spacing.out_padding_x, 3.0f + ni->bounds.y + ni->pin_spacing.out_padding_y + ni->pin_spacing.out_spacing_y * (link->input_pin)));
struct nk_vec2 l1 = nk_layout_space_to_screen(ui_ctx, nk_vec2(no->bounds.x + no->pin_spacing.in_padding_x, 3.0f + no->bounds.y + no->pin_spacing.in_padding_y + no->pin_spacing.in_spacing_y * (link->output_pin)));
l0.x -= editor->scrolling.x;
l0.y -= editor->scrolling.y;
l1.x -= editor->scrolling.x;
l1.y -= editor->scrolling.y;
struct nk_color color = node_get_type_color(no->inputs[link->output_pin].pin_type);
LINK_DRAW(ptr2(&l0.x), ptr2(&l1.x), color);
}
}
if (updated) {
/* reshuffle nodes to have least recently selected node on top */
node_editor_pop(editor, updated);
node_editor_push(editor, updated);
}
/* node selection */
if (nk_input_mouse_clicked(in, NK_BUTTON_LEFT, nk_layout_space_bounds(ui_ctx))) {
it = editor->begin;
editor->selected = NULL;
editor->bounds = nk_rect(in->mouse.pos.x, in->mouse.pos.y, 100, 200);
while (it) {
struct nk_rect b = nk_layout_space_rect_to_screen(ui_ctx, it->bounds);
b.x -= editor->scrolling.x;
b.y -= editor->scrolling.y;
if (nk_input_is_mouse_hovering_rect(in, b))
editor->selected = it;
it = it->next;
}
}
/* contextual menu */
if (nk_contextual_begin(ui_ctx, 0, nk_vec2(150, 220), nk_window_get_bounds(ui_ctx))) {
struct nk_vec2 wincoords = { in->mouse.pos.x-total_space.x-50, in->mouse.pos.y-total_space.y-32 };
#if 1
static char *filter = 0;
static int do_filter = 0;
if( input_down(KEY_F) ) if( input(KEY_LCTRL) || input(KEY_RCTRL) ) do_filter ^= 1;
int choice = ui_toolbar(ICON_MD_SEARCH ";");
if( choice == 1 ) do_filter = 1;
if( do_filter ) {
ui_string(ICON_MD_CLOSE " Filter " ICON_MD_SEARCH, &filter);
if( ui_label_icon_clicked_L.x > 0 && ui_label_icon_clicked_L.x <= 24 ) { // if clicked on CANCEL icon (1st icon)
do_filter = 0;
}
} else {
if( filter ) filter[0] = '\0';
}
char *filter_mask = filter && filter[0] ? va("*%s*", filter) : "*";
#endif
#define ui_label_filtered(lbl) (strmatchi(lbl,filter_mask) && ui_label(lbl))
int close = 0;
if (ui_label_filtered("=Add Color node")) close=1,node_color_create(editor, wincoords);
if (ui_label_filtered("=Add Float node")) close=1,node_float_create(editor, wincoords);
if (ui_label_filtered("=Add Blend Node")) close=1,node_blend_create(editor, wincoords);
if (ui_label_filtered(editor->show_grid ? "=Hide Grid" : "=Show Grid"))
close=1,editor->show_grid = !editor->show_grid;
if(close) do_filter = 0, (filter ? filter[0] = '\0' : '\0'), nk_contextual_close(ui_ctx);
nk_contextual_end(ui_ctx);
}
}
nk_layout_space_end(ui_ctx);
/* window content scrolling */
if (nk_input_is_mouse_hovering_rect(in, nk_window_get_bounds(ui_ctx)) &&
nk_input_is_mouse_down(in, NK_BUTTON_MIDDLE)) {
editor->scrolling.x += in->mouse.delta.x;
editor->scrolling.y += in->mouse.delta.y;
}
}
return !nk_window_is_closed(ui_ctx, "NodeEdit");
}
int editor_nodes(int window_mode) {
window_mode = EDITOR_WINDOW; // force window
if( editor_begin(NODES_TITLE, window_mode) ) {
static struct node_editor nodeEditor = {0};
node_editor(&nodeEditor);
editor_end(window_mode);
}
return 0;
}
AUTORUN {
array_push(editor.subeditors, editor_nodes);
}

View File

@ -33,9 +33,7 @@ AUTOTEST {
obj_extend(boy, ctor);
obj_extend(boy, tick);
obj_extend(boy, save);
}
AUTOTEST {
// instance gameobjs
boy *obj1 = obj_new(boy, "boy", 123);

View File

@ -0,0 +1,724 @@
// ## Editor long-term plan
// - editor = tree of nodes. levels and objects are nodes, and their widgets are also nodes
// - you can perform actions on nodes, with or without descendants, top-bottom or bottom-top
// - these operations include load/save, undo/redo, reset, play/render, ddraw, etc
// - nodes are saved to disk as a filesystem layout: parents are folders, and leafs are files
// - network replication can be done by external tools by comparing the filesystems and by sending the resulting diff zipped
//
// ## Editor roadmap
// - Gizmos✱, scene tree, property editor✱, load/save✱, undo/redo✱, copy/paste, on/off (vis,tick,ddraw,log), vcs.
// - Scenenode pass: node singleton display, node console, node labels, node outlines✱.<!-- node == gameobj ? -->
// - Render pass: billboards✱, materials, un/lit, cast shadows, wireframe, skybox✱/mie✱, fog/atmosphere
// - Level pass: volumes, triggers, platforms, level streaming, collide✱, physics
// - Edit pass: Procedural content, brushes, noise and CSG.
// - GUI pass: timeline and data tracks, node graphs. <!-- worthy: will be reused into materials, animgraphs and blueprints -->
// ## Alt plan
// editor is a database + window/tile manager + ui toolkit; all network driven.
// to be precise, editor is a dumb app and ...
// - does not know a thing about what it stores.
// - does not know how to render the game graphics.
// - does not know how to run the game logic.
//
// the editor will create a canvas for your game to render.
// your game will be responsible to tick the logic and render the window inside the editor.
//
// that being said, editor...
// - can store datas hierarchically.
// - can perform diffs and merges, and version the datas into repositories.
// - can be instructed to render UI on top of game and window views.
// - can download new .natvis and plugins quickly.
// - can dump whole project in a filesystem form (zip).
// - editor reflects database contents up-to-date.
// - database can be queried and modified via OSC(UDP) commands.
// editor database uses one table, and stores two kind of payload types:
// - classes: defines typename and dna. class names are prefixed by '@'
// - instances: defines typename and datas. instance names are as-is, not prefixed.
//
// every save contains 5Ws: what, who, when, where, how,
// every save can be diffed/merged.
// ----------------------------------------------------------------------------
#define EDITOR_VERSION "2023.10"
// ----------------------------------------------------------------------------
typedef struct editor_bind_t {
const char *command;
const char *bindings;
void (*fn)();
} editor_bind_t;
array(editor_bind_t) editor_binds;
#define EDITOR_BIND(CMD,KEYS,...) void macro(editor_bind_##CMD##_fn_)() { __VA_ARGS__ }; AUTORUN { array_push(editor_binds, ((editor_bind_t){#CMD,KEYS,macro(editor_bind_##CMD##_fn_)}) ); }
// ----------------------------------------------------------------------------
typedef void (*editor_no_property)(void *);
array(void*) editor_persist_kv;
array(editor_no_property) editor_no_properties;
#define EDITOR_PROPERTY(property_name,T,defaults) \
typedef map(void*,T) editor_##property_name##_map_t; \
editor_##property_name##_map_t *editor_##property_name##_map() { \
static editor_##property_name##_map_t map = 0; do_once map_init_ptr(map); \
return &map; \
} \
T editor_##property_name(const void *obj) { \
return *map_find_or_add(*editor_##property_name##_map(), (void*)obj, ((T) defaults)); \
} \
void editor_set##property_name(const void *obj, T value) { \
*map_find_or_add(*editor_##property_name##_map(), (void*)obj, ((T) value)) = ((T) value); \
} \
void editor_alt##property_name(const void *obj) { \
T* found = map_find_or_add(*editor_##property_name##_map(), (void*)obj, ((T) defaults)); \
*found = (T)(uintptr_t)!(*found); \
} \
void editor_no##property_name(void *obj) { \
T* found = map_find_or_add(*editor_##property_name##_map(), (void*)obj, ((T) defaults)); \
map_erase(*editor_##property_name##_map(), (void*)obj); \
} \
AUTORUN { array_push(editor_persist_kv, #T); array_push(editor_persist_kv, editor_##property_name##_map()); array_push(editor_no_properties, editor_no##property_name); }
EDITOR_PROPERTY(open, int, 0); // whether object is tree opened in tree editor
EDITOR_PROPERTY(selected, int, 0); // whether object is displaying a contextual popup or not
EDITOR_PROPERTY(changed, int, 0); // whether object is displaying a contextual popup or not
EDITOR_PROPERTY(popup, int, 0); // whether object is displaying a contextual popup or not
EDITOR_PROPERTY(visible, int, 0);
EDITOR_PROPERTY(script, int, 0);
EDITOR_PROPERTY(event, int, 0);
EDITOR_PROPERTY(iconinstance, char*, 0);
EDITOR_PROPERTY(iconclass, char*, 0);
EDITOR_PROPERTY(treeoffsety, int, 0);
// new prop: breakpoint: request to break on any given node
// new prop: persist: objects with this property will be saved on disk
void editor_destroy_properties(void *o) {
for each_array(editor_no_properties,editor_no_property,fn) {
fn(o);
}
}
void editor_load_on_boot(void) {
puts("@todo: load editor");
}
void editor_save_on_quit(void) {
puts("@todo: save editor");
}
AUTORUN {
editor_load_on_boot();
(atexit)(editor_save_on_quit);
}
// ----------------------------------------------------------------------------
typedef int(*subeditor)(int mode);
struct editor_t {
// time
unsigned frame;
double t, dt, slomo;
// controls
int transparent;
int attached;
int active; // focus? does_grabinput instead?
int key;
vec2 mouse; // 2d coord for ray/picking
bool gamepad; // mask instead? |1|2|4|8
int hz_high, hz_medium, hz_low;
int filter;
bool battery; // battery mode: low fps
bool unlit;
bool ddraw;
// event root nodes
obj* root;
obj* on_init;
obj* on_tick;
obj* on_draw;
obj* on_edit;
obj* on_quit;
// all of them (hierarchical)
array(obj*) objs; // @todo:set() world?
// all of them (flat)
set(obj*) world;
//
array(char*) cmds;
// subeditors
array(subeditor) subeditors;
} editor = {
.active = 1,
.gamepad = 1,
.hz_high = 60, .hz_medium = 18, .hz_low = 5,
};
enum {
EDITOR_PANEL,
EDITOR_WINDOW,
EDITOR_WINDOW_NK,
EDITOR_WINDOW_NK_SMALL,
};
int editor_begin(const char *title, int mode) {
if( mode == 0 ) return ui_panel(title, PANEL_OPEN);
if( mode == 1 ) return ui_window(title, 0);
int ww = window_width(), w = ww * 0.66;
int hh = window_height(), h = hh * 0.66;
struct nk_rect position = { (ww-w)/2,(hh-h)/2, w,h };
nk_flags win_flags = NK_WINDOW_TITLE | NK_WINDOW_BORDER |
NK_WINDOW_MOVABLE | NK_WINDOW_SCALABLE |
NK_WINDOW_CLOSABLE | NK_WINDOW_MINIMIZABLE |
// NK_WINDOW_SCALE_LEFT|NK_WINDOW_SCALE_TOP| //< @fixme: move this logic into nuklear
// NK_WINDOW_MAXIMIZABLE | NK_WINDOW_PINNABLE |
0; // NK_WINDOW_SCROLL_AUTO_HIDE;
if( mode == 3 ) {
mode = 2, position.x = input(MOUSE_X), position.w = w/3, win_flags =
NK_WINDOW_TITLE|NK_WINDOW_CLOSABLE|
NK_WINDOW_SCALABLE|NK_WINDOW_MOVABLE| //< nuklear requires these two to `remember` popup rects
0;
}
if( mode == 2 || mode == 3 )
if (nk_begin(ui_ctx, title, position, win_flags))
return 1;
else
return nk_end(ui_ctx), 0;
return 0;
}
int editor_end(int mode) {
if( mode == 0 ) return ui_panel_end();
if( mode == 1 ) return ui_window_end();
if( mode == 2 ) nk_end(ui_ctx);
if( mode == 3 ) nk_end(ui_ctx);
return 0;
}
#if 0 // deprecate
bool editor_active() {
return ui_hover() || ui_active() || gizmo_active() ? editor.active : 0;
}
#endif
int editor_filter() {
if( editor.filter ) {
if (nk_begin(ui_ctx, "Filter", nk_rect(window_width()-window_width()*0.33,32, window_width()*0.33, 40),
NK_WINDOW_NO_SCROLLBAR)) {
char *bak = ui_filter; ui_filter = 0;
ui_string(ICON_MD_CLOSE " Filter " ICON_MD_SEARCH, &bak);
ui_filter = bak;
if( input(KEY_ESC) || ( ui_label_icon_clicked_L.x > 0 && ui_label_icon_clicked_L.x <= 24 )) {
if( ui_filter ) ui_filter[0] = '\0';
editor.filter = 0;
}
}
nk_end(ui_ctx);
}
return editor.filter;
}
static
int editor_select_(void *o, const char *mask) {
int matches = 0;
int off = mask[0] == '!', inv = mask[0] == '~';
int match = strmatchi(obj_type(o), mask+off+inv) || strmatchi(obj_name(o), mask+off+inv);
if( match ) {
editor_setselected(o, inv ? editor_selected(o) ^ 1 : !off);
++matches;
}
for each_objchild(o, obj*, oo) {
matches += editor_select_(oo, mask);
}
return matches;
}
void editor_select(const char *mask) {
for each_array( editor.objs, obj*, o )
editor_select_(o, mask);
}
void editor_unselect() { // same than editor_select("!**");
for each_map_ptr(*editor_selected_map(), void*,o, int, k) {
if( *k ) *k = 0;
}
}
void editor_select_aabb(aabb box) {
int is_inv = input_held(KEY_CTRL);
int is_add = input_held(KEY_SHIFT);
if( !is_inv && !is_add ) editor_unselect();
aabb item = {0};
for each_set_ptr( editor.world, obj*, o ) {
if( obj_hasmethod(*o,aabb) && obj_aabb(*o, &item) ) {
if( aabb_test_aabb(item, box) ) {
if( is_inv )
editor_altselected(*o);
else
editor_setselected(*o, 1);
}
}
}
}
static obj* active_ = 0;
static void editor_selectgroup_(obj *o, obj *first, obj *last) {
// printf("%s (looking for %s in [%s..%s])\n", obj_name(o), active_ ? obj_name(active_) : "", obj_name(first), obj_name(last));
if( !active_ ) if( o == first || o == last ) active_ = o == first ? last : first;
if( active_ ) editor_setselected(o, 1);
if( o == active_ ) active_ = 0;
for each_objchild(o, obj*, oo) {
editor_selectgroup_(oo, first, last);
}
}
void editor_selectgroup(obj *first, obj *last) {
if( last ) {
if( !first ) first = array_count(editor.objs) ? editor.objs[0] : NULL;
if( !first ) editor_setselected(last, 1);
else {
active_ = 0;
for each_array(editor.objs,obj*,o) {
editor_selectgroup_(o, first, last);
}
}
}
}
static obj *find_any_selected_(obj *o) {
if( editor_selected(o) ) return o;
for each_objchild(o,obj*,oo) {
obj *ooo = find_any_selected_(oo);
if( ooo )
return ooo;
}
return 0;
}
void* editor_first_selected() {
for each_array(editor.objs,obj*,o) {
obj *oo = find_any_selected_(o);
// if( oo ) printf("1st found: %s\n", obj_name(oo));
if( oo ) return oo;
}
return 0;
}
static obj *find_last_selected_(obj *o) {
void *last = 0;
if( editor_selected(o) ) last = o;
for each_objchild(o,obj*,oo) {
obj *ooo = find_last_selected_(oo);
if( ooo )
last = ooo;
}
return last;
}
void* editor_last_selected() {
void *last = 0;
for each_array(editor.objs,obj*,o) {
obj *oo = find_last_selected_(o);
// if( oo ) printf("last found: %s\n", obj_name(oo));
if( oo ) last = oo;
}
return last;
}
// ----------------------------------------------------------------------------------------
void editor_addtoworld(obj *o) {
set_find_or_add(editor.world, o);
for each_objchild(o, obj*, oo) {
editor_addtoworld(oo);
}
}
void editor_watch(const void *o) {
array_push(editor.objs, (obj*)o);
obj_push(o); // save state
editor_addtoworld((obj*)o);
}
void* editor_spawn(const char *ini) { // deprecate?
obj *o = obj_make(ini);
editor_watch(o);
return o;
}
void editor_spawn1() {
obj *selected = editor_first_selected();
obj *o = selected ? obj_make(obj_saveini(selected)) : obj_new(obj);
if( selected ) obj_attach(selected, o), editor_setopen(selected, 1);
else
editor_watch(o);
editor_unselect();
editor_setselected(o, 1);
}
typedef set(obj*) set_objp_t;
static
void editor_glob_recurse(set_objp_t*list, obj *o) {
set_find_or_add(*list, o);
for each_objchild(o,obj*,oo) {
editor_glob_recurse(list, oo);
}
}
void editor_destroy_selected() {
set_objp_t list = 0;
set_init_ptr(list);
for each_map_ptr(*editor_selected_map(), obj*,o, int,selected) {
if( *selected ) { editor_glob_recurse(&list, *o); }
}
for each_set(list, obj*, o) {
obj_detach(o);
}
for each_set(list, obj*, o) {
// printf("deleting %p %s\n", o, obj_name(o));
// remove from watched items
for (int i = 0, end = array_count(editor.objs); i < end; ++i) {
if (editor.objs[i] == o) {
editor.objs[i] = 0;
array_erase_slow(editor.objs, i);
--end;
--i;
}
}
// delete from world
set_erase(editor.world, o);
// delete properties + obj
editor_destroy_properties(o);
obj_free(o);
}
set_free(list);
}
void editor_inspect(obj *o) {
ui_section(va("%s (%s)", obj_type(o), obj_name(o)));
if( obj_hasmethod(o, menu) ) {
obj_menu(o);
}
for each_objmember(o,TYPE,NAME,PTR) {
if( !editor_changed(PTR) ) {
obj_push(o);
}
ui_label_icon_highlight = editor_changed(PTR); // @hack: remove ui_label_icon_highlight hack
char *label = va(ICON_MD_UNDO "%s", NAME);
int changed = 0;
/**/ if( !strcmp(TYPE,"float") ) changed = ui_float(label, PTR);
else if( !strcmp(TYPE,"int") ) changed = ui_int(label, PTR);
else if( !strcmp(TYPE,"vec2") ) changed = ui_float2(label, PTR);
else if( !strcmp(TYPE,"vec3") ) changed = ui_float3(label, PTR);
else if( !strcmp(TYPE,"vec4") ) changed = ui_float4(label, PTR);
else if( !strcmp(TYPE,"color") ) changed = ui_color4(label, PTR);
else if( !strcmp(TYPE,"char*") ) changed = ui_string(label, PTR);
else ui_label2(label, va("(%s)", TYPE)); // INFO instead of (TYPE)?
if( changed ) {
editor_setchanged(PTR, 1);
}
if( ui_label_icon_highlight )
if( ui_label_icon_clicked_L.x >= 6 && ui_label_icon_clicked_L.x <= 26 ) { // @hack: if clicked on UNDO icon (1st icon)
editor_setchanged(PTR, 0);
}
if( !editor_changed(PTR) ) {
obj_pop(o);
}
}
}
// ----------------------------------------------------------------------------------------
// tty
static thread_mutex_t *console_lock;
static array(char*) editor_jobs;
int editor_send(const char *cmd) { // return job-id
int skip = strspn(cmd, " \t\r\n");
char *buf = STRDUP(cmd + skip);
strswap(buf, "\r\n", "");
int jobid;
do_threadlock(console_lock) {
array_push(editor_jobs, buf);
jobid = array_count(editor_jobs) - 1;
}
return jobid;
}
const char* editor_recv(int jobid, double timeout_ss) {
char *answer = 0;
while(!answer && timeout_ss >= 0 ) {
do_threadlock(console_lock) {
if( editor_jobs[jobid][0] == '\0' )
answer = editor_jobs[jobid];
}
timeout_ss -= 0.1;
if( timeout_ss > 0 ) sleep_ms(100); // thread_yield()
}
return answer + 1;
}
// plain and ctrl keys
EDITOR_BIND(play, "down(F5)", { window_pause(0); /* if(!editor.slomo) editor.active = 0; */ editor.slomo = 1; } );
EDITOR_BIND(stop, "down(ESC)", { if(editor.t > 0) { window_pause(1), editor.frame = 0, editor.t = 0, editor.dt = 0, editor.slomo = 0, editor.active = 1; editor_select("**"); editor_destroy_selected(); }} );
EDITOR_BIND(eject, "down(F1)", { /*window_pause(!editor.active); editor.slomo = !!editor.active;*/ editor.active ^= 1; } );
EDITOR_BIND(pause, "(held(CTRL) & down(P)) | down(PAUSE)", { window_pause( window_has_pause() ^ 1 ); } );
EDITOR_BIND(frame, "held(CTRL) & down(LEFT)", { window_pause(1); editor.frame++, editor.t += (editor.dt = 1/60.f); } );
EDITOR_BIND(slomo, "held(CTRL) & down(RIGHT)", { window_pause(0); editor.slomo = maxf(fmod(editor.slomo * 2, 16), 0.125); } );
EDITOR_BIND(reload, "held(CTRL) & down(F5)", { window_reload(); } );
EDITOR_BIND(filter, "held(CTRL) & down(F)", { editor.filter ^= 1; } );
// alt keys
EDITOR_BIND(quit, "held(ALT) & down(F4)", { record_stop(), exit(0); } );
EDITOR_BIND(mute, "held(ALT) & down(M)", { audio_volume_master( 1 ^ !!audio_volume_master(-1) ); } );
EDITOR_BIND(gamepad, "held(ALT) & down(G)", { editor.gamepad ^= 1; } );
EDITOR_BIND(transparent, "held(ALT) & down(T)", { editor.transparent ^= 1; } );
EDITOR_BIND(record, "held(ALT) & down(Z)", { if(record_active()) record_stop(), ui_notify(va("Video recorded"), date_string()); else { char *name = file_counter(va("%s.mp4",app_name())); app_beep(), window_record(name); } } );
EDITOR_BIND(screenshot, "held(ALT) & down(S)", { char *name = file_counter(va("%s.png",app_name())); window_screenshot(name), ui_notify(va("Screenshot: %s", name), date_string()); } );
EDITOR_BIND(battery, "held(ALT) & down(B)", { editor.battery ^= 1; } );
EDITOR_BIND(outliner, "held(ALT) & down(O)", { ui_show("Outliner", ui_visible("Outliner") ^ true); } );
EDITOR_BIND(profiler, "held(ALT) & down(P)", { ui_show("Profiler", profiler_enable(ui_visible("Profiler") ^ true)); } );
EDITOR_BIND(fullscreen, "(held(ALT)&down(ENTER))|down(F11)",{ record_stop(), window_fullscreen( window_has_fullscreen() ^ 1 ); } ); // close any recording before framebuffer resizing, which would corrupt video stream
EDITOR_BIND(unlit, "held(ALT) & down(U)", { editor.unlit ^= 1; } );
EDITOR_BIND(ddraw, "held(ALT) & down(D)", { editor.ddraw ^= 1; } );
void editor_pump() {
for each_array(editor_binds,editor_bind_t,b) {
if( input_eval(b.bindings) ) {
editor_send(b.command);
}
}
do_threadlock(console_lock) {
for each_array_ptr(editor_jobs,char*,cmd) {
if( (*cmd)[0] ) {
int found = 0;
for each_array(editor_binds,editor_bind_t,b) {
if( !strcmpi(b.command, *cmd)) {
b.fn();
found = 1;
break;
}
}
if( !found ) {
// alert(va("Editor: could not handle `%s` command.", *cmd));
(*cmd)[0] = '\0'; strcatf(&(*cmd), "\1%s\n", "Err\n"); (*cmd)[0] = '\0';
}
if( (*cmd)[0] ) {
(*cmd)[0] = '\0'; strcatf(&(*cmd), "\1%s\n", "Ok\n"); (*cmd)[0] = '\0';
}
}
}
}
}
// ----------------------------------------------------------------------------------------
void editor_symbol(int x, int y, const char *sym) {
#define FONT_SYMBOLS FONT_FACE2
#define FONT_WHITE FONT_COLOR1
#define FONT_YELLOW FONT_COLOR2
#define FONT_ORANGE FONT_COLOR3
#define FONT_CYAN FONT_COLOR4
// style: atlas size, unicode ranges and 6 font faces max
do_once font_face(FONT_SYMBOLS, "MaterialIconsSharp-Regular.otf", 24.f, FONT_EM|FONT_2048);
// style: 10 colors max
do_once font_color(FONT_WHITE, WHITE);
do_once font_color(FONT_YELLOW, YELLOW);
do_once font_color(FONT_CYAN, CYAN);
do_once font_color(FONT_ORANGE, ORANGE);
font_goto(x,y);
font_print(va(FONT_SYMBOLS FONT_WHITE FONT_H1 "%s", sym));
}
void editor_frame( void (*game)(unsigned, float, double) ) {
do_once {
set_init_ptr(editor.world);
//set_init_ptr(editor.selection);
profiler_enable( false );
window_pause( true );
window_cursor_shape(CURSOR_SW_AUTO);
fx_load("editorOutline.fs");
fx_enable(0, 1);
obj_setname(editor.root = obj_new(obj), "Signals");
obj_setname(editor.on_init = obj_new(obj), "onInit");
obj_setname(editor.on_tick = obj_new(obj), "onTick");
obj_setname(editor.on_draw = obj_new(obj), "onDraw");
obj_setname(editor.on_edit = obj_new(obj), "onEdit");
obj_setname(editor.on_quit = obj_new(obj), "onQuit");
obj_attach(editor.root, editor.on_init);
obj_attach(editor.root, editor.on_tick);
obj_attach(editor.root, editor.on_draw);
obj_attach(editor.root, editor.on_edit);
obj_attach(editor.root, editor.on_quit);
editor_seticoninstance(editor.root, ICON_MDI_SIGNAL_VARIANT);
editor_seticoninstance(editor.on_init, ICON_MDI_SIGNAL_VARIANT);
editor_seticoninstance(editor.on_tick, ICON_MDI_SIGNAL_VARIANT);
editor_seticoninstance(editor.on_draw, ICON_MDI_SIGNAL_VARIANT);
editor_seticoninstance(editor.on_edit, ICON_MDI_SIGNAL_VARIANT);
editor_seticoninstance(editor.on_quit, ICON_MDI_SIGNAL_VARIANT);
editor_seticonclass(obj_type(editor.root), ICON_MDI_CUBE_OUTLINE);
}
// game tick
game(editor.frame, editor.dt, editor.t);
// timing
editor.dt = clampf(window_delta(), 0, 1/60.f) * !window_has_pause() * editor.slomo;
editor.t += editor.dt;
editor.frame += !window_has_pause();
editor.frame += !editor.frame;
// process inputs & messages
editor_pump();
// adaptive framerate
int app_on_background = !window_has_focus();
int hz = app_on_background ? editor.hz_low : editor.battery ? editor.hz_medium : editor.hz_high;
window_fps_lock( hz < 5 ? 5 : hz );
// draw menubar
static int stats_mode = 1;
static double last_fps = 0; if(!window_has_pause()) last_fps = window_fps();
const char *STATS = va("x%4.3f %03d.%03dss %02dF %s",
editor.slomo, (int)editor.t, (int)(1000 * (editor.t - (int)editor.t)),
(editor.frame-1) % ((int)window_fps_target() + !(int)window_fps_target()),
stats_mode == 1 ? va("%5.2f/%dfps", last_fps, (int)window_fps_target()) : stats_mode == 0 ? "0/0 KiB" : xstats());
const char *ICON_PL4Y = window_has_pause() ? ICON_MDI_PLAY : ICON_MDI_PAUSE;
const char *ICON_SKIP = window_has_pause() ? ICON_MDI_STEP_FORWARD/*ICON_MDI_SKIP_NEXT*/ : ICON_MDI_FAST_FORWARD;
int ingame = !editor.active;
UI_MENU(14, \
if(ingame) ui_disable(); \
UI_MENU_ITEM(ICON_MDI_FILE_TREE, editor_send("scene")) \
if(ingame) ui_enable(); \
UI_MENU_ITEM(ICON_PL4Y, if(editor.t == 0) editor_send("eject"); editor_send(window_has_pause() ? "play" : "pause")) \
UI_MENU_ITEM(ICON_SKIP, editor_send(window_has_pause() ? "frame" : "slomo")) \
UI_MENU_ITEM(ICON_MDI_STOP, editor_send("stop")) \
UI_MENU_ITEM(ICON_MDI_EJECT, editor_send("eject")) \
UI_MENU_ITEM(STATS, stats_mode = (++stats_mode) % 3) \
UI_MENU_ALIGN_RIGHT(32+32+32+32+32+32+34) \
if(ingame) ui_disable(); \
UI_MENU_ITEM(ICON_MD_FOLDER_SPECIAL, editor_send("browser")) \
UI_MENU_ITEM(ICON_MDI_CHART_TIMELINE, editor_send("timeline")) \
UI_MENU_ITEM(ICON_MDI_CONSOLE, editor_send("console")) \
UI_MENU_ITEM(ICON_MDI_GRAPH, editor_send("nodes")) \
UI_MENU_ITEM(ICON_MD_SEARCH, editor_send("filter")) \
UI_MENU_POPUP(ICON_MD_SETTINGS, vec2(0.33,1.00), ui_debug()) \
if(ingame) ui_enable(); \
UI_MENU_ITEM(ICON_MD_CLOSE, editor_send("quit")) \
);
if( !editor.active ) return;
// draw edit view (gizmos, position markers, etc).
for each_set_ptr(editor.world,obj*,o) {
if( obj_hasmethod(*o,edit) ) {
obj_edit(*o);
}
}
// draw silhouettes
sprite_flush();
fx_begin();
for each_map_ptr(*editor_selected_map(),void*,o,int,selected) {
if( !*selected ) continue;
if( obj_hasmethod(*o,draw) ) {
obj_draw(*o);
}
if( obj_hasmethod(*o,edit) ) {
obj_edit(*o);
}
}
sprite_flush();
fx_end();
// draw box selection
if( !ui_active() ) { //< check that we're not moving a window
static vec2 from = {0}, to = {0};
if( input_down(MOUSE_L) ) to = vec2(input(MOUSE_X), input(MOUSE_Y)), from = to;
if( input(MOUSE_L) ) to = vec2(input(MOUSE_X), input(MOUSE_Y));
if( len2sq(sub2(from,to)) > 0 ) {
vec2 a = min2(from, to), b = max2(from, to);
ddraw_push_2d();
ddraw_color_push(YELLOW);
ddraw_line( vec3(a.x,a.y,0),vec3(b.x-1,a.y,0) );
ddraw_line( vec3(b.x,a.y,0),vec3(b.x,b.y-1,0) );
ddraw_line( vec3(b.x,b.y,0),vec3(a.x-1,b.y,0) );
ddraw_line( vec3(a.x,b.y,0),vec3(a.x,a.y-1,0) );
ddraw_color_pop();
ddraw_pop_2d();
}
if( input_up(MOUSE_L) ) {
vec2 a = min2(from, to), b = max2(from, to);
from = to = vec2(0,0);
editor_select_aabb(aabb(vec3(a.x,a.y,0),vec3(b.x,b.y,0)));
}
}
// draw mouse aabb
aabb mouse = { vec3(input(MOUSE_X),input(MOUSE_Y),0), vec3(input(MOUSE_X),input(MOUSE_Y),1)};
if( 1 ) {
ddraw_color_push(YELLOW);
ddraw_push_2d();
ddraw_aabb(mouse.min, mouse.max);
ddraw_pop_2d();
ddraw_color_pop();
}
// tick mouse aabb selection and contextual tab (RMB)
aabb box = {0};
for each_set(editor.world,obj*,o) {
if( !obj_hasmethod(o, aabb) ) continue;
if( !obj_aabb(o, &box) ) continue;
// trigger contextual inspector
if( input_down(MOUSE_R) ) {
int is_selected = editor_selected(o);
editor_setpopup(o, is_selected);
}
// draw contextual inspector
if( editor_popup(o) ) {
if( editor_begin(va("%s (%s)", obj_name(o), obj_type(o)),EDITOR_WINDOW_NK_SMALL) ) {
ui_label2(obj_name(o), obj_type(o));
editor_inspect(o);
editor_end(EDITOR_WINDOW_NK_SMALL);
} else {
editor_setpopup(o, 0);
}
}
}
// draw subeditors
static int preferred_window_mode = EDITOR_WINDOW;
static struct nk_color bak, *on = 0; do_once bak = ui_ctx->style.window.fixed_background.data.color; // ui_ctx->style.window.fixed_background.data.color = !!(on = (on ? NULL : &bak)) ? AS_NKCOLOR(0) : bak; };
if( editor.transparent ) ui_ctx->style.window.fixed_background.data.color = AS_NKCOLOR(0);
for each_array(editor.subeditors, subeditor, fn) {
fn(preferred_window_mode);
}
ui_ctx->style.window.fixed_background.data.color = bak;
// draw ui filter (note: render at end-of-frame, so it's hopefully on-top)
editor_filter();
}
#include "v4k_editor1_scene.h"
#include "v4k_editor2_browser.h"
#include "v4k_editor3_timeline.h"
#include "v4k_editor4_console.h"
#include "v4k_editor5_nodes.h"

View File

@ -0,0 +1,184 @@
#define SCENE_ICON ICON_MDI_FILE_TREE
#define SCENE_TITLE "Scene " SCENE_ICON
EDITOR_BIND(scene, "held(CTRL)&down(1)", { ui_show(SCENE_TITLE, ui_visible(SCENE_TITLE) ^ true); });
EDITOR_PROPERTY(bookmarked, int, 0);
EDITOR_BIND(node_new, "down(INS)", { editor_spawn1(); } );
EDITOR_BIND(node_del, "down(DEL)", { editor_destroy_selected(); } );
EDITOR_BIND(node_save, "held(CTRL)&down(S)", { puts("@todo"); } );
EDITOR_BIND(scene_save, "held(CTRL)&down(S)&held(SHIFT)",{ puts("@todo"); } );
EDITOR_BIND(select_all, "held(CTRL) & down(A)", { editor_select("**"); } );
EDITOR_BIND(select_none, "held(CTRL) & down(D)", { editor_select("!**"); } );
EDITOR_BIND(select_invert, "held(CTRL) & down(I)", { editor_select("~**"); } );
EDITOR_BIND(bookmark, "held(CTRL) & down(B)", { editor_selected_map_t *map = editor_selected_map(); \
int on = 0; \
for each_map_ptr(*map,void*,o,int,selected) if(*selected) on |= !editor_bookmarked(*o); \
for each_map_ptr(*map,void*,o,int,selected) if(*selected) editor_setbookmarked(*o, on); \
} );
enum {
SCENE_RECURSE = 1,
SCENE_SELECTION = 2,
SCENE_CHECKBOX = 4,
SCENE_INDENT = 8,
SCENE_ALL = ~0u
};
static
void editor_scene_(obj *o, unsigned flags) {
static unsigned tabs = ~0u;
++tabs;
if( o ) {
unsigned do_tags = 1;
unsigned do_indent = !!(flags & SCENE_INDENT);
unsigned do_checkbox = !!(flags & SCENE_CHECKBOX);
unsigned do_recurse = !!(flags & SCENE_RECURSE);
unsigned do_selection = !!(flags & SCENE_SELECTION);
nk_layout_row_dynamic(ui_ctx, 25, 1);
const char *objicon = editor_iconinstance(o);
if(!objicon) objicon = editor_iconclass(obj_type(o));
if(!objicon) objicon = ICON_MDI_CUBE_OUTLINE;
const char *objname = va("%s (%s)", obj_type(o), obj_name(o));
const char *objchevron =
!do_recurse || array_count(*obj_children(o)) <= 1 ? ICON_MDI_CIRCLE_SMALL :
editor_open(o) ? ICON_MDI_CHEVRON_DOWN : ICON_MDI_CHEVRON_RIGHT;
char *label = va("%*s%s%s %s", do_indent*(4+2*tabs), "", objchevron, objicon, objname);
const char *iconsL =
//editor_selected(o) ? ICON_MD_CHECK_BOX : ICON_MD_CHECK_BOX_OUTLINE_BLANK;
editor_selected(o) ? ICON_MDI_CHECKBOX_MARKED : ICON_MDI_CHECKBOX_BLANK_OUTLINE;
const char *iconsR = va("%s%s%s",
editor_script(o) ? ICON_MDI_SCRIPT : ICON_MDI_CIRCLE_SMALL,
editor_event(o) ? ICON_MDI_CALENDAR : ICON_MDI_CIRCLE_SMALL,
editor_visible(o) ? ICON_MDI_EYE_OUTLINE : ICON_MDI_EYE_CLOSED );
UI_TOOLBAR_OVERLAY_DECLARE(int choiceL, choiceR);
struct nk_command_buffer *canvas = nk_window_get_canvas(ui_ctx);
struct nk_rect bounds; nk_layout_peek(&bounds, ui_ctx);
int clicked = nk_hovered_text(ui_ctx, label, strlen(label), NK_TEXT_LEFT, editor_selected(o));
if( clicked && nk_input_is_mouse_hovering_rect(&ui_ctx->input, ((struct nk_rect) { bounds.x,bounds.y,bounds.w*0.66,bounds.h })) )
editor_altselected( o );
vec2i offset_in_tree = {0};
if( do_indent ) {
float thickness = 2.f;
struct nk_color color = {255,255,255,64};
int offsx = 30;
int spacx = 10;
int lenx = (tabs+1)*spacx;
int halfy = bounds.h / 2;
int offsy = halfy + 2;
offset_in_tree = vec2i(bounds.x+offsx+lenx-spacx,bounds.y+offsy);
editor_settreeoffsety(o, offset_in_tree.y);
for( obj *p = obj_parent(o); p ; p = 0 )
nk_stroke_line(canvas, offset_in_tree.x-6,offset_in_tree.y, offset_in_tree.x-spacx,offset_in_tree.y, thickness, color),
nk_stroke_line(canvas, offset_in_tree.x-spacx,offset_in_tree.y,offset_in_tree.x-spacx,editor_treeoffsety(p)+4, thickness, color);
}
if( ui_contextual() ) {
API int editor_send(const char *);
int choice = ui_label(ICON_MD_BOOKMARK_ADDED "Toggle bookmarks (CTRL+B)");
if( choice & 1 ) editor_send("bookmark");
ui_contextual_end(!!choice);
}
UI_TOOLBAR_OVERLAY(choiceL,iconsL,nk_rgba_f(1,1,1,do_checkbox*ui_alpha*0.65),NK_TEXT_LEFT);
if( do_tags )
UI_TOOLBAR_OVERLAY(choiceR,iconsR,nk_rgba_f(1,1,1,ui_alpha*0.65),NK_TEXT_RIGHT);
if( choiceR == 3 ) editor_altscript( o );
if( choiceR == 2 ) editor_altevent( o);
if( choiceR == 1 ) editor_altvisible( o );
if( do_recurse && editor_open(o) ) {
for each_objchild(o,obj*,oo) {
editor_scene_(oo,flags);
}
}
if( clicked && !choiceL && !choiceR ) {
int is_picking = input(KEY_CTRL);
if( !is_picking ) {
if( input(KEY_SHIFT) ) {
editor_selectgroup( editor_first_selected(), editor_last_selected() );
} else {
editor_unselect();
editor_setselected(o, 1);
}
}
for( obj *p = obj_parent(o); p; p = obj_parent(p) ) {
editor_setopen(p, 1);
}
if( nk_input_is_mouse_hovering_rect(&ui_ctx->input, ((struct nk_rect) { bounds.x,bounds.y,offset_in_tree.x-bounds.x+UI_ICON_FONTSIZE/2,bounds.h })) ) {
editor_altopen( o );
}
}
}
--tabs;
}
int editor_scene(int window_mode) {
window_mode = EDITOR_WINDOW; // force window
if( editor_begin(SCENE_TITLE, window_mode)) {
// #define HELP ICON_MDI_INFORMATION_OUTLINE "@-A\n-B\n-C\n" ";"
int choice = ui_toolbar(ICON_MDI_PLUS "@New node (CTRL+N);" ICON_MDI_DOWNLOAD "@Save node (CTRL+S);" ICON_MDI_DOWNLOAD "@Save scene (SHIFT+CTRL+S);" ICON_MD_BOOKMARK_ADDED "@Toggle Bookmark (CTRL+B);");
if( choice == 1 ) editor_send("node_new");
if( choice == 2 ) editor_send("node_save");
if( choice == 3 ) editor_send("scene_save");
if( choice == 4 ) editor_send("bookmark");
array(obj*) bookmarks = 0;
for each_map_ptr(*editor_bookmarked_map(), void*,o,int,bookmarked) {
if( *bookmarked ) {
array_push(bookmarks, *o);
}
}
if( ui_collapse("!" ICON_MD_BOOKMARK "Bookmarks", "DEBUG:BOOKMARK")) {
for each_array( bookmarks, obj*, o )
editor_scene_( o, SCENE_ALL & ~(SCENE_RECURSE|SCENE_INDENT|SCENE_CHECKBOX) );
ui_collapse_end();
}
array_free(bookmarks);
editor_scene_( editor.root, SCENE_ALL );
for each_array( editor.objs, obj*, o )
editor_scene_( o, SCENE_ALL );
ui_separator();
// edit selection
for each_map(*editor_selected_map(), void*,o, int, k) {
if( k ) editor_inspect(o);
}
editor_end(window_mode);
}
return 0;
}
AUTORUN {
array_push(editor.subeditors, editor_scene);
}

View File

@ -0,0 +1,21 @@
#define BROWSER_ICON ICON_MD_FOLDER_SPECIAL
#define BROWSER_TITLE "Browser " BROWSER_ICON
EDITOR_BIND(browser, "held(CTRL)&down(2)", { ui_show(BROWSER_TITLE, ui_visible(BROWSER_TITLE) ^ true); });
int editor_browser(int window_mode) {
window_mode = EDITOR_WINDOW; // force window
if( editor_begin(BROWSER_TITLE, window_mode) ) {
const char *file = 0;
if( ui_browse(&file, NULL) ) {
const char *sep = ifdef(win32, "\"", "'");
app_exec(va("%s %s%s%s", ifdef(win32, "start \"\"", ifdef(osx, "open", "xdg-open")), sep, file, sep));
}
editor_end(window_mode);
}
return 0;
}
AUTORUN {
array_push(editor.subeditors, editor_browser);
}

View File

@ -0,0 +1,172 @@
#define TIMELINE_ICON ICON_MDI_CHART_TIMELINE
#define TIMELINE_TITLE "Timeline " TIMELINE_ICON
EDITOR_BIND(timeline, "held(CTRL)&down(3)", { ui_show(TIMELINE_TITLE, ui_visible(TIMELINE_TITLE) ^ true); });
int ui_tween(const char *label, tween_t *t) {
if( ui_filter && ui_filter[0] ) if( !strstr(label, ui_filter) ) return 0;
int expand_keys = label[0] == '!'; label += expand_keys;
const char *id = label;
if( strchr(id, '@') ) *strchr((char*)(id = (const char*)va("%s", label)), '@') = '\0';
enum { LABEL_SPACING = 250 };
enum { ROUNDING = 0 };
enum { THICKNESS = 1 };
enum { PIXELS_PER_SECOND = 60 };
enum { KEY_WIDTH = 5, KEY_HEIGHT = 5 };
enum { TIMELINE_HEIGHT = 25 };
enum { MARKER1_HEIGHT = 5, MARKER10_HEIGHT = 20, MARKER5_HEIGHT = (MARKER1_HEIGHT + MARKER10_HEIGHT) / 2 };
unsigned base_color = WHITE;
unsigned time_color = YELLOW;
unsigned duration_color = ORANGE;
unsigned key_color = GREEN;
int changed = 0;
#if 0
// two rows with height:30 composed of three widgets
nk_layout_row_template_begin(ui_ctx, 30);
nk_layout_row_template_push_variable(ui_ctx, t->duration * PIXELS_PER_SECOND); // min 80px. can grow
nk_layout_row_template_end(ui_ctx);
#endif
char *sid = va("%s.%d", id, 0);
uint64_t hash = 14695981039346656037ULL, mult = 0x100000001b3ULL;
for(int i = 0; sid[i]; ++i) hash = (hash ^ sid[i]) * mult;
ui_hue = (hash & 0x3F) / (float)0x3F; ui_hue += !ui_hue;
ui_label(label);
struct nk_command_buffer *canvas = nk_window_get_canvas(ui_ctx);
struct nk_rect bounds; nk_layout_peek(&bounds, ui_ctx);
bounds.y -= 30;
struct nk_rect baseline = bounds; baseline.y += 30/2;
baseline.x += LABEL_SPACING;
baseline.w -= LABEL_SPACING;
// tween duration
{
struct nk_rect pos = baseline;
pos.w = pos.x + t->duration * PIXELS_PER_SECOND;
pos.y -= TIMELINE_HEIGHT/2;
pos.h = TIMELINE_HEIGHT;
nk_stroke_rect(canvas, pos, ROUNDING, THICKNESS*2, AS_NKCOLOR(duration_color));
}
// tween ranges
for(int i = 0, end = array_count(t->keyframes) - 1; i < end; ++i) {
tween_keyframe_t *k = t->keyframes + i;
tween_keyframe_t *next = k + 1;
struct nk_rect pos = baseline;
pos.x += k->t * PIXELS_PER_SECOND;
pos.w = (next->t - k->t) * PIXELS_PER_SECOND;
pos.y -= TIMELINE_HEIGHT/2;
pos.h = TIMELINE_HEIGHT;
char *sid = va("%s.%d", id, i);
uint64_t hash = 14695981039346656037ULL, mult = 0x100000001b3ULL;
for(int i = 0; sid[i]; ++i) hash = (hash ^ sid[i]) * mult;
ui_hue = (hash & 0x3F) / (float)0x3F; ui_hue += !ui_hue;
struct nk_color c = nk_hsva_f(ui_hue, 0.75f, 0.8f, ui_alpha);
nk_fill_rect(canvas, pos, ROUNDING, k->ease == EASE_NOP ? AS_NKCOLOR(0) : c); // AS_NKCOLOR(track_color));
}
// horizontal line
nk_stroke_line(canvas, baseline.x, baseline.y, baseline.x+baseline.w,baseline.y, THICKNESS, AS_NKCOLOR(base_color));
// unit, 5-unit and 10-unit markers
for( float i = 0, j = 0; i < baseline.w; i += PIXELS_PER_SECOND/10, ++j ) {
int len = !((int)j%10) ? MARKER10_HEIGHT : !((int)j%5) ? MARKER5_HEIGHT : MARKER1_HEIGHT;
nk_stroke_line(canvas, baseline.x+i, baseline.y-len, baseline.x+i, baseline.y+len, THICKNESS, AS_NKCOLOR(base_color));
}
// time marker
float px = t->time * PIXELS_PER_SECOND;
nk_stroke_line(canvas, baseline.x+px, bounds.y, baseline.x+px, bounds.y+bounds.h, THICKNESS*2, AS_NKCOLOR(time_color));
nk_draw_symbol(canvas, NK_SYMBOL_TRIANGLE_DOWN, ((struct nk_rect){ baseline.x+px-4,bounds.y-4-8,8,8}), /*bg*/AS_NKCOLOR(0), /*fg*/AS_NKCOLOR(time_color), 0.f/*border_width*/, ui_ctx->style.font);
// key markers
for each_array_ptr(t->keyframes, tween_keyframe_t, k) {
struct nk_rect pos = baseline;
pos.x += k->t * PIXELS_PER_SECOND;
vec2 romboid[] = {
{pos.x-KEY_WIDTH,pos.y}, {pos.x,pos.y-KEY_HEIGHT},
{pos.x+KEY_WIDTH,pos.y}, {pos.x,pos.y+KEY_HEIGHT}
};
nk_fill_polygon(canvas, (float*)romboid, countof(romboid), AS_NKCOLOR(key_color));
}
// keys ui
if( expand_keys )
for(int i = 0, end = array_count(t->keyframes); i < end; ++i) {
tween_keyframe_t *k = t->keyframes + i;
if( ui_collapse(va("Key %d", i), va("%s.%d", id, i))) {
changed |= ui_float("Time", &k->t);
changed |= ui_float3("Value", &k->v.x);
changed |= ui_list("Ease", ease_enums(), EASE_NUM, &k->ease );
ui_collapse_end();
}
}
return changed;
}
tween_t* rand_tween() {
tween_t demo = tween();
int num_keys = randi(2,8);
double t = 0;
for( int i = 0; i < num_keys; ++i) {
tween_setkey(&demo, t, scale3(vec3(randf(),randf(),randf()),randi(-5,5)), randi(0,EASE_NUM) );
t += randi(1,5) / ((float)(1 << randi(0,2)));
}
tween_t *p = CALLOC(1, sizeof(tween_t));
memcpy(p, &demo, sizeof(tween_t));
return p;
}
int editor_timeline(int window_mode) {
static array(tween_t*) tweens = 0;
do_once {
array_push(tweens, rand_tween());
}
if( editor.t == 0 )
for each_array(tweens, tween_t*,t) {
tween_reset(t);
}
else
for each_array(tweens, tween_t*,t) {
tween_update(t, editor.dt);
}
static void *selected = NULL;
if( editor_begin(TIMELINE_TITLE, window_mode) ) {
int choice = ui_toolbar(ICON_MDI_PLUS ";" ICON_MDI_MINUS );
if( choice == 1 ) array_push(tweens, rand_tween());
if( choice == 2 && selected ) {
int target = -1;
for( int i = 0, end = array_count(tweens); i < end; ++i ) if( tweens[i] == selected ) { target = i; break; }
if( target >= 0 ) { array_erase_slow(tweens, target); selected = NULL; }
}
for each_array(tweens, tween_t*,t) {
ui_tween(va("%s%p@%05.2fs Value: %s", t == selected ? "!":"", t, t->time, ftoa3(t->result)), t);
if(ui_label_icon_clicked_L.x) selected = (t != selected) ? t : NULL;
}
editor_end(window_mode);
}
return 0;
}
AUTORUN {
array_push(editor.subeditors, editor_timeline);
}

View File

@ -0,0 +1,56 @@
#define CONSOLE_ICON ICON_MDI_CONSOLE
#define CONSOLE_TITLE "Console " CONSOLE_ICON
EDITOR_BIND(console, "held(CTRL)&down(4)", { ui_show(CONSOLE_TITLE, ui_visible(CONSOLE_TITLE) ^ true); });
int editor_console(int window_mode) {
if( editor_begin(CONSOLE_TITLE, window_mode) ) {
// peek complete window space
struct nk_rect bounds = nk_window_get_content_region(ui_ctx);
enum { CONSOLE_LINE_HEIGHT = 20 };
static array(char*) lines = 0;
do_once {
array_push(lines, stringf("> Editor v%s. Type `%s` for help.", EDITOR_VERSION, ""));
}
int max_lines = (bounds.h - UI_ROW_HEIGHT) / (CONSOLE_LINE_HEIGHT * 2);
if( max_lines >= 1 ) {
nk_layout_row_static(ui_ctx, bounds.h - UI_ROW_HEIGHT, bounds.w, 1);
if(nk_group_begin(ui_ctx, "console.group", NK_WINDOW_NO_SCROLLBAR|NK_WINDOW_BORDER)) {
nk_layout_row_static(ui_ctx, CONSOLE_LINE_HEIGHT, bounds.w, 1);
for( int i = array_count(lines); i < max_lines; ++i )
array_push_front(lines, 0);
for( int i = array_count(lines) - max_lines; i < array_count(lines); ++i ) {
if( !lines[i] ) {
#if 0 // debug
nk_label_wrap(ui_ctx, va("%d.A/%d",i+1,max_lines));
nk_label_wrap(ui_ctx, va("%d.B/%d",i+1,max_lines));
#else
nk_label_wrap(ui_ctx, "");
nk_label_wrap(ui_ctx, "");
#endif
} else {
nk_label_wrap(ui_ctx, lines[i]);
const char *answer = isdigit(*lines[i]) ? editor_recv( atoi(lines[i]), 0 ) : NULL;
nk_label_wrap(ui_ctx, answer ? answer : "");
}
}
nk_group_end(ui_ctx);
}
}
static char *cmd = 0;
if( ui_string(NULL, &cmd) ) {
int jobid = editor_send(cmd);
array_push(lines, stringf("%d> %s", jobid, cmd));
cmd[0] = 0;
}
editor_end(window_mode);
}
return 0;
}
AUTORUN {
array_push(editor.subeditors, editor_console);
}

View File

@ -0,0 +1,881 @@
#define NODES_ICON ICON_MDI_GRAPH
#define NODES_TITLE "Nodes " NODES_ICON
EDITOR_BIND(nodes, "held(CTRL)&down(5)", { ui_show(NODES_TITLE, ui_visible(NODES_TITLE) ^ true); });
/*
A basic node-based UI built with Nuklear.
Builds on the node editor example included in Nuklear v1.00, with the aim of
being used as a prototype for implementing a functioning node editor.
Features:
- Nodes of different types. Currently their implementations are #included in
the main file, but they could easily be turned into eg. a plugin system.
- Pins/pins of different types -- currently float values and colors.
- Adding and removing nodes.
- Linking nodes, with validation (one link per input, only link similar pins).
- Detaching and moving links.
- Evaluation of output values of connected nodes.
- Memory management based on fixed size arrays for links and node pointers
- Multiple node types
- Multiple pin types
- Linking between pins of the same type
- Detaching and reattaching links
- Getting value from linked node if pin is connected
Todo:
- Complete pin types.
- Allow dragging from output to input pin.
- Cut link by CTRL+clicking input pin.
- Cut link by drawing intersect line on a link.
- Group elemnts together with mouse, or LSHIFT+clicking.
- Drag groups.
- DEL elements.
- DEL groups.
- CTRL-C/CTRL-V/CTRL-X elements.
- CTRL-C/CTRL-V/CTRL-X groups.
- CTRL-Z,CTRL-Y.
- CTRL-N.
- CTRL-L,CTRL-S.
- CTRL-F.
- CTRL-Wheel Zooming.
- Allow to extend node types from Lua.
Extra todo:
- Execution Flow (see: nk_stroke_triangle, nk_fill_triangle)
- Complete missing nodes (see: nk_draw_image, nk_draw_text)
- Right-click could visualize node/board diagram as Lua script.
- Once that done, copy/pasting scripts should work within editor.
Sources:
- https://github.com/Immediate-Mode-UI/Nuklear/pull/561
- https://github.com/vurtun/nuklear/blob/master/demo/node_editor.c
*/
typedef enum pin_type_t {
type_flow,
type_int,type_float,
type_block,type_texture,type_image,
type_color,
/*
type_bool,
type_char, type_string,
type_int2, type_int3, type_int4,
type_float2, type_float3, type_float4,
type_array, type_map,
*/
type_total
} pin_type_t;
struct node_pin {
pin_type_t pin_type;
nk_bool is_connected;
struct node* connected_node;
int connected_pin;
};
struct node {
int ID;
char name[32];
struct nk_rect bounds;
int input_count;
int output_count;
struct node_pin *inputs;
struct node_pin *outputs;
struct {
float in_padding_x;
float in_padding_y;
float in_spacing_y;
float out_padding_x;
float out_padding_y;
float out_spacing_y;
} pin_spacing; /* Maybe this should be called "node_layout" and include the bounds? */
struct node *next; /* Z ordering only */
struct node *prev; /* Z ordering only */
void* (*eval_func)(struct node*, int oIndex);
void (*display_func)(struct nk_context*, struct node*);
};
struct node_link {
struct node* input_node;
int input_pin;
struct node* output_node;
int output_pin;
nk_bool is_active;
};
struct node_linking {
int active;
struct node *node;
int input_id;
int input_pin;
};
struct node_editor {
int initialized;
struct node *node_buf[32];
struct node_link links[64];
struct node *output_node;
struct node *begin;
struct node *end;
int node_count;
int link_count;
struct nk_rect bounds;
struct node *selected;
int show_grid;
struct nk_vec2 scrolling;
struct node_linking linking;
};
/* === PROTOTYPES === */
/* The node implementations need these two functions. */
/* These could/should go in a header file along with the node and node_pin structs and be #included in the node implementations */
struct node* node_editor_add(struct node_editor *editor, size_t nodeSize, const char *name, struct nk_rect bounds, int in_count, int out_count);
void* node_editor_eval_connected(struct node *node, int input_pin_number);
/* ================== */
/* === NODE TYPE IMPLEMENTATIONS === */
#define NODE_DEFAULT_ROW_HEIGHT 25
// ----------------------------------------------------------------------------------------------------
// #include "node_output.h"
struct node_type_output {
struct node node;
struct nk_colorf input_val;
};
struct nk_colorf *node_output_get(struct node* node) {
struct node_type_output *output_node = (struct node_type_output*)node;
if (!node->inputs[0].is_connected) {
struct nk_colorf black = {0.0f, 0.0f, 0.0f, 0.0f};
output_node->input_val = black;
}
return &output_node->input_val;
}
static void node_output_display(struct nk_context *ctx, struct node *node) {
if (node->inputs[0].is_connected) {
struct node_type_output *output_node = (struct node_type_output*)node;
output_node->input_val = *(struct nk_colorf*)node_editor_eval_connected(node, 0);
nk_layout_row_dynamic(ctx, 60, 1);
nk_button_color(ctx, nk_rgba_cf(output_node->input_val));
}
}
struct node* node_output_create(struct node_editor *editor, struct nk_vec2 position) {
struct node_type_output *output_node = (struct node_type_output*)node_editor_add(editor, sizeof(struct node_type_output), "Output", nk_rect(position.x, position.y, 100, 100), 1, 0);
if (output_node){
output_node->node.inputs[0].pin_type = type_color;
output_node->node.display_func = node_output_display;
}
return (struct node*)output_node;
}
// ----------------------------------------------------------------------------------------------------
// #include "node_float.h"
struct node_type_float {
struct node node;
float output_val;
};
static float *node_float_eval(struct node* node, int oIndex) {
struct node_type_float *float_node = (struct node_type_float*)node;
NK_ASSERT(oIndex == 0);
return &float_node->output_val;
}
static void node_float_draw(struct nk_context *ctx, struct node *node) {
struct node_type_float *float_node = (struct node_type_float*)node;
nk_layout_row_dynamic(ctx, NODE_DEFAULT_ROW_HEIGHT, 1);
float_node->output_val = nk_propertyf(ctx, "#Value:", 0.0f, float_node->output_val, 1.0f, 0.01f, 0.01f);
}
void node_float_create(struct node_editor *editor, struct nk_vec2 position) {
struct node_type_float *float_node = (struct node_type_float*)node_editor_add(editor, sizeof(struct node_type_float), "Float", nk_rect(position.x, position.y, 180, 75), 0, 1);
if (float_node)
{
float_node->output_val = 1.0f;
float_node->node.display_func = node_float_draw;
float_node->node.eval_func = (void*(*)(struct node*, int)) node_float_eval;
}
}
// ----------------------------------------------------------------------------------------------------
// #include "node_color.h"
struct node_type_color {
struct node node;
float input_val[4];
struct nk_colorf output_val;
};
static struct nk_colorf *node_color_eval(struct node* node, int oIndex)
{
struct node_type_color *color_node = (struct node_type_color*)node;
NK_ASSERT(oIndex == 0); /* only one output connector */
return &color_node->output_val;
}
static void node_color_draw(struct nk_context *ctx, struct node *node)
{
struct node_type_color *color_node = (struct node_type_color*)node;
float eval_result; /* Get the values from connected nodes into this so the inputs revert on disconnect */
const char* labels[4] = {"#R:","#G:","#B:","#A:"};
float color_val[4]; /* Because we can't just loop through the struct... */
nk_layout_row_dynamic(ctx, NODE_DEFAULT_ROW_HEIGHT, 1);
nk_button_color(ctx, nk_rgba_cf(color_node->output_val));
for (int i = 0; i < 4; i++)
{
if (color_node->node.inputs[i].is_connected) {
eval_result = *(float*)node_editor_eval_connected(node, i);
eval_result = nk_propertyf(ctx, labels[i], eval_result, eval_result, eval_result, 0.01f, 0.01f);
color_val[i] = eval_result;
}
else {
color_node->input_val[i] = nk_propertyf(ctx, labels[i], 0.0f, color_node->input_val[i], 1.0f, 0.01f, 0.01f);
color_val[i] = color_node->input_val[i];
}
}
color_node->output_val.r = color_val[0];
color_node->output_val.g = color_val[1];
color_node->output_val.b = color_val[2];
color_node->output_val.a = color_val[3];
}
void node_color_create(struct node_editor *editor, struct nk_vec2 position)
{
struct node_type_color *color_node = (struct node_type_color*)node_editor_add(editor, sizeof(struct node_type_color), "Color", nk_rect(position.x, position.y, 180, 190), 4, 1);
if (color_node)
{
const struct nk_colorf black = {0.0f, 0.0f, 0.0f, 1.0f};
for (int i = 0; i < color_node->node.input_count; i++)
color_node->node.inputs[i].pin_type = type_float;
color_node->node.outputs[0].pin_type = type_color;
color_node->node.pin_spacing.in_padding_y += NODE_DEFAULT_ROW_HEIGHT;
color_node->input_val[0] = 0.0f;
color_node->input_val[1] = 0.0f;
color_node->input_val[2] = 0.0f;
color_node->input_val[3] = 1.0f;
color_node->output_val = black;
color_node->node.display_func = node_color_draw;
color_node->node.eval_func = (void*(*)(struct node*, int)) node_color_eval;
}
}
// ----------------------------------------------------------------------------------------------------
// #include "node_blend.h"
struct node_type_blend {
struct node node;
struct nk_colorf input_val[2];
struct nk_colorf output_val;
float blend_val;
};
static struct nk_colorf *node_blend_eval(struct node *node, int oIndex) {
struct node_type_blend* blend_node = (struct node_type_blend*)node;
return &blend_node->output_val;
}
static void node_blend_display(struct nk_context *ctx, struct node *node) {
struct node_type_blend *blend_node = (struct node_type_blend*)node;
const struct nk_colorf blank = {0.0f, 0.0f, 0.0f, 0.0f};
float blend_amnt;
nk_layout_row_dynamic(ctx, NODE_DEFAULT_ROW_HEIGHT, 1);
for (int i = 0; i < 2; i++){
if(node->inputs[i].is_connected) {
blend_node->input_val[i] = *(struct nk_colorf*)node_editor_eval_connected(node, i);
}
else {
blend_node->input_val[i] = blank;
}
nk_button_color(ctx, nk_rgba_cf(blend_node->input_val[i]));
}
if (node->inputs[2].is_connected) {
blend_amnt = *(float*)node_editor_eval_connected(node, 2);
blend_amnt = nk_propertyf(ctx, "#Blend", blend_amnt, blend_amnt, blend_amnt, 0.01f, 0.01f);
}
else {
blend_node->blend_val = nk_propertyf(ctx, "#Blend", 0.0f, blend_node->blend_val, 1.0f, 0.01f, 0.01f);
blend_amnt = blend_node->blend_val;
}
if(node->inputs[0].is_connected && node->inputs[1].is_connected) {
blend_node->output_val.r = blend_node->input_val[0].r * (1.0f-blend_amnt) + blend_node->input_val[1].r * blend_amnt;
blend_node->output_val.g = blend_node->input_val[0].g * (1.0f-blend_amnt) + blend_node->input_val[1].g * blend_amnt;
blend_node->output_val.b = blend_node->input_val[0].b * (1.0f-blend_amnt) + blend_node->input_val[1].b * blend_amnt;
blend_node->output_val.a = blend_node->input_val[0].a * (1.0f-blend_amnt) + blend_node->input_val[1].a * blend_amnt;
}
else {
blend_node->output_val = blank;
}
}
void node_blend_create(struct node_editor *editor, struct nk_vec2 position) {
struct node_type_blend* blend_node = (struct node_type_blend*)node_editor_add(editor, sizeof(struct node_type_blend), "Blend", nk_rect(position.x, position.y, 180, 130), 3, 1);
if (blend_node) {
const struct nk_colorf blank = {0.0f, 0.0f, 0.0f, 0.0f};
for (int i = 0; i < (int)NK_LEN(blend_node->input_val); i++)
blend_node->node.inputs[i].pin_type = type_color;
blend_node->node.outputs[0].pin_type = type_color;
// blend_node->node.pin_spacing.in_padding_y = 42.0f;
// blend_node->node.pin_spacing.in_spacing_y = 29.0f;
for (int i = 0; i < (int)NK_LEN(blend_node->input_val); i++)
blend_node->input_val[i] = blank;
blend_node->output_val = blank;
blend_node->blend_val = 0.5f;
blend_node->node.display_func = node_blend_display;
blend_node->node.eval_func = (void*(*)(struct node*, int)) node_blend_eval;
}
}
/* ================================= */
#define NK_RGB3(r,g,b) {r,g,b,255}
#define BG_COLOR ((struct nk_color){60,60,60,192}) // nk_rgba(0,0,0,192)
static
struct editor_node_style {
int pin_type;
const char *shape;
struct nk_color color_idle;
struct nk_color color_hover;
} styles[] = {
// order matters:
{ type_flow, "triangle_right", NK_RGB3(200,200,200), NK_RGB3(255,255,255) }, // if .num_links == 0
{ type_int, "circle", NK_RGB3(33,227,175), NK_RGB3(135,239,195) },
{ type_float, "circle", NK_RGB3(156,253,65), NK_RGB3(144,225,137) },
{ type_block, "circle", NK_RGB3(6,165,239), NK_RGB3(137,196,247) },
{ type_texture, "circle", NK_RGB3(148,0,0), NK_RGB3(183,137,137) },
{ type_image, "circle", NK_RGB3(200,130,255), NK_RGB3(220,170,255) },
{ type_color, "circle", NK_RGB3(252,200,35), NK_RGB3(255,217,140) },
};
#define COLOR_FLOW_HI styles[type_flow].color_hover
#define COLOR_FLOW_LO styles[type_flow].color_idle
#define GRID_SIZE 64.0f
#define GRID_COLOR ((struct nk_color)NK_RGB3(80,80,120))
#define GRID_THICKNESS 1.0f
// 4 colors: top-left, top-right, bottom-right, bottom-left
#define GRID_BG_COLORS ((struct nk_color){30,30,30,255}), ((struct nk_color){40,20,0,255}), ((struct nk_color){30,30,30,255}), ((struct nk_color){20,30,40,255})
#define LINK_THICKNESS 1.0f
#define LINK_DRAW(POINT_A,POINT_B,COLOR) do { \
vec2 a = (POINT_A); \
vec2 b = (POINT_B); \
nk_stroke_line(canvas, a.x, a.y, b.x, b.y, LINK_THICKNESS, COLOR); \
} while(0)
#undef LINK_DRAW
#define LINK_DRAW(POINT_A,POINT_B,COLOR) do { \
vec2 a = (POINT_A); \
vec2 b = (POINT_B); \
nk_stroke_curve(canvas, a.x, a.y, a.x+50, a.y, b.x-50, b.y, b.x, b.y, LINK_THICKNESS, COLOR); \
} while(0)
#undef LINK_DRAW
#define LINK_DRAW(POINT_A,POINT_B,COLOR) do { \
vec2 a = (POINT_A); \
vec2 b = (POINT_B); \
float dist2 = len2( sub2( ptr2(&b.x), ptr2(&a.x) ) ); \
vec2 mid_a = mix2( ptr2(&a.x), ptr2(&b.x), 0.25 ); mid_a.y += dist2/2; \
vec2 mid_b = mix2( ptr2(&a.x), ptr2(&b.x), 0.75 ); mid_b.y += dist2/3; \
nk_stroke_curve(canvas, a.x, a.y, mid_a.x, mid_a.y, mid_b.x, mid_b.y, b.x, b.y, LINK_THICKNESS, COLOR); \
} while(0)
#define PIN_RADIUS 12
#define PIN_THICKNESS 1.0f
#define PIN_DRAW(PIN_ADDR,POINT,RADIUS) do { \
circle.x = (POINT).x - (RADIUS) / 2; \
circle.y = (POINT).y - (RADIUS) / 2; \
circle.w = circle.h = (RADIUS); \
struct nk_color color = node_get_type_color((PIN_ADDR).pin_type); \
if((PIN_ADDR).is_connected) \
nk_fill_circle(canvas, circle, color); \
else \
nk_stroke_circle(canvas, circle, PIN_THICKNESS, color); \
} while(0)
static struct nk_color node_get_type_color(unsigned pin_type) {
for( int i = 0; i < type_total; ++i )
if( styles[i].pin_type == pin_type )
return styles[i].color_idle;
return ((struct nk_color)NK_RGB3(255,0,255));
}
static void node_editor_push(struct node_editor *editor, struct node *node) {
if (!editor->begin) {
node->next = NULL;
node->prev = NULL;
editor->begin = node;
editor->end = node;
} else {
node->prev = editor->end;
if (editor->end)
editor->end->next = node;
node->next = NULL;
editor->end = node;
}
}
static void node_editor_pop(struct node_editor *editor, struct node *node) {
if (node->next)
node->next->prev = node->prev;
if (node->prev)
node->prev->next = node->next;
if (editor->end == node)
editor->end = node->prev;
if (editor->begin == node)
editor->begin = node->next;
node->next = NULL;
node->prev = NULL;
}
static struct node* node_editor_find_by_id(struct node_editor *editor, int ID) {
struct node *iter = editor->begin;
while (iter) {
if (iter->ID == ID)
return iter;
iter = iter->next;
}
return NULL;
}
static struct node_link* node_editor_find_link_by_output(struct node_editor *editor, struct node *output_node, int node_input_connector) {
for( int i = 0; i < editor->link_count; i++ ) {
if (editor->links[i].output_node == output_node &&
editor->links[i].output_pin == node_input_connector &&
editor->links[i].is_active == nk_true) {
return &editor->links[i];
}
}
return NULL;
}
static struct node_link* node_editor_find_link_by_input(struct node_editor *editor, struct node *input_node, int node_output_connector) {
for( int i = 0; i < editor->link_count; i++ ) {
if (editor->links[i].input_node == input_node &&
editor->links[i].input_pin == node_output_connector &&
editor->links[i].is_active == nk_true) {
return &editor->links[i];
}
}
return NULL;
}
static void node_editor_delete_link(struct node_link *link) {
link->is_active = nk_false;
link->input_node->outputs[link->input_pin].is_connected = nk_false;
link->output_node->inputs[link->output_pin].is_connected = nk_false;
}
struct node* node_editor_add(struct node_editor *editor, size_t nodeSize, const char *name, struct nk_rect bounds, int in_count, int out_count) {
static int IDs = 0;
struct node *node = NULL;
if ((nk_size)editor->node_count < NK_LEN(editor->node_buf)) {
/* node_buf has unused pins */
node = MALLOC(nodeSize);
editor->node_buf[editor->node_count++] = node;
node->ID = IDs++;
} else {
/* check for freed up pins in node_buf */
for (int i = 0; i < editor->node_count; i++) {
if (editor->node_buf[i] == NULL) {
node = MALLOC(nodeSize);
editor->node_buf[i] = node;
node->ID = i;
break;
}
}
}
if (node == NULL) {
puts("Node creation failed");
return NULL;
}
node->bounds = bounds;
node->input_count = in_count;
node->output_count = out_count;
node->inputs = MALLOC(node->input_count * sizeof(struct node_pin));
node->outputs = MALLOC(node->output_count * sizeof(struct node_pin));
for (int i = 0; i < node->input_count; i++) {
node->inputs[i].is_connected = nk_false;
node->inputs[i].pin_type = type_float; /* default pin type */
}
for (int i = 0; i < node->output_count; i++) {
node->outputs[i].is_connected = nk_false;
node->outputs[i].pin_type = type_float; /* default pin type */
}
/* default pin spacing */
node->pin_spacing.in_padding_x = 2;
node->pin_spacing.in_padding_y = 32 + 25/2 + 6; // titlebar height + next half row + adjust
node->pin_spacing.in_spacing_y = 25; // row height+border
node->pin_spacing.out_padding_x = 3;
node->pin_spacing.out_padding_y = 32 + 25/2 + 6; // titlebar height + next half row + adjust
node->pin_spacing.out_spacing_y = 25; // row height+border
strcpy(node->name, name);
node_editor_push(editor, node);
return node;
}
void *node_editor_eval_connected(struct node* node, int input_pin_number) {
NK_ASSERT(node->inputs[input_pin_number].is_connected);
return node->inputs[input_pin_number].connected_node->eval_func(node->inputs[input_pin_number].connected_node, node->inputs[input_pin_number].connected_pin);
}
static void node_editor_link(struct node_editor *editor, struct node *in_node, int in_pin, struct node *out_node, int out_pin) {
/* Confusingly, in and out nodes/pins here refer to the inputs and outputs OF THE LINK ITSELF, not the nodes */
struct node_link *link = NULL;
if ((nk_size)editor->link_count < NK_LEN(editor->links)) {
link = &editor->links[editor->link_count++];
} else {
for (int i = 0; i < (int)NK_LEN(editor->links); i++)
{
if (editor->links[i].is_active == nk_false) {
link = &editor->links[i];
break;
}
}
}
if (link) {
out_node->inputs[out_pin].is_connected = nk_true;
in_node->outputs[in_pin].is_connected = nk_true;
out_node->inputs[out_pin].connected_node = in_node;
out_node->inputs[out_pin].connected_pin = in_pin;
link->input_node = in_node;
link->input_pin = in_pin;
link->output_node = out_node;
link->output_pin = out_pin;
link->is_active = nk_true;
} else {
puts("Too many links");
}
}
static void node_editor_init(struct node_editor *editor) {
if (editor->initialized) return;
struct nk_rect total_space = nk_window_get_content_region(ui_ctx);
struct nk_vec2 output_node_position = { total_space.w*2/3, total_space.h/3 };
struct nk_vec2 color_node_position = { total_space.w*1/4, total_space.h/3 };
memset(editor, 0, sizeof(*editor));
editor->output_node = node_output_create(editor, output_node_position);
node_color_create(editor, color_node_position);
editor->show_grid = nk_true;
editor->initialized = 1;
}
static int node_editor(struct node_editor *editor) {
int n = 0;
struct nk_rect total_space;
const struct nk_input *in = &ui_ctx->input;
struct nk_command_buffer *canvas = nk_window_get_canvas(ui_ctx);
struct node *updated = 0;
node_editor_init(editor);
{
/* allocate complete window space */
total_space = nk_window_get_content_region(ui_ctx);
nk_layout_space_begin(ui_ctx, NK_STATIC, total_space.h, editor->node_count);
{
struct node *it = editor->begin;
struct nk_rect size = nk_layout_space_bounds(ui_ctx);
struct nk_panel *nodePanel = 0;
//nk_fill_rect(canvas, size, 0/*rounding*/, ((struct nk_color){30,30,30,255})); // 20,30,40,255
nk_fill_rect_multi_color(canvas, size, GRID_BG_COLORS);
if (editor->show_grid) {
/* display grid */
for (float x = (float)fmod(size.x - editor->scrolling.x, GRID_SIZE); x < size.w; x += GRID_SIZE)
nk_stroke_line(canvas, x+size.x, size.y, x+size.x, size.y+size.h, GRID_THICKNESS, GRID_COLOR);
for (float y = (float)fmod(size.y - editor->scrolling.y, GRID_SIZE); y < size.h; y += GRID_SIZE)
nk_stroke_line(canvas, size.x, y+size.y, size.x+size.w, y+size.y, GRID_THICKNESS, GRID_COLOR);
}
/* execute each node as a movable group */
/* loop through nodes */
while (it) {
/* Output node window should not have a close button */
nk_flags nodePanel_flags = NK_WINDOW_MOVABLE|NK_WINDOW_NO_SCROLLBAR|NK_WINDOW_BORDER|NK_WINDOW_TITLE;
if (it != editor->output_node)
nodePanel_flags |= NK_WINDOW_CLOSABLE;
/* calculate scrolled node window position and size */
nk_layout_space_push(ui_ctx, nk_rect(it->bounds.x - editor->scrolling.x,
it->bounds.y - editor->scrolling.y, it->bounds.w, it->bounds.h));
/* execute node window */
char *name = va(" " ICON_MD_MENU " %s",it->name); //< @r-lyeh added some spacing+icon because of our UI customizations
struct nk_color bak = ui_ctx->style.window.fixed_background.data.color;
ui_ctx->style.window.fixed_background.data.color = BG_COLOR;
if (nk_group_begin(ui_ctx, name, nodePanel_flags))
{
/* always have last selected node on top */
nodePanel = nk_window_get_panel(ui_ctx);
if (nk_input_mouse_clicked(in, NK_BUTTON_LEFT, nodePanel->bounds) &&
(!(it->prev && nk_input_mouse_clicked(in, NK_BUTTON_LEFT,
nk_layout_space_rect_to_screen(ui_ctx, nodePanel->bounds)))) &&
editor->end != it)
{
updated = it;
}
if ((nodePanel->flags & NK_WINDOW_HIDDEN)) /* Node close button has been clicked */
{
/* Delete node */
struct node_link *link_remove;
node_editor_pop(editor, it);
for (int n = 0; n < it->input_count; n++) {
if ((link_remove = node_editor_find_link_by_output(editor, it, n)))
{
node_editor_delete_link(link_remove);
}
}
for (int n = 0; n < it -> output_count; n++) {
while((link_remove = node_editor_find_link_by_input(editor, it, n)))
{
node_editor_delete_link(link_remove);
}
}
NK_ASSERT(editor->node_buf[it->ID] == it);
editor->node_buf[it->ID] = NULL;
FREE(it->inputs);
FREE(it->outputs);
FREE(it);
}
else {
/* ================= NODE CONTENT ===================== */
it->display_func(ui_ctx, it);
/* ==================================================== */
}
nk_group_end(ui_ctx);
}
ui_ctx->style.window.fixed_background.data.color = bak;
if (!(nodePanel->flags & NK_WINDOW_HIDDEN))
{
/* node pin and linking */
struct nk_rect bounds;
bounds = nk_layout_space_rect_to_local(ui_ctx, nodePanel->bounds);
bounds.x += editor->scrolling.x;
bounds.y += editor->scrolling.y;
it->bounds = bounds;
/* output pins */
for (int n = 0; n < it->output_count; ++n) {
struct nk_rect circle;
struct nk_vec2 pt = {nodePanel->bounds.x, nodePanel->bounds.y};
pt.x += nodePanel->bounds.w - PIN_RADIUS / 2 + it->pin_spacing.out_padding_x;
pt.y += it->pin_spacing.out_padding_y + it->pin_spacing.out_spacing_y * (n);
PIN_DRAW(it->outputs[n],pt,PIN_RADIUS);
/* start linking process */
/* set linking active */
if (nk_input_has_mouse_click_down_in_rect(in, NK_BUTTON_LEFT, circle, nk_true)) {
editor->linking.active = nk_true;
editor->linking.node = it;
editor->linking.input_id = it->ID;
editor->linking.input_pin = n;
}
/* draw link being dragged (from linked pin to mouse position) */
if (editor->linking.active && editor->linking.node == it &&
editor->linking.input_pin == n) {
LINK_DRAW(vec2(circle.x+3,circle.y+3),ptr2(&in->mouse.pos.x),COLOR_FLOW_HI);
}
}
/* input pins */
for (int n = 0; n < it->input_count; ++n) {
struct nk_rect circle;
struct nk_vec2 pt = {nodePanel->bounds.x, nodePanel->bounds.y};
pt.x += it->pin_spacing.in_padding_x;
pt.y += it->pin_spacing.in_padding_y + it->pin_spacing.in_spacing_y * (n);
PIN_DRAW(it->inputs[n],pt,PIN_RADIUS);
/* Detach link */
if (nk_input_has_mouse_click_down_in_rect(in, NK_BUTTON_LEFT, circle, nk_true) &&
editor->linking.active == nk_false &&
it->inputs[n].is_connected == nk_true) {
struct node_link *node_relink = node_editor_find_link_by_output(editor, it, n);
editor->linking.active = nk_true;
editor->linking.node = node_relink->input_node;
editor->linking.input_id = node_relink->input_node->ID;
editor->linking.input_pin = node_relink->input_pin;
node_editor_delete_link(node_relink);
}
/* Create link */
if (nk_input_is_mouse_released(in, NK_BUTTON_LEFT) &&
nk_input_is_mouse_hovering_rect(in, circle) &&
editor->linking.active &&
editor->linking.node != it &&
it->inputs[n].pin_type == editor->linking.node->outputs[editor->linking.input_pin].pin_type &&
it->inputs[n].is_connected != nk_true) {
editor->linking.active = nk_false;
node_editor_link(editor, editor->linking.node,
editor->linking.input_pin, it, n);
}
}
}
it = it->next;
}
/* reset (output) linking connection */
if (editor->linking.active && (!!input(KEY_LCTRL) || !!input(KEY_RCTRL) || nk_input_is_mouse_released(in, NK_BUTTON_LEFT))) {
editor->linking.active = nk_false;
editor->linking.node = NULL;
}
/* draw each static link */
for (int n = 0; n < editor->link_count; ++n) {
struct node_link *link = &editor->links[n];
if (link->is_active == nk_true){
struct node *ni = link->input_node;
struct node *no = link->output_node;
struct nk_vec2 l0 = nk_layout_space_to_screen(ui_ctx, nk_vec2(ni->bounds.x + ni->bounds.w + ni->pin_spacing.out_padding_x, 3.0f + ni->bounds.y + ni->pin_spacing.out_padding_y + ni->pin_spacing.out_spacing_y * (link->input_pin)));
struct nk_vec2 l1 = nk_layout_space_to_screen(ui_ctx, nk_vec2(no->bounds.x + no->pin_spacing.in_padding_x, 3.0f + no->bounds.y + no->pin_spacing.in_padding_y + no->pin_spacing.in_spacing_y * (link->output_pin)));
l0.x -= editor->scrolling.x;
l0.y -= editor->scrolling.y;
l1.x -= editor->scrolling.x;
l1.y -= editor->scrolling.y;
struct nk_color color = node_get_type_color(no->inputs[link->output_pin].pin_type);
LINK_DRAW(ptr2(&l0.x), ptr2(&l1.x), color);
}
}
if (updated) {
/* reshuffle nodes to have least recently selected node on top */
node_editor_pop(editor, updated);
node_editor_push(editor, updated);
}
/* node selection */
if (nk_input_mouse_clicked(in, NK_BUTTON_LEFT, nk_layout_space_bounds(ui_ctx))) {
it = editor->begin;
editor->selected = NULL;
editor->bounds = nk_rect(in->mouse.pos.x, in->mouse.pos.y, 100, 200);
while (it) {
struct nk_rect b = nk_layout_space_rect_to_screen(ui_ctx, it->bounds);
b.x -= editor->scrolling.x;
b.y -= editor->scrolling.y;
if (nk_input_is_mouse_hovering_rect(in, b))
editor->selected = it;
it = it->next;
}
}
/* contextual menu */
if (nk_contextual_begin(ui_ctx, 0, nk_vec2(150, 220), nk_window_get_bounds(ui_ctx))) {
struct nk_vec2 wincoords = { in->mouse.pos.x-total_space.x-50, in->mouse.pos.y-total_space.y-32 };
#if 1
static char *filter = 0;
static int do_filter = 0;
if( input_down(KEY_F) ) if( input(KEY_LCTRL) || input(KEY_RCTRL) ) do_filter ^= 1;
int choice = ui_toolbar(ICON_MD_SEARCH ";");
if( choice == 1 ) do_filter = 1;
if( do_filter ) {
ui_string(ICON_MD_CLOSE " Filter " ICON_MD_SEARCH, &filter);
if( ui_label_icon_clicked_L.x > 0 && ui_label_icon_clicked_L.x <= 24 ) { // if clicked on CANCEL icon (1st icon)
do_filter = 0;
}
} else {
if( filter ) filter[0] = '\0';
}
char *filter_mask = filter && filter[0] ? va("*%s*", filter) : "*";
#endif
#define ui_label_filtered(lbl) (strmatchi(lbl,filter_mask) && ui_label(lbl))
int close = 0;
if (ui_label_filtered("=Add Color node")) close=1,node_color_create(editor, wincoords);
if (ui_label_filtered("=Add Float node")) close=1,node_float_create(editor, wincoords);
if (ui_label_filtered("=Add Blend Node")) close=1,node_blend_create(editor, wincoords);
if (ui_label_filtered(editor->show_grid ? "=Hide Grid" : "=Show Grid"))
close=1,editor->show_grid = !editor->show_grid;
if(close) do_filter = 0, (filter ? filter[0] = '\0' : '\0'), nk_contextual_close(ui_ctx);
nk_contextual_end(ui_ctx);
}
}
nk_layout_space_end(ui_ctx);
/* window content scrolling */
if (nk_input_is_mouse_hovering_rect(in, nk_window_get_bounds(ui_ctx)) &&
nk_input_is_mouse_down(in, NK_BUTTON_MIDDLE)) {
editor->scrolling.x += in->mouse.delta.x;
editor->scrolling.y += in->mouse.delta.y;
}
}
return !nk_window_is_closed(ui_ctx, "NodeEdit");
}
int editor_nodes(int window_mode) {
window_mode = EDITOR_WINDOW; // force window
if( editor_begin(NODES_TITLE, window_mode) ) {
static struct node_editor nodeEditor = {0};
node_editor(&nodeEditor);
editor_end(window_mode);
}
return 0;
}
AUTORUN {
array_push(editor.subeditors, editor_nodes);
}

View File

@ -11,8 +11,14 @@ w=0.276585
h=0.946236
visible=1
[Scene 󰙅]
x=-0.000000
x=0.000000
y=0.052117
w=0.209707
h=0.946108
visible=1
[Nodes 󱁉]
x=0.000916
y=0.053746
w=0.999084
h=0.944625
visible=0

View File

@ -21,6 +21,12 @@ pushd tools\editor
echo Skipping %%f
)
)
for %%f in (".\fwk*") do (
set "filename=%%~nf"
set "newname=v4k!filename:fwk=!%%~xf"
echo Renaming "%%f" to "!newname!"
copy "%%f" "!newname!"
)
echo All done.
endlocal
popd