fix CRLF
parent
b14c31b4e0
commit
b841064174
|
@ -1,25 +1,25 @@
|
|||
default
|
||||
.art*.zip
|
||||
__pycache__
|
||||
.vs
|
||||
_deploy
|
||||
tools/steamcmd
|
||||
!tools/steamcmd/steamcmd.exe
|
||||
_builder_output
|
||||
emsdk
|
||||
demos/html5/*.data
|
||||
demos/html5/*.js
|
||||
demos/html5/*.html
|
||||
demos/ports/*/*.exe
|
||||
demos/ports/doom/.doomrc
|
||||
*.sublime-workspace
|
||||
engine/v4k.html
|
||||
*.exe.manifest
|
||||
*.log
|
||||
v4k.osx
|
||||
libv4k.*
|
||||
*.dSYM
|
||||
.DS_store
|
||||
*.raddbg
|
||||
plugins
|
||||
tools/assimp-vc*-mt.lib
|
||||
default
|
||||
.art*.zip
|
||||
__pycache__
|
||||
.vs
|
||||
_deploy
|
||||
tools/steamcmd
|
||||
!tools/steamcmd/steamcmd.exe
|
||||
_builder_output
|
||||
emsdk
|
||||
demos/html5/*.data
|
||||
demos/html5/*.js
|
||||
demos/html5/*.html
|
||||
demos/ports/*/*.exe
|
||||
demos/ports/doom/.doomrc
|
||||
*.sublime-workspace
|
||||
engine/v4k.html
|
||||
*.exe.manifest
|
||||
*.log
|
||||
v4k.osx
|
||||
libv4k.*
|
||||
*.dSYM
|
||||
.DS_store
|
||||
*.raddbg
|
||||
plugins
|
||||
tools/assimp-vc*-mt.lib
|
||||
|
|
4382
bind/v4k.lua
4382
bind/v4k.lua
File diff suppressed because it is too large
Load Diff
|
@ -1,134 +1,134 @@
|
|||
// material demo
|
||||
// - rlyeh, public domain
|
||||
//
|
||||
// @todo: object_print(obj, "");
|
||||
|
||||
#include "v4k.h"
|
||||
|
||||
|
||||
const char *skyboxes[][2] = { // reflection, env, metadata
|
||||
{"hdr/Tokyo_BigSight_1k.hdr","hdr/Tokyo_BigSight_Env.hdr"},
|
||||
{"hdr/graffiti_shelter_4k.hdr","hdr/graffiti_shelter_Env.hdr"},
|
||||
{"hdr/music_hall_01_4k.hdr","hdr/music_hall_01_Env.hdr"},
|
||||
{"hdr/the_sky_is_on_fire_2k.hdr","hdr/the_sky_is_on_fire_Env.hdr"},
|
||||
{"hdr/GCanyon_C_YumaPoint_1k.hdr","hdr/GCanyon_C_YumaPoint_Env.hdr"},
|
||||
{"hdr/Factory_Catwalk_1k.hdr","hdr/Factory_Catwalk_Env.hdr"},
|
||||
{"hdr/MonValley_G_DirtRoad_1k.hdr","hdr/MonValley_G_DirtRoad_Env.hdr"},
|
||||
{"hdr/Shiodome_Stairs_1k.hdr","hdr/Shiodome_Stairs_Env.hdr"},
|
||||
{"hdr/mesto.hdr","hdr/mesto_Env.hdr"},
|
||||
};
|
||||
|
||||
int main() {
|
||||
// create the window
|
||||
window_create( 0.75f, WINDOW_MSAA8 );
|
||||
window_color( GRAY );
|
||||
|
||||
// create camera
|
||||
camera_t cam = camera();
|
||||
|
||||
// fx: load all post fx files in all subdirs.
|
||||
fx_load("fx**.fs");
|
||||
|
||||
// load video, RGB texture, no audio
|
||||
video_t *v = video( "pexels-pachon-in-motion-17486489.mp4", VIDEO_RGB | VIDEO_NO_AUDIO | VIDEO_LOOP ); video_seek(v, 30);
|
||||
// load texture
|
||||
texture_t t1 = texture("kgirl/g01_texture.png", TEXTURE_SRGB);
|
||||
texture_t t2 = texture("matcaps/material3", TEXTURE_SRGB);
|
||||
// load model
|
||||
model_t m1 = model("suzanne.obj", MODEL_NO_ANIMATIONS);
|
||||
model_t m2 = model("suzanne.obj", MODEL_NO_ANIMATIONS|MODEL_MATCAPS);
|
||||
// model_t m3 = model("damagedhelmet.gltf", MODEL_NO_ANIMATIONS|MODEL_PBR);
|
||||
model_t m3 = model("Scutum_low.fbx", MODEL_NO_ANIMATIONS|MODEL_PBR);
|
||||
// model_t m4 = model("avp/scene.gltf", MODEL_NO_ANIMATIONS|MODEL_PBR);
|
||||
// model_t m3 = model("Cerberus_LP.FBX", MODEL_NO_ANIMATIONS|MODEL_PBR);
|
||||
|
||||
// spawn object1 (diffuse)
|
||||
object_t* obj1 = scene_spawn();
|
||||
object_model(obj1, m1);
|
||||
object_diffuse(obj1, t1);
|
||||
object_scale(obj1, vec3(3,3,3));
|
||||
object_move(obj1, vec3(-10+5*0,0,-10));
|
||||
object_pivot(obj1, vec3(0,90,0));
|
||||
|
||||
// spawn object2 (matcap)
|
||||
object_t* obj2 = scene_spawn();
|
||||
object_model(obj2, m2);
|
||||
object_diffuse(obj2, t2);
|
||||
object_scale(obj2, vec3(3,3,3));
|
||||
object_move(obj2, vec3(-10+5*2,0,-10));
|
||||
object_pivot(obj2, vec3(0,90,0));
|
||||
|
||||
// spawn object3 (video)
|
||||
object_t* obj3 = scene_spawn();
|
||||
object_model(obj3, m1);
|
||||
object_diffuse(obj3, video_textures(v)[0]);
|
||||
object_scale(obj3, vec3(3,3,3));
|
||||
object_move(obj3, vec3(-10+5*1,0,-10));
|
||||
object_pivot(obj3, vec3(0,90,0));
|
||||
|
||||
// spawn object4 (pbr)
|
||||
object_t* obj4 = scene_spawn();
|
||||
object_model(obj4, m3);
|
||||
object_scale(obj4, vec3(3,3,3));
|
||||
object_move(obj4, vec3(-10+6*3,0,-10));
|
||||
object_pivot(obj4, vec3(0,0,90));
|
||||
|
||||
// spawn object5 (pbr)
|
||||
// object_t* obj5 = scene_spawn();
|
||||
// object_model(obj5, m4);
|
||||
// object_scale(obj5, vec3(3,3,3));
|
||||
// object_move(obj5, vec3(-10+6*3,0,-10));
|
||||
// object_pivot(obj5, vec3(0,0,90));
|
||||
|
||||
// create point light
|
||||
scene_spawn_light(); // sun
|
||||
light_t* l = scene_spawn_light();
|
||||
light_type(l, LIGHT_POINT);
|
||||
l->diffuse = vec3(1,0,0);
|
||||
|
||||
// load skybox
|
||||
scene_get_active()->skybox = skybox_pbr(skyboxes[0][0], skyboxes[0][0], skyboxes[0][1]);
|
||||
|
||||
|
||||
while(window_swap() && !input(KEY_ESC)) {
|
||||
// draw environment
|
||||
ddraw_grid(0);
|
||||
|
||||
// update video
|
||||
video_decode( v );
|
||||
|
||||
// update light position
|
||||
light_teleport(l, cam.position);
|
||||
|
||||
// draw scene
|
||||
fx_begin();
|
||||
scene_render(SCENE_FOREGROUND|SCENE_BACKGROUND|SCENE_UPDATE_SH_COEF);
|
||||
fx_end();
|
||||
|
||||
// fps camera
|
||||
bool active = ui_active() || ui_hover() || gizmo_active() ? false : input(MOUSE_L) || input(MOUSE_M) || input(MOUSE_R);
|
||||
if( active ) cam.speed = clampf(cam.speed + input_diff(MOUSE_W) / 10, 0.05f, 5.0f);
|
||||
vec2 mouselook = scale2(vec2(input_diff(MOUSE_X), -input_diff(MOUSE_Y)), 0.2f * active);
|
||||
vec3 wasdec = scale3(vec3(input(KEY_D)-input(KEY_A),input(KEY_E)-input(KEY_C),input(KEY_W)-input(KEY_S)), cam.speed);
|
||||
camera_moveby(&cam, wasdec);
|
||||
camera_fps(&cam, mouselook.x,mouselook.y);
|
||||
window_cursor( !active );
|
||||
|
||||
if (ui_panel("FXs", 0)) {
|
||||
ui_fxs();
|
||||
ui_panel_end();
|
||||
}
|
||||
|
||||
if( ui_panel( "Viewer", 0 ) ) {
|
||||
for( int i = 0; i < countof(skyboxes); i++ ) {
|
||||
const char *filename = skyboxes[i][0];
|
||||
// bool selected = !strcmp(g_skybox.reflection->filename, file_name(filename));
|
||||
bool selected = false;
|
||||
if( ui_bool( filename, &selected ) ) {
|
||||
scene_get_active()->skybox = skybox_pbr(skyboxes[i][0], skyboxes[i][0], skyboxes[i][1]);
|
||||
}
|
||||
}
|
||||
ui_panel_end();
|
||||
}
|
||||
}
|
||||
}
|
||||
// material demo
|
||||
// - rlyeh, public domain
|
||||
//
|
||||
// @todo: object_print(obj, "");
|
||||
|
||||
#include "v4k.h"
|
||||
|
||||
|
||||
const char *skyboxes[][2] = { // reflection, env, metadata
|
||||
{"hdr/Tokyo_BigSight_1k.hdr","hdr/Tokyo_BigSight_Env.hdr"},
|
||||
{"hdr/graffiti_shelter_4k.hdr","hdr/graffiti_shelter_Env.hdr"},
|
||||
{"hdr/music_hall_01_4k.hdr","hdr/music_hall_01_Env.hdr"},
|
||||
{"hdr/the_sky_is_on_fire_2k.hdr","hdr/the_sky_is_on_fire_Env.hdr"},
|
||||
{"hdr/GCanyon_C_YumaPoint_1k.hdr","hdr/GCanyon_C_YumaPoint_Env.hdr"},
|
||||
{"hdr/Factory_Catwalk_1k.hdr","hdr/Factory_Catwalk_Env.hdr"},
|
||||
{"hdr/MonValley_G_DirtRoad_1k.hdr","hdr/MonValley_G_DirtRoad_Env.hdr"},
|
||||
{"hdr/Shiodome_Stairs_1k.hdr","hdr/Shiodome_Stairs_Env.hdr"},
|
||||
{"hdr/mesto.hdr","hdr/mesto_Env.hdr"},
|
||||
};
|
||||
|
||||
int main() {
|
||||
// create the window
|
||||
window_create( 0.75f, WINDOW_MSAA8 );
|
||||
window_color( GRAY );
|
||||
|
||||
// create camera
|
||||
camera_t cam = camera();
|
||||
|
||||
// fx: load all post fx files in all subdirs.
|
||||
fx_load("fx**.fs");
|
||||
|
||||
// load video, RGB texture, no audio
|
||||
video_t *v = video( "pexels-pachon-in-motion-17486489.mp4", VIDEO_RGB | VIDEO_NO_AUDIO | VIDEO_LOOP ); video_seek(v, 30);
|
||||
// load texture
|
||||
texture_t t1 = texture("kgirl/g01_texture.png", TEXTURE_SRGB);
|
||||
texture_t t2 = texture("matcaps/material3", TEXTURE_SRGB);
|
||||
// load model
|
||||
model_t m1 = model("suzanne.obj", MODEL_NO_ANIMATIONS);
|
||||
model_t m2 = model("suzanne.obj", MODEL_NO_ANIMATIONS|MODEL_MATCAPS);
|
||||
// model_t m3 = model("damagedhelmet.gltf", MODEL_NO_ANIMATIONS|MODEL_PBR);
|
||||
model_t m3 = model("Scutum_low.fbx", MODEL_NO_ANIMATIONS|MODEL_PBR);
|
||||
// model_t m4 = model("avp/scene.gltf", MODEL_NO_ANIMATIONS|MODEL_PBR);
|
||||
// model_t m3 = model("Cerberus_LP.FBX", MODEL_NO_ANIMATIONS|MODEL_PBR);
|
||||
|
||||
// spawn object1 (diffuse)
|
||||
object_t* obj1 = scene_spawn();
|
||||
object_model(obj1, m1);
|
||||
object_diffuse(obj1, t1);
|
||||
object_scale(obj1, vec3(3,3,3));
|
||||
object_move(obj1, vec3(-10+5*0,0,-10));
|
||||
object_pivot(obj1, vec3(0,90,0));
|
||||
|
||||
// spawn object2 (matcap)
|
||||
object_t* obj2 = scene_spawn();
|
||||
object_model(obj2, m2);
|
||||
object_diffuse(obj2, t2);
|
||||
object_scale(obj2, vec3(3,3,3));
|
||||
object_move(obj2, vec3(-10+5*2,0,-10));
|
||||
object_pivot(obj2, vec3(0,90,0));
|
||||
|
||||
// spawn object3 (video)
|
||||
object_t* obj3 = scene_spawn();
|
||||
object_model(obj3, m1);
|
||||
object_diffuse(obj3, video_textures(v)[0]);
|
||||
object_scale(obj3, vec3(3,3,3));
|
||||
object_move(obj3, vec3(-10+5*1,0,-10));
|
||||
object_pivot(obj3, vec3(0,90,0));
|
||||
|
||||
// spawn object4 (pbr)
|
||||
object_t* obj4 = scene_spawn();
|
||||
object_model(obj4, m3);
|
||||
object_scale(obj4, vec3(3,3,3));
|
||||
object_move(obj4, vec3(-10+6*3,0,-10));
|
||||
object_pivot(obj4, vec3(0,0,90));
|
||||
|
||||
// spawn object5 (pbr)
|
||||
// object_t* obj5 = scene_spawn();
|
||||
// object_model(obj5, m4);
|
||||
// object_scale(obj5, vec3(3,3,3));
|
||||
// object_move(obj5, vec3(-10+6*3,0,-10));
|
||||
// object_pivot(obj5, vec3(0,0,90));
|
||||
|
||||
// create point light
|
||||
scene_spawn_light(); // sun
|
||||
light_t* l = scene_spawn_light();
|
||||
light_type(l, LIGHT_POINT);
|
||||
l->diffuse = vec3(1,0,0);
|
||||
|
||||
// load skybox
|
||||
scene_get_active()->skybox = skybox_pbr(skyboxes[0][0], skyboxes[0][0], skyboxes[0][1]);
|
||||
|
||||
|
||||
while(window_swap() && !input(KEY_ESC)) {
|
||||
// draw environment
|
||||
ddraw_grid(0);
|
||||
|
||||
// update video
|
||||
video_decode( v );
|
||||
|
||||
// update light position
|
||||
light_teleport(l, cam.position);
|
||||
|
||||
// draw scene
|
||||
fx_begin();
|
||||
scene_render(SCENE_FOREGROUND|SCENE_BACKGROUND|SCENE_UPDATE_SH_COEF);
|
||||
fx_end();
|
||||
|
||||
// fps camera
|
||||
bool active = ui_active() || ui_hover() || gizmo_active() ? false : input(MOUSE_L) || input(MOUSE_M) || input(MOUSE_R);
|
||||
if( active ) cam.speed = clampf(cam.speed + input_diff(MOUSE_W) / 10, 0.05f, 5.0f);
|
||||
vec2 mouselook = scale2(vec2(input_diff(MOUSE_X), -input_diff(MOUSE_Y)), 0.2f * active);
|
||||
vec3 wasdec = scale3(vec3(input(KEY_D)-input(KEY_A),input(KEY_E)-input(KEY_C),input(KEY_W)-input(KEY_S)), cam.speed);
|
||||
camera_moveby(&cam, wasdec);
|
||||
camera_fps(&cam, mouselook.x,mouselook.y);
|
||||
window_cursor( !active );
|
||||
|
||||
if (ui_panel("FXs", 0)) {
|
||||
ui_fxs();
|
||||
ui_panel_end();
|
||||
}
|
||||
|
||||
if( ui_panel( "Viewer", 0 ) ) {
|
||||
for( int i = 0; i < countof(skyboxes); i++ ) {
|
||||
const char *filename = skyboxes[i][0];
|
||||
// bool selected = !strcmp(g_skybox.reflection->filename, file_name(filename));
|
||||
bool selected = false;
|
||||
if( ui_bool( filename, &selected ) ) {
|
||||
scene_get_active()->skybox = skybox_pbr(skyboxes[i][0], skyboxes[i][0], skyboxes[i][1]);
|
||||
}
|
||||
}
|
||||
ui_panel_end();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
158
demos/99-lmap.c
158
demos/99-lmap.c
|
@ -1,79 +1,79 @@
|
|||
#include "v4k.h"
|
||||
|
||||
model_t litm;
|
||||
|
||||
void bakedrawmodel(lightmap_t *lm, model_t *m, float *view, float *proj, void *userdata) {
|
||||
shader_bind(lm->shader);
|
||||
model_render(*m, proj, view, m->pivot, lm->shader);
|
||||
shader_float("u_litboost", 4.0);
|
||||
model_render(litm, proj, view, litm.pivot, lm->shader);
|
||||
}
|
||||
|
||||
void progressupdate(float progress) {
|
||||
static double lastUpdateTime = 0.0;
|
||||
double time = time_ss();
|
||||
if (time - lastUpdateTime > 1.0) {
|
||||
lastUpdateTime = time;
|
||||
PRINTF("progress: %.02f%%", progress*100);
|
||||
}
|
||||
// window_swap();
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
window_create(0.5, 0);
|
||||
window_title(__FILE__);
|
||||
camera_t cam = camera();
|
||||
skybox_t sky = skybox(0, 0); skybox_mie_calc_sh(&sky, 2.0f);
|
||||
model_t mdl = model(option("--model","gazebo.obj"), 0);
|
||||
litm = model("cube.obj", 0);
|
||||
{
|
||||
mat44 lp; scaling44(lp, 0.3, 0.3, 0.3); translate44(lp, 8,4,0);
|
||||
copy44(litm.pivot, lp);
|
||||
}
|
||||
rotate44(mdl.pivot, -90, 1, 0, 0);
|
||||
scale44(mdl.pivot, 4,4,4);
|
||||
shader_bind(mdl.program);
|
||||
shader_vec3v("u_coefficients_sh", 9, sky.cubemap.sh);
|
||||
// shader_bool("u_texmod", 0);
|
||||
|
||||
unsigned char emissive[] = { 255, 180, 0, 255 };
|
||||
texture_t emission = texture_create(1,1,4,emissive,TEXTURE_LINEAR);
|
||||
model_set_texture(litm, emission);
|
||||
|
||||
lightmap_t baker = lightmap(64, 0.01, 100, vec3(0,0,0), 2, 0.01, 0.0);
|
||||
lightmap_setup(&baker, 512, 512);
|
||||
array_push(baker.models, &mdl);
|
||||
|
||||
bool do_bake=0;
|
||||
int b=1;
|
||||
|
||||
while (window_swap() && !input(KEY_ESC)) {
|
||||
bool active = ui_active() || ui_hover() || gizmo_active() ? false : input(MOUSE_L) || input(MOUSE_M) || input(MOUSE_R);
|
||||
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);
|
||||
window_cursor( !active );
|
||||
|
||||
skybox_render(&sky, cam.proj, cam.view);
|
||||
model_render(mdl, cam.proj, cam.view, mdl.pivot, 0);
|
||||
model_render(litm, cam.proj, cam.view, litm.pivot, 0);
|
||||
|
||||
if( ui_panel("Lightmapper", PANEL_OPEN) ) {
|
||||
ui_label2("Freecam", "Mouse + W/A/S/D/E/Q keys");
|
||||
ui_label("Warning " ICON_MD_WARNING "@This will take a few seconds and bake a lightmap illuminated by: The mesh itself (initially black) + A white sky (1.0f, 1.0f, 1.0f)");
|
||||
ui_int("Bounces", &b);
|
||||
if( ui_button(va("Bake %d light bounce(s)", b)) ) {
|
||||
do_bake=1;
|
||||
}
|
||||
ui_panel_end();
|
||||
}
|
||||
|
||||
if (do_bake) {
|
||||
do_bake=0;
|
||||
lightmap_bake(&baker, b, bakedrawmodel, progressupdate, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
#include "v4k.h"
|
||||
|
||||
model_t litm;
|
||||
|
||||
void bakedrawmodel(lightmap_t *lm, model_t *m, float *view, float *proj, void *userdata) {
|
||||
shader_bind(lm->shader);
|
||||
model_render(*m, proj, view, m->pivot, lm->shader);
|
||||
shader_float("u_litboost", 4.0);
|
||||
model_render(litm, proj, view, litm.pivot, lm->shader);
|
||||
}
|
||||
|
||||
void progressupdate(float progress) {
|
||||
static double lastUpdateTime = 0.0;
|
||||
double time = time_ss();
|
||||
if (time - lastUpdateTime > 1.0) {
|
||||
lastUpdateTime = time;
|
||||
PRINTF("progress: %.02f%%", progress*100);
|
||||
}
|
||||
// window_swap();
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
window_create(0.5, 0);
|
||||
window_title(__FILE__);
|
||||
camera_t cam = camera();
|
||||
skybox_t sky = skybox(0, 0); skybox_mie_calc_sh(&sky, 2.0f);
|
||||
model_t mdl = model(option("--model","gazebo.obj"), 0);
|
||||
litm = model("cube.obj", 0);
|
||||
{
|
||||
mat44 lp; scaling44(lp, 0.3, 0.3, 0.3); translate44(lp, 8,4,0);
|
||||
copy44(litm.pivot, lp);
|
||||
}
|
||||
rotate44(mdl.pivot, -90, 1, 0, 0);
|
||||
scale44(mdl.pivot, 4,4,4);
|
||||
shader_bind(mdl.program);
|
||||
shader_vec3v("u_coefficients_sh", 9, sky.cubemap.sh);
|
||||
// shader_bool("u_texmod", 0);
|
||||
|
||||
unsigned char emissive[] = { 255, 180, 0, 255 };
|
||||
texture_t emission = texture_create(1,1,4,emissive,TEXTURE_LINEAR);
|
||||
model_set_texture(litm, emission);
|
||||
|
||||
lightmap_t baker = lightmap(64, 0.01, 100, vec3(0,0,0), 2, 0.01, 0.0);
|
||||
lightmap_setup(&baker, 512, 512);
|
||||
array_push(baker.models, &mdl);
|
||||
|
||||
bool do_bake=0;
|
||||
int b=1;
|
||||
|
||||
while (window_swap() && !input(KEY_ESC)) {
|
||||
bool active = ui_active() || ui_hover() || gizmo_active() ? false : input(MOUSE_L) || input(MOUSE_M) || input(MOUSE_R);
|
||||
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);
|
||||
window_cursor( !active );
|
||||
|
||||
skybox_render(&sky, cam.proj, cam.view);
|
||||
model_render(mdl, cam.proj, cam.view, mdl.pivot, 0);
|
||||
model_render(litm, cam.proj, cam.view, litm.pivot, 0);
|
||||
|
||||
if( ui_panel("Lightmapper", PANEL_OPEN) ) {
|
||||
ui_label2("Freecam", "Mouse + W/A/S/D/E/Q keys");
|
||||
ui_label("Warning " ICON_MD_WARNING "@This will take a few seconds and bake a lightmap illuminated by: The mesh itself (initially black) + A white sky (1.0f, 1.0f, 1.0f)");
|
||||
ui_int("Bounces", &b);
|
||||
if( ui_button(va("Bake %d light bounce(s)", b)) ) {
|
||||
do_bake=1;
|
||||
}
|
||||
ui_panel_end();
|
||||
}
|
||||
|
||||
if (do_bake) {
|
||||
do_bake=0;
|
||||
lightmap_bake(&baker, b, bakedrawmodel, progressupdate, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
frame: 0-50 AlienArmature|Alien_Clapping
|
||||
frame: 51-106 AlienArmature|Alien_Death
|
||||
frame: 107-207 AlienArmature|Alien_Idle
|
||||
frame: 208-308 AlienArmature|Alien_IdleHold
|
||||
frame: 309-334 AlienArmature|Alien_Jump
|
||||
frame: 335-357 AlienArmature|Alien_Punch
|
||||
frame: 358-379 AlienArmature|Alien_Run
|
||||
frame: 380-401 AlienArmature|Alien_RunHold
|
||||
frame: 402-432 AlienArmature|Alien_RunningJump
|
||||
frame: 433-459 AlienArmature|Alien_Sitting
|
||||
frame: 460-475 AlienArmature|Alien_Standing
|
||||
frame: 476-508 AlienArmature|Alien_Swimming
|
||||
frame: 509-534 AlienArmature|Alien_SwordSlash
|
||||
frame: 535-560 AlienArmature|Alien_Walk
|
||||
frame: 0-50 AlienArmature|Alien_Clapping
|
||||
frame: 51-106 AlienArmature|Alien_Death
|
||||
frame: 107-207 AlienArmature|Alien_Idle
|
||||
frame: 208-308 AlienArmature|Alien_IdleHold
|
||||
frame: 309-334 AlienArmature|Alien_Jump
|
||||
frame: 335-357 AlienArmature|Alien_Punch
|
||||
frame: 358-379 AlienArmature|Alien_Run
|
||||
frame: 380-401 AlienArmature|Alien_RunHold
|
||||
frame: 402-432 AlienArmature|Alien_RunningJump
|
||||
frame: 433-459 AlienArmature|Alien_Sitting
|
||||
frame: 460-475 AlienArmature|Alien_Standing
|
||||
frame: 476-508 AlienArmature|Alien_Swimming
|
||||
frame: 509-534 AlienArmature|Alien_SwordSlash
|
||||
frame: 535-560 AlienArmature|Alien_Walk
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
frame: 0-50 AlienArmature|Alien_Clapping
|
||||
frame: 51-106 AlienArmature|Alien_Death
|
||||
frame: 107-207 AlienArmature|Alien_Idle
|
||||
frame: 208-308 AlienArmature|Alien_IdleHold
|
||||
frame: 309-334 AlienArmature|Alien_Jump
|
||||
frame: 335-357 AlienArmature|Alien_Punch
|
||||
frame: 358-379 AlienArmature|Alien_Run
|
||||
frame: 380-401 AlienArmature|Alien_RunHold
|
||||
frame: 402-432 AlienArmature|Alien_RunningJump
|
||||
frame: 433-459 AlienArmature|Alien_Sitting
|
||||
frame: 460-475 AlienArmature|Alien_Standing
|
||||
frame: 476-508 AlienArmature|Alien_Swimming
|
||||
frame: 509-534 AlienArmature|Alien_SwordSlash
|
||||
frame: 535-560 AlienArmature|Alien_Walk
|
||||
frame: 0-50 AlienArmature|Alien_Clapping
|
||||
frame: 51-106 AlienArmature|Alien_Death
|
||||
frame: 107-207 AlienArmature|Alien_Idle
|
||||
frame: 208-308 AlienArmature|Alien_IdleHold
|
||||
frame: 309-334 AlienArmature|Alien_Jump
|
||||
frame: 335-357 AlienArmature|Alien_Punch
|
||||
frame: 358-379 AlienArmature|Alien_Run
|
||||
frame: 380-401 AlienArmature|Alien_RunHold
|
||||
frame: 402-432 AlienArmature|Alien_RunningJump
|
||||
frame: 433-459 AlienArmature|Alien_Sitting
|
||||
frame: 460-475 AlienArmature|Alien_Standing
|
||||
frame: 476-508 AlienArmature|Alien_Swimming
|
||||
frame: 509-534 AlienArmature|Alien_SwordSlash
|
||||
frame: 535-560 AlienArmature|Alien_Walk
|
||||
|
|
|
@ -1 +1 @@
|
|||
frame: 0-506 Take 001
|
||||
frame: 0-506 Take 001
|
||||
|
|
|
@ -1,40 +1,40 @@
|
|||
frame: 0-100 Idle
|
||||
frame: 101-125 Dance
|
||||
frame: 126-169 Death
|
||||
frame: 170-215 Hello
|
||||
frame: 216-228 HitRecieve_1
|
||||
frame: 229-241 HitRecieve_2
|
||||
frame: 242-268 Jump
|
||||
frame: 269-310 No
|
||||
frame: 311-353 Pickup
|
||||
frame: 354-371 Punch
|
||||
frame: 372-397 Run
|
||||
frame: 398-423 Run_Holding
|
||||
frame: 424-449 Run_Tall
|
||||
frame: 450-465 Shoot
|
||||
frame: 466-484 SwordSlash
|
||||
frame: 485-510 Walk
|
||||
frame: 511-536 Walk_Holding
|
||||
frame: 537-562 Walk_Tall
|
||||
frame: 563-604 Yes
|
||||
frame: 605-623 Kick
|
||||
frame: 624-648 RobotArmature|Dance
|
||||
frame: 649-692 RobotArmature|Death
|
||||
frame: 693-738 RobotArmature|Hello
|
||||
frame: 739-751 RobotArmature|HitRecieve_1
|
||||
frame: 752-764 RobotArmature|HitRecieve_2
|
||||
frame: 765-865 RobotArmature|Idle
|
||||
frame: 866-892 RobotArmature|Jump
|
||||
frame: 893-911 RobotArmature|Kick
|
||||
frame: 912-953 RobotArmature|No
|
||||
frame: 954-996 RobotArmature|Pickup
|
||||
frame: 997-1014 RobotArmature|Punch
|
||||
frame: 1015-1040 RobotArmature|Run
|
||||
frame: 1041-1066 RobotArmature|Run_Holding
|
||||
frame: 1067-1092 RobotArmature|Run_Tall
|
||||
frame: 1093-1108 RobotArmature|Shoot
|
||||
frame: 1109-1127 RobotArmature|SwordSlash
|
||||
frame: 1128-1153 RobotArmature|Walk
|
||||
frame: 1154-1179 RobotArmature|Walk_Holding
|
||||
frame: 1180-1205 RobotArmature|Walk_Tall
|
||||
frame: 1206-1247 RobotArmature|Yes
|
||||
frame: 0-100 Idle
|
||||
frame: 101-125 Dance
|
||||
frame: 126-169 Death
|
||||
frame: 170-215 Hello
|
||||
frame: 216-228 HitRecieve_1
|
||||
frame: 229-241 HitRecieve_2
|
||||
frame: 242-268 Jump
|
||||
frame: 269-310 No
|
||||
frame: 311-353 Pickup
|
||||
frame: 354-371 Punch
|
||||
frame: 372-397 Run
|
||||
frame: 398-423 Run_Holding
|
||||
frame: 424-449 Run_Tall
|
||||
frame: 450-465 Shoot
|
||||
frame: 466-484 SwordSlash
|
||||
frame: 485-510 Walk
|
||||
frame: 511-536 Walk_Holding
|
||||
frame: 537-562 Walk_Tall
|
||||
frame: 563-604 Yes
|
||||
frame: 605-623 Kick
|
||||
frame: 624-648 RobotArmature|Dance
|
||||
frame: 649-692 RobotArmature|Death
|
||||
frame: 693-738 RobotArmature|Hello
|
||||
frame: 739-751 RobotArmature|HitRecieve_1
|
||||
frame: 752-764 RobotArmature|HitRecieve_2
|
||||
frame: 765-865 RobotArmature|Idle
|
||||
frame: 866-892 RobotArmature|Jump
|
||||
frame: 893-911 RobotArmature|Kick
|
||||
frame: 912-953 RobotArmature|No
|
||||
frame: 954-996 RobotArmature|Pickup
|
||||
frame: 997-1014 RobotArmature|Punch
|
||||
frame: 1015-1040 RobotArmature|Run
|
||||
frame: 1041-1066 RobotArmature|Run_Holding
|
||||
frame: 1067-1092 RobotArmature|Run_Tall
|
||||
frame: 1093-1108 RobotArmature|Shoot
|
||||
frame: 1109-1127 RobotArmature|SwordSlash
|
||||
frame: 1128-1153 RobotArmature|Walk
|
||||
frame: 1154-1179 RobotArmature|Walk_Holding
|
||||
frame: 1180-1205 RobotArmature|Walk_Tall
|
||||
frame: 1206-1247 RobotArmature|Yes
|
||||
|
|
|
@ -1,36 +1,36 @@
|
|||
frame: 0-24 Dance
|
||||
frame: 25-68 Death
|
||||
frame: 69-114 Hello
|
||||
frame: 115-127 HitRecieve_1
|
||||
frame: 128-140 HitRecieve_2
|
||||
frame: 141-241 Idle
|
||||
frame: 242-268 Jump
|
||||
frame: 269-287 Kick
|
||||
frame: 288-329 No
|
||||
frame: 330-372 Pickup
|
||||
frame: 373-390 Punch
|
||||
frame: 391-416 Run
|
||||
frame: 417-442 Run_Tall
|
||||
frame: 443-458 Shoot
|
||||
frame: 459-477 SwordSlash
|
||||
frame: 478-503 Walk
|
||||
frame: 504-529 Walk_Tall
|
||||
frame: 530-572 Yes
|
||||
frame: 573-597 RobotArmature|Dance
|
||||
frame: 598-641 RobotArmature|Death
|
||||
frame: 642-687 RobotArmature|Hello
|
||||
frame: 688-700 RobotArmature|HitRecieve_1
|
||||
frame: 701-713 RobotArmature|HitRecieve_2
|
||||
frame: 714-814 RobotArmature|Idle
|
||||
frame: 815-841 RobotArmature|Jump
|
||||
frame: 842-860 RobotArmature|Kick
|
||||
frame: 861-902 RobotArmature|No
|
||||
frame: 903-945 RobotArmature|Pickup
|
||||
frame: 946-963 RobotArmature|Punch
|
||||
frame: 964-989 RobotArmature|Run
|
||||
frame: 990-1015 RobotArmature|Run_Tall
|
||||
frame: 1016-1031 RobotArmature|Shoot
|
||||
frame: 1032-1050 RobotArmature|SwordSlash
|
||||
frame: 1051-1076 RobotArmature|Walk
|
||||
frame: 1077-1102 RobotArmature|Walk_Tall
|
||||
frame: 1103-1145 RobotArmature|Yes
|
||||
frame: 0-24 Dance
|
||||
frame: 25-68 Death
|
||||
frame: 69-114 Hello
|
||||
frame: 115-127 HitRecieve_1
|
||||
frame: 128-140 HitRecieve_2
|
||||
frame: 141-241 Idle
|
||||
frame: 242-268 Jump
|
||||
frame: 269-287 Kick
|
||||
frame: 288-329 No
|
||||
frame: 330-372 Pickup
|
||||
frame: 373-390 Punch
|
||||
frame: 391-416 Run
|
||||
frame: 417-442 Run_Tall
|
||||
frame: 443-458 Shoot
|
||||
frame: 459-477 SwordSlash
|
||||
frame: 478-503 Walk
|
||||
frame: 504-529 Walk_Tall
|
||||
frame: 530-572 Yes
|
||||
frame: 573-597 RobotArmature|Dance
|
||||
frame: 598-641 RobotArmature|Death
|
||||
frame: 642-687 RobotArmature|Hello
|
||||
frame: 688-700 RobotArmature|HitRecieve_1
|
||||
frame: 701-713 RobotArmature|HitRecieve_2
|
||||
frame: 714-814 RobotArmature|Idle
|
||||
frame: 815-841 RobotArmature|Jump
|
||||
frame: 842-860 RobotArmature|Kick
|
||||
frame: 861-902 RobotArmature|No
|
||||
frame: 903-945 RobotArmature|Pickup
|
||||
frame: 946-963 RobotArmature|Punch
|
||||
frame: 964-989 RobotArmature|Run
|
||||
frame: 990-1015 RobotArmature|Run_Tall
|
||||
frame: 1016-1031 RobotArmature|Shoot
|
||||
frame: 1032-1050 RobotArmature|SwordSlash
|
||||
frame: 1051-1076 RobotArmature|Walk
|
||||
frame: 1077-1102 RobotArmature|Walk_Tall
|
||||
frame: 1103-1145 RobotArmature|Yes
|
||||
|
|
|
@ -1,36 +1,36 @@
|
|||
frame: 0-100 Idle
|
||||
frame: 101-125 Dance
|
||||
frame: 126-169 Death
|
||||
frame: 170-215 Hello
|
||||
frame: 216-228 HitRecieve_1
|
||||
frame: 229-241 HitRecieve_2
|
||||
frame: 242-268 Jump
|
||||
frame: 269-311 No
|
||||
frame: 312-354 Pickup
|
||||
frame: 355-372 Punch
|
||||
frame: 373-398 Run
|
||||
frame: 399-424 Run_Holding
|
||||
frame: 425-440 Shoot
|
||||
frame: 441-459 SwordSlash
|
||||
frame: 460-485 Walk
|
||||
frame: 486-528 Yes
|
||||
frame: 529-547 Kick
|
||||
frame: 548-573 Walk_Holding
|
||||
frame: 574-598 RobotArmature|Dance
|
||||
frame: 599-642 RobotArmature|Death
|
||||
frame: 643-688 RobotArmature|Hello
|
||||
frame: 689-701 RobotArmature|HitRecieve_1
|
||||
frame: 702-714 RobotArmature|HitRecieve_2
|
||||
frame: 715-815 RobotArmature|Idle
|
||||
frame: 816-842 RobotArmature|Jump
|
||||
frame: 843-861 RobotArmature|Kick
|
||||
frame: 862-904 RobotArmature|No
|
||||
frame: 905-947 RobotArmature|Pickup
|
||||
frame: 948-965 RobotArmature|Punch
|
||||
frame: 966-991 RobotArmature|Run
|
||||
frame: 992-1017 RobotArmature|Run_Holding
|
||||
frame: 1018-1033 RobotArmature|Shoot
|
||||
frame: 1034-1052 RobotArmature|SwordSlash
|
||||
frame: 1053-1078 RobotArmature|Walk
|
||||
frame: 1079-1104 RobotArmature|Walk_Holding
|
||||
frame: 1105-1147 RobotArmature|Yes
|
||||
frame: 0-100 Idle
|
||||
frame: 101-125 Dance
|
||||
frame: 126-169 Death
|
||||
frame: 170-215 Hello
|
||||
frame: 216-228 HitRecieve_1
|
||||
frame: 229-241 HitRecieve_2
|
||||
frame: 242-268 Jump
|
||||
frame: 269-311 No
|
||||
frame: 312-354 Pickup
|
||||
frame: 355-372 Punch
|
||||
frame: 373-398 Run
|
||||
frame: 399-424 Run_Holding
|
||||
frame: 425-440 Shoot
|
||||
frame: 441-459 SwordSlash
|
||||
frame: 460-485 Walk
|
||||
frame: 486-528 Yes
|
||||
frame: 529-547 Kick
|
||||
frame: 548-573 Walk_Holding
|
||||
frame: 574-598 RobotArmature|Dance
|
||||
frame: 599-642 RobotArmature|Death
|
||||
frame: 643-688 RobotArmature|Hello
|
||||
frame: 689-701 RobotArmature|HitRecieve_1
|
||||
frame: 702-714 RobotArmature|HitRecieve_2
|
||||
frame: 715-815 RobotArmature|Idle
|
||||
frame: 816-842 RobotArmature|Jump
|
||||
frame: 843-861 RobotArmature|Kick
|
||||
frame: 862-904 RobotArmature|No
|
||||
frame: 905-947 RobotArmature|Pickup
|
||||
frame: 948-965 RobotArmature|Punch
|
||||
frame: 966-991 RobotArmature|Run
|
||||
frame: 992-1017 RobotArmature|Run_Holding
|
||||
frame: 1018-1033 RobotArmature|Shoot
|
||||
frame: 1034-1052 RobotArmature|SwordSlash
|
||||
frame: 1053-1078 RobotArmature|Walk
|
||||
frame: 1079-1104 RobotArmature|Walk_Holding
|
||||
frame: 1105-1147 RobotArmature|Yes
|
||||
|
|
|
@ -1,36 +1,36 @@
|
|||
frame: 0-100 Idle
|
||||
frame: 101-125 Dance
|
||||
frame: 126-169 Death
|
||||
frame: 170-215 Hello
|
||||
frame: 216-228 HitRecieve_1
|
||||
frame: 229-241 HitRecieve_2
|
||||
frame: 242-268 Jump
|
||||
frame: 269-311 No
|
||||
frame: 312-354 Pickup
|
||||
frame: 355-372 Punch
|
||||
frame: 373-398 Run
|
||||
frame: 399-424 Run_Holding
|
||||
frame: 425-440 Shoot
|
||||
frame: 441-459 SwordSlash
|
||||
frame: 460-485 Walk
|
||||
frame: 486-528 Yes
|
||||
frame: 529-547 Kick
|
||||
frame: 548-573 Walk_Holding
|
||||
frame: 574-598 RobotArmature|Dance
|
||||
frame: 599-642 RobotArmature|Death
|
||||
frame: 643-688 RobotArmature|Hello
|
||||
frame: 689-701 RobotArmature|HitRecieve_1
|
||||
frame: 702-714 RobotArmature|HitRecieve_2
|
||||
frame: 715-815 RobotArmature|Idle
|
||||
frame: 816-842 RobotArmature|Jump
|
||||
frame: 843-861 RobotArmature|Kick
|
||||
frame: 862-904 RobotArmature|No
|
||||
frame: 905-947 RobotArmature|Pickup
|
||||
frame: 948-965 RobotArmature|Punch
|
||||
frame: 966-991 RobotArmature|Run
|
||||
frame: 992-1017 RobotArmature|Run_Holding
|
||||
frame: 1018-1033 RobotArmature|Shoot
|
||||
frame: 1034-1052 RobotArmature|SwordSlash
|
||||
frame: 1053-1078 RobotArmature|Walk
|
||||
frame: 1079-1104 RobotArmature|Walk_Holding
|
||||
frame: 1105-1147 RobotArmature|Yes
|
||||
frame: 0-100 Idle
|
||||
frame: 101-125 Dance
|
||||
frame: 126-169 Death
|
||||
frame: 170-215 Hello
|
||||
frame: 216-228 HitRecieve_1
|
||||
frame: 229-241 HitRecieve_2
|
||||
frame: 242-268 Jump
|
||||
frame: 269-311 No
|
||||
frame: 312-354 Pickup
|
||||
frame: 355-372 Punch
|
||||
frame: 373-398 Run
|
||||
frame: 399-424 Run_Holding
|
||||
frame: 425-440 Shoot
|
||||
frame: 441-459 SwordSlash
|
||||
frame: 460-485 Walk
|
||||
frame: 486-528 Yes
|
||||
frame: 529-547 Kick
|
||||
frame: 548-573 Walk_Holding
|
||||
frame: 574-598 RobotArmature|Dance
|
||||
frame: 599-642 RobotArmature|Death
|
||||
frame: 643-688 RobotArmature|Hello
|
||||
frame: 689-701 RobotArmature|HitRecieve_1
|
||||
frame: 702-714 RobotArmature|HitRecieve_2
|
||||
frame: 715-815 RobotArmature|Idle
|
||||
frame: 816-842 RobotArmature|Jump
|
||||
frame: 843-861 RobotArmature|Kick
|
||||
frame: 862-904 RobotArmature|No
|
||||
frame: 905-947 RobotArmature|Pickup
|
||||
frame: 948-965 RobotArmature|Punch
|
||||
frame: 966-991 RobotArmature|Run
|
||||
frame: 992-1017 RobotArmature|Run_Holding
|
||||
frame: 1018-1033 RobotArmature|Shoot
|
||||
frame: 1034-1052 RobotArmature|SwordSlash
|
||||
frame: 1053-1078 RobotArmature|Walk
|
||||
frame: 1079-1104 RobotArmature|Walk_Holding
|
||||
frame: 1105-1147 RobotArmature|Yes
|
||||
|
|
|
@ -1 +1 @@
|
|||
frame: 0-0 Idle
|
||||
frame: 0-0 Idle
|
||||
|
|
|
@ -1,182 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<!-- This file license is CC0 (https://creativecommons.org/publicdomain/zero/1.0/). -->
|
||||
<!-- Original code by @SpartanJ https://github.com/SpartanJ/eepp/blob/8552941da19380d7a629c4da80a976aec5d39e5c/bin/emscripten-fs.html -->
|
||||
<html lang="en-us">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no"/>
|
||||
<!-- <title>Example</title> -->
|
||||
<style>
|
||||
body { margin: 0; background-color: black }
|
||||
.emscripten {
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
margin: 0px;
|
||||
border: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
display: block;
|
||||
image-rendering: optimizeSpeed;
|
||||
image-rendering: -moz-crisp-edges;
|
||||
image-rendering: -o-crisp-edges;
|
||||
image-rendering: -webkit-optimize-contrast;
|
||||
image-rendering: optimize-contrast;
|
||||
image-rendering: crisp-edges;
|
||||
image-rendering: pixelated;
|
||||
-ms-interpolation-mode: nearest-neighbor;
|
||||
}
|
||||
.loader {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border-radius: 50%;
|
||||
display: inline-block;
|
||||
border-top: 3px solid #3daee9;
|
||||
border-right: 3px solid transparent;
|
||||
box-sizing: border-box;
|
||||
animation: rotation 1s linear infinite;
|
||||
}
|
||||
.loader-cont {
|
||||
display: flex;
|
||||
width: 100vw;
|
||||
justify-content: center;
|
||||
height: 100vh;
|
||||
align-items: center;
|
||||
}
|
||||
@keyframes rotation {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<canvas class="emscripten" id="canvas" oncontextmenu="event.preventDefault()"></canvas>
|
||||
<div id="cont" class="loader-cont">
|
||||
<span class="loader"></span>
|
||||
</div>
|
||||
<script type='text/javascript'>
|
||||
var loaderCont;
|
||||
|
||||
function getDemoScript(name) {
|
||||
if (name)
|
||||
return name;
|
||||
return "index.js";
|
||||
}
|
||||
|
||||
function getParameter(name) {
|
||||
let url_string = window.location.href;
|
||||
let url = new URL(url_string);
|
||||
return url.searchParams.get(name);
|
||||
}
|
||||
|
||||
function loadScript(url, callback) {
|
||||
loaderCont = document.getElementById('cont');
|
||||
let head = document.head;
|
||||
let script = document.createElement('script');
|
||||
script.type = 'text/javascript';
|
||||
script.src = url;
|
||||
script.onreadystatechange = callback;
|
||||
script.onload = callback;
|
||||
head.appendChild(script);
|
||||
}
|
||||
|
||||
var Module = {
|
||||
preRun: [],
|
||||
postRun: [],
|
||||
print: (function() {
|
||||
return function(text) {
|
||||
text = Array.prototype.slice.call(arguments).join(' ');
|
||||
console.log(text);
|
||||
};
|
||||
})(),
|
||||
printErr: function(text) {
|
||||
text = Array.prototype.slice.call(arguments).join(' ');
|
||||
console.error(text);
|
||||
},
|
||||
canvas: (function() {
|
||||
var canvas = document.getElementById('canvas');
|
||||
var dpi = window.devicePixelRatio;
|
||||
canvas.width = window.innerWidth*dpi;
|
||||
canvas.height = window.innerHeight*dpi;
|
||||
|
||||
window.addEventListener('resize', () => {
|
||||
if (canvas.canResize) {
|
||||
dpi = window.devicePixelRatio;
|
||||
canvas.width = window.innerWidth*dpi;
|
||||
canvas.height = window.innerHeight*dpi;
|
||||
}
|
||||
})
|
||||
return canvas;
|
||||
})(),
|
||||
setStatus: function(text) {
|
||||
console.log("status: " + text);
|
||||
if (text == "Running...")
|
||||
loaderCont.style.display = 'none';
|
||||
},
|
||||
monitorRunDependencies: function(left) {
|
||||
// no run dependencies to log
|
||||
},
|
||||
arguments: window.location.search.substr(1).split('&')
|
||||
};
|
||||
window.onerror = function() {
|
||||
console.log("onerror: " + event);
|
||||
};
|
||||
|
||||
(function() {
|
||||
loadScript(getDemoScript(getParameter("run")));
|
||||
})();
|
||||
</script>
|
||||
|
||||
{{{ SCRIPT }}}
|
||||
|
||||
<button onclick="openFullscreen();" style="position:relative; z-index: 1000; float: right;">⛶</button>
|
||||
<!-- <button id="btn-audio" onclick="toggleAudio();" style="position:relative; z-index: 1000; float: right;">M</button> -->
|
||||
<script>
|
||||
function openFullscreen() {
|
||||
var canvas = document.getElementById("canvas");
|
||||
if (canvas.requestFullscreen) {
|
||||
canvas.requestFullscreen();
|
||||
} else if (canvas.webkitRequestFullscreen) { /* Safari */
|
||||
canvas.webkitRequestFullscreen();
|
||||
} else if (canvas.msRequestFullscreen) { /* IE11 */
|
||||
canvas.msRequestFullscreen();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- <script type='text/javascript'>
|
||||
var audioBtn = document.querySelector('#btn-audio');
|
||||
|
||||
// An array of all contexts to resume on the page
|
||||
const audioContexList = [];
|
||||
(function() {
|
||||
// A proxy object to intercept AudioContexts and
|
||||
// add them to the array for tracking and resuming later
|
||||
self.AudioContext = new Proxy(self.AudioContext, {
|
||||
construct(target, args) {
|
||||
const result = new target(...args);
|
||||
audioContexList.push(result);
|
||||
if (result.state == "suspended") audioBtn.value = "RESUME";
|
||||
return result;
|
||||
}
|
||||
});
|
||||
})();
|
||||
|
||||
function toggleAudio() {
|
||||
var resumed = false;
|
||||
audioContexList.forEach(ctx => {
|
||||
if (ctx.state == "suspended") { ctx.resume(); resumed = true; }
|
||||
else if (ctx.state == "running") ctx.suspend();
|
||||
});
|
||||
|
||||
if (resumed) audioBtn.value = "SUSPEND";
|
||||
else audioBtn.value = "RESUME";
|
||||
}
|
||||
</script> -->
|
||||
</body>
|
||||
</html>
|
|
@ -1,284 +0,0 @@
|
|||
local core = require "core"
|
||||
local common = require "core.common"
|
||||
local config = require "core.config"
|
||||
local command = require "core.command"
|
||||
local style = require "core.style"
|
||||
local keymap = require "core.keymap"
|
||||
local translate = require "core.doc.translate"
|
||||
local RootView = require "core.rootview"
|
||||
local DocView = require "core.docview"
|
||||
|
||||
config.autocomplete_max_suggestions = 6
|
||||
|
||||
local autocomplete = {}
|
||||
autocomplete.map = {}
|
||||
|
||||
|
||||
local mt = { __tostring = function(t) return t.text end }
|
||||
|
||||
function autocomplete.add(t)
|
||||
local items = {}
|
||||
for text, info in pairs(t.items) do
|
||||
info = (type(info) == "string") and info
|
||||
table.insert(items, setmetatable({ text = text, info = info }, mt))
|
||||
end
|
||||
autocomplete.map[t.name] = { files = t.files or ".*", items = items }
|
||||
end
|
||||
|
||||
|
||||
core.add_thread(function()
|
||||
local cache = setmetatable({}, { __mode = "k" })
|
||||
|
||||
local function get_symbols(doc)
|
||||
local i = 1
|
||||
local s = {}
|
||||
while i < #doc.lines do
|
||||
for sym in doc.lines[i]:gmatch(config.symbol_pattern) do
|
||||
s[sym] = true
|
||||
end
|
||||
i = i + 1
|
||||
if i % 100 == 0 then coroutine.yield() end
|
||||
end
|
||||
return s
|
||||
end
|
||||
|
||||
local function cache_is_valid(doc)
|
||||
local c = cache[doc]
|
||||
return c and c.last_change_id == doc:get_change_id()
|
||||
end
|
||||
|
||||
while true do
|
||||
local symbols = {}
|
||||
|
||||
-- lift all symbols from all docs
|
||||
for _, doc in ipairs(core.docs) do
|
||||
-- update the cache if the doc has changed since the last iteration
|
||||
if not cache_is_valid(doc) then
|
||||
cache[doc] = {
|
||||
last_change_id = doc:get_change_id(),
|
||||
symbols = get_symbols(doc)
|
||||
}
|
||||
end
|
||||
-- update symbol set with doc's symbol set
|
||||
for sym in pairs(cache[doc].symbols) do
|
||||
symbols[sym] = true
|
||||
end
|
||||
coroutine.yield()
|
||||
end
|
||||
|
||||
-- update symbols list
|
||||
autocomplete.add { name = "open-docs", items = symbols }
|
||||
|
||||
-- wait for next scan
|
||||
local valid = true
|
||||
while valid do
|
||||
coroutine.yield(1)
|
||||
for _, doc in ipairs(core.docs) do
|
||||
if not cache_is_valid(doc) then
|
||||
valid = false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end)
|
||||
|
||||
|
||||
local partial = ""
|
||||
local suggestions_idx = 1
|
||||
local suggestions = {}
|
||||
local last_line, last_col
|
||||
|
||||
|
||||
local function reset_suggestions()
|
||||
suggestions_idx = 1
|
||||
suggestions = {}
|
||||
end
|
||||
|
||||
|
||||
local function update_suggestions()
|
||||
local doc = core.active_view.doc
|
||||
local filename = doc and doc.filename or ""
|
||||
|
||||
-- get all relevant suggestions for given filename
|
||||
local items = {}
|
||||
for _, v in pairs(autocomplete.map) do
|
||||
if common.match_pattern(filename, v.files) then
|
||||
for _, item in pairs(v.items) do
|
||||
table.insert(items, item)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- fuzzy match, remove duplicates and store
|
||||
items = common.fuzzy_match(items, partial)
|
||||
local j = 1
|
||||
for i = 1, config.autocomplete_max_suggestions do
|
||||
suggestions[i] = items[j]
|
||||
while items[j] and items[i].text == items[j].text do
|
||||
items[i].info = items[i].info or items[j].info
|
||||
j = j + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local function get_partial_symbol()
|
||||
local doc = core.active_view.doc
|
||||
local line2, col2 = doc:get_selection()
|
||||
local line1, col1 = doc:position_offset(line2, col2, translate.start_of_word)
|
||||
return doc:get_text(line1, col1, line2, col2)
|
||||
end
|
||||
|
||||
|
||||
local function get_active_view()
|
||||
if getmetatable(core.active_view) == DocView then
|
||||
return core.active_view
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local function get_suggestions_rect(av)
|
||||
if #suggestions == 0 then
|
||||
return 0, 0, 0, 0
|
||||
end
|
||||
|
||||
local line, col = av.doc:get_selection()
|
||||
local x, y = av:get_line_screen_position(line)
|
||||
x = x + av:get_col_x_offset(line, col - #partial)
|
||||
y = y + av:get_line_height() + style.padding.y
|
||||
local font = av:get_font()
|
||||
local th = font:get_height()
|
||||
|
||||
local max_width = 0
|
||||
for _, s in ipairs(suggestions) do
|
||||
local w = font:get_width(s.text)
|
||||
if s.info then
|
||||
w = w + style.font:get_width(s.info) + style.padding.x
|
||||
end
|
||||
max_width = math.max(max_width, w)
|
||||
end
|
||||
|
||||
return
|
||||
x - style.padding.x,
|
||||
y - style.padding.y,
|
||||
max_width + style.padding.x * 2,
|
||||
#suggestions * (th + style.padding.y) + style.padding.y
|
||||
end
|
||||
|
||||
|
||||
local function draw_suggestions_box(av)
|
||||
-- draw background rect
|
||||
local rx, ry, rw, rh = get_suggestions_rect(av)
|
||||
renderer.draw_rect(rx, ry, rw, rh, style.background3)
|
||||
|
||||
-- draw text
|
||||
local font = av:get_font()
|
||||
local lh = font:get_height() + style.padding.y
|
||||
local y = ry + style.padding.y / 2
|
||||
for i, s in ipairs(suggestions) do
|
||||
local color = (i == suggestions_idx) and style.accent or style.text
|
||||
common.draw_text(font, color, s.text, "left", rx + style.padding.x, y, rw, lh)
|
||||
if s.info then
|
||||
color = (i == suggestions_idx) and style.text or style.dim
|
||||
common.draw_text(style.font, color, s.info, "right", rx, y, rw - style.padding.x, lh)
|
||||
end
|
||||
y = y + lh
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-- patch event logic into RootView
|
||||
local on_text_input = RootView.on_text_input
|
||||
local update = RootView.update
|
||||
local draw = RootView.draw
|
||||
|
||||
|
||||
RootView.on_text_input = function(...)
|
||||
on_text_input(...)
|
||||
|
||||
local av = get_active_view()
|
||||
if av then
|
||||
-- update partial symbol and suggestions
|
||||
partial = get_partial_symbol()
|
||||
if #partial >= 3 then
|
||||
update_suggestions()
|
||||
last_line, last_col = av.doc:get_selection()
|
||||
else
|
||||
reset_suggestions()
|
||||
end
|
||||
|
||||
-- scroll if rect is out of bounds of view
|
||||
local _, y, _, h = get_suggestions_rect(av)
|
||||
local limit = av.position.y + av.size.y
|
||||
if y + h > limit then
|
||||
av.scroll.to.y = av.scroll.y + y + h - limit
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
RootView.update = function(...)
|
||||
update(...)
|
||||
|
||||
local av = get_active_view()
|
||||
if av then
|
||||
-- reset suggestions if caret was moved
|
||||
local line, col = av.doc:get_selection()
|
||||
if line ~= last_line or col ~= last_col then
|
||||
reset_suggestions()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
RootView.draw = function(...)
|
||||
draw(...)
|
||||
|
||||
local av = get_active_view()
|
||||
if av then
|
||||
-- draw suggestions box after everything else
|
||||
core.root_view:defer_draw(draw_suggestions_box, av)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local function predicate()
|
||||
return get_active_view() and #suggestions > 0
|
||||
end
|
||||
|
||||
|
||||
command.add(predicate, {
|
||||
["autocomplete:complete"] = function()
|
||||
local doc = core.active_view.doc
|
||||
local line, col = doc:get_selection()
|
||||
local text = suggestions[suggestions_idx].text
|
||||
doc:insert(line, col, text)
|
||||
doc:remove(line, col, line, col - #partial)
|
||||
doc:set_selection(line, col + #text - #partial)
|
||||
reset_suggestions()
|
||||
end,
|
||||
|
||||
["autocomplete:previous"] = function()
|
||||
suggestions_idx = math.max(suggestions_idx - 1, 1)
|
||||
end,
|
||||
|
||||
["autocomplete:next"] = function()
|
||||
suggestions_idx = math.min(suggestions_idx + 1, #suggestions)
|
||||
end,
|
||||
|
||||
["autocomplete:cancel"] = function()
|
||||
reset_suggestions()
|
||||
end,
|
||||
})
|
||||
|
||||
|
||||
keymap.add {
|
||||
["tab"] = "autocomplete:complete",
|
||||
["up"] = "autocomplete:previous",
|
||||
["down"] = "autocomplete:next",
|
||||
["escape"] = "autocomplete:cancel",
|
||||
}
|
||||
|
||||
|
||||
return autocomplete
|
|
@ -1,114 +0,0 @@
|
|||
local core = require "core"
|
||||
local translate = require "core.doc.translate"
|
||||
local config = require "core.config"
|
||||
local DocView = require "core.docview"
|
||||
local command = require "core.command"
|
||||
local keymap = require "core.keymap"
|
||||
|
||||
|
||||
config.autoinsert_map = {
|
||||
["["] = "]",
|
||||
["{"] = "}",
|
||||
["("] = ")",
|
||||
['"'] = '"',
|
||||
["'"] = "'",
|
||||
["`"] = "`",
|
||||
}
|
||||
|
||||
|
||||
local function is_closer(chr)
|
||||
for _, v in pairs(config.autoinsert_map) do
|
||||
if v == chr then
|
||||
return true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function count_char(text, chr)
|
||||
local count = 0
|
||||
for _ in text:gmatch(chr) do
|
||||
count = count + 1
|
||||
end
|
||||
return count
|
||||
end
|
||||
|
||||
|
||||
local on_text_input = DocView.on_text_input
|
||||
|
||||
function DocView:on_text_input(text)
|
||||
local mapping = config.autoinsert_map[text]
|
||||
|
||||
-- prevents plugin from operating on `CommandView`
|
||||
if getmetatable(self) ~= DocView then
|
||||
return on_text_input(self, text)
|
||||
end
|
||||
|
||||
-- wrap selection if we have a selection
|
||||
if mapping and self.doc:has_selection() then
|
||||
local l1, c1, l2, c2, swap = self.doc:get_selection(true)
|
||||
self.doc:insert(l2, c2, mapping)
|
||||
self.doc:insert(l1, c1, text)
|
||||
self.doc:set_selection(l1, c1, l2, c2 + 2, swap)
|
||||
return
|
||||
end
|
||||
|
||||
-- skip inserting closing text
|
||||
local chr = self.doc:get_char(self.doc:get_selection())
|
||||
if text == chr and is_closer(chr) then
|
||||
self.doc:move_to(1)
|
||||
return
|
||||
end
|
||||
|
||||
-- don't insert closing quote if we have a non-even number on this line
|
||||
local line = self.doc:get_selection()
|
||||
if text == mapping and count_char(self.doc.lines[line], text) % 2 == 1 then
|
||||
return on_text_input(self, text)
|
||||
end
|
||||
|
||||
-- auto insert closing bracket
|
||||
if mapping and (chr:find("%s") or is_closer(chr) and chr ~= '"') then
|
||||
on_text_input(self, text)
|
||||
on_text_input(self, mapping)
|
||||
self.doc:move_to(-1)
|
||||
return
|
||||
end
|
||||
|
||||
on_text_input(self, text)
|
||||
end
|
||||
|
||||
|
||||
|
||||
local function predicate()
|
||||
return getmetatable(core.active_view) == DocView
|
||||
and not core.active_view.doc:has_selection()
|
||||
end
|
||||
|
||||
command.add(predicate, {
|
||||
["autoinsert:backspace"] = function()
|
||||
local doc = core.active_view.doc
|
||||
local l, c = doc:get_selection()
|
||||
local chr = doc:get_char(l, c)
|
||||
if config.autoinsert_map[doc:get_char(l, c - 1)] and is_closer(chr) then
|
||||
doc:delete_to(1)
|
||||
end
|
||||
command.perform "doc:backspace"
|
||||
end,
|
||||
|
||||
["autoinsert:delete-to-previous-word-start"] = function()
|
||||
local doc = core.active_view.doc
|
||||
local le, ce = translate.previous_word_start(doc, doc:get_selection())
|
||||
while true do
|
||||
local l, c = doc:get_selection()
|
||||
if l == le and c == ce then
|
||||
break
|
||||
end
|
||||
command.perform "autoinsert:backspace"
|
||||
end
|
||||
end,
|
||||
})
|
||||
|
||||
keymap.add {
|
||||
["backspace"] = "autoinsert:backspace",
|
||||
["ctrl+backspace"] = "autoinsert:delete-to-previous-word-start",
|
||||
["ctrl+shift+backspace"] = "autoinsert:delete-to-previous-word-start",
|
||||
}
|
|
@ -1,61 +0,0 @@
|
|||
local core = require "core"
|
||||
local config = require "core.config"
|
||||
local Doc = require "core.doc"
|
||||
|
||||
|
||||
local times = setmetatable({}, { __mode = "k" })
|
||||
|
||||
local function update_time(doc)
|
||||
local info = system.get_file_info(doc.filename)
|
||||
times[doc] = info.modified
|
||||
end
|
||||
|
||||
|
||||
local function reload_doc(doc)
|
||||
local fp = io.open(doc.filename, "r")
|
||||
local text = fp:read("*a")
|
||||
fp:close()
|
||||
|
||||
local sel = { doc:get_selection() }
|
||||
doc:remove(1, 1, math.huge, math.huge)
|
||||
doc:insert(1, 1, text:gsub("\r", ""):gsub("\n$", ""))
|
||||
doc:set_selection(table.unpack(sel))
|
||||
|
||||
update_time(doc)
|
||||
doc:clean()
|
||||
core.log_quiet("Auto-reloaded doc \"%s\"", doc.filename)
|
||||
end
|
||||
|
||||
|
||||
core.add_thread(function()
|
||||
while true do
|
||||
-- check all doc modified times
|
||||
for _, doc in ipairs(core.docs) do
|
||||
local info = system.get_file_info(doc.filename or "")
|
||||
if info and times[doc] ~= info.modified then
|
||||
reload_doc(doc)
|
||||
end
|
||||
coroutine.yield()
|
||||
end
|
||||
|
||||
-- wait for next scan
|
||||
coroutine.yield(config.project_scan_rate)
|
||||
end
|
||||
end)
|
||||
|
||||
|
||||
-- patch `Doc.save|load` to store modified time
|
||||
local load = Doc.load
|
||||
local save = Doc.save
|
||||
|
||||
Doc.load = function(self, ...)
|
||||
local res = load(self, ...)
|
||||
update_time(self)
|
||||
return res
|
||||
end
|
||||
|
||||
Doc.save = function(self, ...)
|
||||
local res = save(self, ...)
|
||||
update_time(self)
|
||||
return res
|
||||
end
|
|
@ -1,35 +0,0 @@
|
|||
require "plugins.reflow"
|
||||
local config = require "core.config"
|
||||
local command = require "core.command"
|
||||
local DocView = require "core.docview"
|
||||
|
||||
config.autowrap_files = { "%.md$", "%.txt$" }
|
||||
|
||||
|
||||
local on_text_input = DocView.on_text_input
|
||||
|
||||
DocView.on_text_input = function(self, ...)
|
||||
on_text_input(self, ...)
|
||||
|
||||
-- early-exit if the filename does not match a file type pattern
|
||||
local filename = self.doc.filename or ""
|
||||
local matched = false
|
||||
for _, ptn in ipairs(config.autowrap_files) do
|
||||
if filename:match(ptn) then
|
||||
matched = true
|
||||
break
|
||||
end
|
||||
end
|
||||
if not matched then return end
|
||||
|
||||
-- do automatic reflow on line if we're typing at the end of the line and have
|
||||
-- reached the line limit
|
||||
local line, col = self.doc:get_selection()
|
||||
local text = self.doc:get_text(line, 1, line, math.huge)
|
||||
if #text >= config.line_limit and col > #text then
|
||||
command.perform("doc:select-lines")
|
||||
command.perform("reflow:reflow")
|
||||
command.perform("doc:move-to-next-char")
|
||||
command.perform("doc:move-to-previous-char")
|
||||
end
|
||||
end
|
|
@ -1,117 +0,0 @@
|
|||
local core = require "core"
|
||||
local style = require "core.style"
|
||||
local command = require "core.command"
|
||||
local keymap = require "core.keymap"
|
||||
local DocView = require "core.docview"
|
||||
|
||||
local bracket_maps = {
|
||||
-- [ ] ( ) { }
|
||||
{ [91] = 93, [40] = 41, [123] = 125, step = 1 },
|
||||
-- ] [ ) ( } {
|
||||
{ [93] = 91, [41] = 40, [125] = 123, step = -1 },
|
||||
}
|
||||
|
||||
|
||||
local function get_matching_bracket(doc, line, col, line_limit, open_byte, close_byte, step)
|
||||
local end_line = line + line_limit * step
|
||||
local depth = 0
|
||||
|
||||
while line ~= end_line do
|
||||
local byte = doc.lines[line]:byte(col)
|
||||
if byte == open_byte then
|
||||
depth = depth + 1
|
||||
elseif byte == close_byte then
|
||||
depth = depth - 1
|
||||
if depth == 0 then return line, col end
|
||||
end
|
||||
|
||||
local prev_line, prev_col = line, col
|
||||
line, col = doc:position_offset(line, col, step)
|
||||
if line == prev_line and col == prev_col then
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local state = {}
|
||||
|
||||
local function update_state(line_limit)
|
||||
line_limit = line_limit or math.huge
|
||||
|
||||
-- reset if we don't have a document (eg. DocView isn't focused)
|
||||
local doc = core.active_view.doc
|
||||
if not doc then
|
||||
state = {}
|
||||
return
|
||||
end
|
||||
|
||||
-- early exit if nothing has changed since the last call
|
||||
local line, col = doc:get_selection()
|
||||
local change_id = doc:get_change_id()
|
||||
if state.doc == doc and state.line == line and state.col == col
|
||||
and state.change_id == change_id and state.limit == line_limit then
|
||||
return
|
||||
end
|
||||
|
||||
-- find matching bracket if we're on a bracket
|
||||
local line2, col2
|
||||
for _, map in ipairs(bracket_maps) do
|
||||
for i = 0, -1, -1 do
|
||||
local line, col = doc:position_offset(line, col, i)
|
||||
local open = doc.lines[line]:byte(col)
|
||||
local close = map[open]
|
||||
if close then
|
||||
line2, col2 = get_matching_bracket(doc, line, col, line_limit, open, close, map.step)
|
||||
goto found
|
||||
end
|
||||
end
|
||||
end
|
||||
::found::
|
||||
|
||||
-- update
|
||||
state = {
|
||||
change_id = change_id,
|
||||
doc = doc,
|
||||
line = line,
|
||||
col = col,
|
||||
line2 = line2,
|
||||
col2 = col2,
|
||||
limit = line_limit,
|
||||
}
|
||||
end
|
||||
|
||||
|
||||
local update = DocView.update
|
||||
|
||||
function DocView:update(...)
|
||||
update(self, ...)
|
||||
update_state(100)
|
||||
end
|
||||
|
||||
|
||||
local draw_line_text = DocView.draw_line_text
|
||||
|
||||
function DocView:draw_line_text(idx, x, y)
|
||||
draw_line_text(self, idx, x, y)
|
||||
|
||||
if self.doc == state.doc and idx == state.line2 then
|
||||
local color = style.bracketmatch_color or style.syntax["function"]
|
||||
local x1 = x + self:get_col_x_offset(idx, state.col2)
|
||||
local x2 = x + self:get_col_x_offset(idx, state.col2 + 1)
|
||||
local h = math.ceil(1 * SCALE)
|
||||
renderer.draw_rect(x1, y + self:get_line_height() - h, x2 - x1, h, color)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
command.add("core.docview", {
|
||||
["bracket-match:move-to-matching"] = function()
|
||||
update_state()
|
||||
if state.line2 then
|
||||
core.active_view.doc:set_selection(state.line2, state.col2)
|
||||
end
|
||||
end,
|
||||
})
|
||||
|
||||
keymap.add { ["ctrl+m"] = "bracket-match:move-to-matching" }
|
|
@ -1,53 +0,0 @@
|
|||
-- CloseConfirmX plugin for lite text editor
|
||||
-- implementation by chekoopa
|
||||
|
||||
local core = require "core"
|
||||
local config = require "core.config"
|
||||
|
||||
config.closeconfirmx_use_legacy = false
|
||||
config.closeconfirmx_use_short_name = true
|
||||
|
||||
local legacy_confirm = core.confirm_close_all
|
||||
|
||||
local function commandful_confirm()
|
||||
local dirty_count = 0
|
||||
local dirty_name
|
||||
for _, doc in ipairs(core.docs) do
|
||||
if doc:is_dirty() then
|
||||
dirty_count = dirty_count + 1
|
||||
dirty_name = doc:get_name()
|
||||
end
|
||||
end
|
||||
if dirty_count > 0 then
|
||||
local text
|
||||
if dirty_count == 1 then
|
||||
if config.closeconfirmx_use_short_name then
|
||||
dirty_name = dirty_name:match("[^/%\\]*$")
|
||||
end
|
||||
text = string.format("Unsaved changes in \"%s\"; Confirm Exit", dirty_name)
|
||||
else
|
||||
text = string.format("Unsaved changes in %d docs; Confirm Exit", dirty_count)
|
||||
end
|
||||
core.command_view:enter(text, function(_, item)
|
||||
if item.text:match("^[cC]") then
|
||||
core.quit(true)
|
||||
end
|
||||
end, function(text)
|
||||
local items = {}
|
||||
if not text:find("^[^sS]") then table.insert(items, "Stay here") end
|
||||
if not text:find("^[^cC]") then table.insert(items, "Close Without Saving") end
|
||||
return items
|
||||
end)
|
||||
-- as we delegate a choice inside the callback,
|
||||
return false
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
function core.confirm_close_all()
|
||||
if config.closeconfirmx_use_legacy then
|
||||
return legacy_confirm()
|
||||
else
|
||||
return commandful_confirm()
|
||||
end
|
||||
end
|
|
@ -1,54 +0,0 @@
|
|||
local common = require "core.common"
|
||||
local DocView = require "core.docview"
|
||||
|
||||
|
||||
local white = { common.color "#ffffff" }
|
||||
local black = { common.color "#000000" }
|
||||
local tmp = {}
|
||||
|
||||
|
||||
local function draw_color_previews(self, idx, x, y, ptn, base, nibbles)
|
||||
local text = self.doc.lines[idx]
|
||||
local s, e = 0, 0
|
||||
|
||||
while true do
|
||||
s, e = text:find(ptn, e + 1)
|
||||
if not s then break end
|
||||
|
||||
local str = text:sub(s, e)
|
||||
local r, g, b = str:match(ptn)
|
||||
r, g, b = tonumber(r, base), tonumber(g, base), tonumber(b, base)
|
||||
|
||||
-- #123 becomes #112233
|
||||
if nibbles then
|
||||
r = r * 16
|
||||
g = g * 16
|
||||
b = b * 16
|
||||
end
|
||||
|
||||
local x1 = x + self:get_col_x_offset(idx, s)
|
||||
local x2 = x + self:get_col_x_offset(idx, e + 1)
|
||||
local oy = self:get_line_text_y_offset()
|
||||
|
||||
local text_color = math.max(r, g, b) < 128 and white or black
|
||||
tmp[1], tmp[2], tmp[3] = r, g, b
|
||||
|
||||
local l1, _, l2, _ = self.doc:get_selection(true)
|
||||
|
||||
if not (self.doc:has_selection() and idx >= l1 and idx <= l2) then
|
||||
renderer.draw_rect(x1, y, x2 - x1, self:get_line_height(), tmp)
|
||||
renderer.draw_text(self:get_font(), str, x1, y + oy, text_color)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local draw_line_text = DocView.draw_line_text
|
||||
|
||||
function DocView:draw_line_text(idx, x, y)
|
||||
draw_line_text(self, idx, x, y)
|
||||
draw_color_previews(self, idx, x, y, "#(%x%x)(%x%x)(%x%x)%f[%W]", 16)
|
||||
draw_color_previews(self, idx, x, y, "#(%x)(%x)(%x)%f[%W]", 16, true) -- support #fff css format
|
||||
draw_color_previews(self, idx, x, y, "rgb?%((%d+)%D+(%d+)%D+(%d+).-%)", 10)
|
||||
draw_color_previews(self, idx, x, y, "rgba?%((%d+)%D+(%d+)%D+(%d+).-%)", 10)
|
||||
end
|
|
@ -1,383 +0,0 @@
|
|||
local core = require "core"
|
||||
local keymap = require "core.keymap"
|
||||
local command = require "core.command"
|
||||
local common = require "core.common"
|
||||
local config = require "core.config"
|
||||
local style = require "core.style"
|
||||
local View = require "core.view"
|
||||
|
||||
config.console_size = 250 * SCALE
|
||||
config.max_console_lines = 200
|
||||
config.autoscroll_console = true
|
||||
|
||||
local files = {
|
||||
script = core.temp_filename(PLATFORM == "Windows" and ".bat"),
|
||||
script2 = core.temp_filename(PLATFORM == "Windows" and ".bat"),
|
||||
output = core.temp_filename(),
|
||||
complete = core.temp_filename(),
|
||||
}
|
||||
|
||||
local console = {}
|
||||
|
||||
local views = {}
|
||||
local pending_threads = {}
|
||||
local thread_active = false
|
||||
local output = nil
|
||||
local output_id = 0
|
||||
local visible = false
|
||||
|
||||
function console.clear()
|
||||
output = { { text = "", time = 0 } }
|
||||
end
|
||||
|
||||
|
||||
local function read_file(filename, offset)
|
||||
local fp = io.open(filename, "rb")
|
||||
fp:seek("set", offset or 0)
|
||||
local res = fp:read("*a")
|
||||
fp:close()
|
||||
return res
|
||||
end
|
||||
|
||||
|
||||
local function write_file(filename, text)
|
||||
local fp = io.open(filename, "w")
|
||||
fp:write(text)
|
||||
fp:close()
|
||||
end
|
||||
|
||||
|
||||
local function lines(text)
|
||||
return (text .. "\n"):gmatch("(.-)\n")
|
||||
end
|
||||
|
||||
|
||||
local function push_output(str, opt)
|
||||
local first = true
|
||||
for line in lines(str) do
|
||||
if first then
|
||||
line = table.remove(output).text .. line
|
||||
end
|
||||
line = line:gsub("\x1b%[[%d;]+m", "") -- strip ANSI colors
|
||||
table.insert(output, {
|
||||
text = line,
|
||||
time = os.time(),
|
||||
icon = line:find(opt.error_pattern) and "!"
|
||||
or line:find(opt.warning_pattern) and "i",
|
||||
file_pattern = opt.file_pattern,
|
||||
})
|
||||
if #output > config.max_console_lines then
|
||||
table.remove(output, 1)
|
||||
for view in pairs(views) do
|
||||
view:on_line_removed()
|
||||
end
|
||||
end
|
||||
first = false
|
||||
end
|
||||
output_id = output_id + 1
|
||||
core.redraw = true
|
||||
end
|
||||
|
||||
|
||||
local function init_opt(opt)
|
||||
local res = {
|
||||
command = "",
|
||||
file_pattern = "[^?:%s]+%.[^?:%s]+",
|
||||
error_pattern = "error",
|
||||
warning_pattern = "warning",
|
||||
on_complete = function() end,
|
||||
}
|
||||
for k, v in pairs(res) do
|
||||
res[k] = opt[k] or v
|
||||
end
|
||||
return res
|
||||
end
|
||||
|
||||
|
||||
function console.run(opt)
|
||||
opt = init_opt(opt)
|
||||
|
||||
local function thread()
|
||||
-- init script file(s)
|
||||
if PLATFORM == "Windows" then
|
||||
write_file(files.script, opt.command .. "\n")
|
||||
write_file(files.script2, string.format([[
|
||||
@echo off
|
||||
call %q >%q 2>&1
|
||||
echo "" >%q
|
||||
exit
|
||||
]], files.script, files.output, files.complete))
|
||||
system.exec(string.format("call %q", files.script2))
|
||||
else
|
||||
write_file(files.script, string.format([[
|
||||
%s
|
||||
touch %q
|
||||
]], opt.command, files.complete))
|
||||
system.exec(string.format("bash %q >%q 2>&1", files.script, files.output))
|
||||
end
|
||||
|
||||
-- checks output file for change and reads
|
||||
local last_size = 0
|
||||
local function check_output_file()
|
||||
if PLATFORM == "Windows" then
|
||||
local fp = io.open(files.output)
|
||||
if fp then fp:close() end
|
||||
end
|
||||
local info = system.get_file_info(files.output)
|
||||
if info and info.size > last_size then
|
||||
local text = read_file(files.output, last_size)
|
||||
push_output(text, opt)
|
||||
last_size = info.size
|
||||
end
|
||||
end
|
||||
|
||||
-- read output file until we get a file indicating completion
|
||||
while not system.get_file_info(files.complete) do
|
||||
check_output_file()
|
||||
coroutine.yield(0.1)
|
||||
end
|
||||
check_output_file()
|
||||
if output[#output].text ~= "" then
|
||||
push_output("\n", opt)
|
||||
end
|
||||
push_output("!DIVIDER\n", opt)
|
||||
|
||||
-- clean up and finish
|
||||
for _, file in pairs(files) do
|
||||
os.remove(file)
|
||||
end
|
||||
opt.on_complete()
|
||||
|
||||
-- handle pending thread
|
||||
local pending = table.remove(pending_threads, 1)
|
||||
if pending then
|
||||
core.add_thread(pending)
|
||||
else
|
||||
thread_active = false
|
||||
end
|
||||
end
|
||||
|
||||
-- push/init thread
|
||||
if thread_active then
|
||||
table.insert(pending_threads, thread)
|
||||
else
|
||||
core.add_thread(thread)
|
||||
thread_active = true
|
||||
end
|
||||
|
||||
-- make sure static console is visible if it's the only ConsoleView
|
||||
local count = 0
|
||||
for _ in pairs(views) do count = count + 1 end
|
||||
if count == 1 then visible = true end
|
||||
end
|
||||
|
||||
|
||||
|
||||
local ConsoleView = View:extend()
|
||||
|
||||
function ConsoleView:new()
|
||||
ConsoleView.super.new(self)
|
||||
self.scrollable = true
|
||||
self.hovered_idx = -1
|
||||
views[self] = true
|
||||
end
|
||||
|
||||
|
||||
function ConsoleView:try_close(...)
|
||||
ConsoleView.super.try_close(self, ...)
|
||||
views[self] = nil
|
||||
end
|
||||
|
||||
|
||||
function ConsoleView:get_name()
|
||||
return "Console"
|
||||
end
|
||||
|
||||
|
||||
function ConsoleView:get_line_height()
|
||||
return style.code_font:get_height() * config.line_height
|
||||
end
|
||||
|
||||
|
||||
function ConsoleView:get_line_count()
|
||||
return #output - (output[#output].text == "" and 1 or 0)
|
||||
end
|
||||
|
||||
|
||||
function ConsoleView:get_scrollable_size()
|
||||
return self:get_line_count() * self:get_line_height() + style.padding.y * 2
|
||||
end
|
||||
|
||||
|
||||
function ConsoleView:get_visible_line_range()
|
||||
local lh = self:get_line_height()
|
||||
local min = math.max(1, math.floor(self.scroll.y / lh))
|
||||
return min, min + math.floor(self.size.y / lh) + 1
|
||||
end
|
||||
|
||||
|
||||
function ConsoleView:on_mouse_moved(mx, my, ...)
|
||||
ConsoleView.super.on_mouse_moved(self, mx, my, ...)
|
||||
self.hovered_idx = 0
|
||||
for i, item, x,y,w,h in self:each_visible_line() do
|
||||
if mx >= x and my >= y and mx < x + w and my < y + h then
|
||||
if item.text:find(item.file_pattern) then
|
||||
self.hovered_idx = i
|
||||
end
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local function resolve_file(name)
|
||||
if system.get_file_info(name) then
|
||||
return name
|
||||
end
|
||||
local filenames = {}
|
||||
for _, f in ipairs(core.project_files) do
|
||||
table.insert(filenames, f.filename)
|
||||
end
|
||||
local t = common.fuzzy_match(filenames, name)
|
||||
return t[1]
|
||||
end
|
||||
|
||||
|
||||
function ConsoleView:on_line_removed()
|
||||
local diff = self:get_line_height()
|
||||
self.scroll.y = self.scroll.y - diff
|
||||
self.scroll.to.y = self.scroll.to.y - diff
|
||||
end
|
||||
|
||||
|
||||
function ConsoleView:on_mouse_pressed(...)
|
||||
local caught = ConsoleView.super.on_mouse_pressed(self, ...)
|
||||
if caught then
|
||||
return
|
||||
end
|
||||
local item = output[self.hovered_idx]
|
||||
if item then
|
||||
local file, line, col = item.text:match(item.file_pattern)
|
||||
local resolved_file = resolve_file(file)
|
||||
if not resolved_file then
|
||||
core.error("Couldn't resolve file \"%s\"", file)
|
||||
return
|
||||
end
|
||||
core.try(function()
|
||||
core.set_active_view(core.last_active_view)
|
||||
local dv = core.root_view:open_doc(core.open_doc(resolved_file))
|
||||
if line then
|
||||
dv.doc:set_selection(line, col or 0)
|
||||
dv:scroll_to_line(line, false, true)
|
||||
end
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function ConsoleView:each_visible_line()
|
||||
return coroutine.wrap(function()
|
||||
local x, y = self:get_content_offset()
|
||||
local lh = self:get_line_height()
|
||||
local min, max = self:get_visible_line_range()
|
||||
y = y + lh * (min - 1) + style.padding.y
|
||||
max = math.min(max, self:get_line_count())
|
||||
|
||||
for i = min, max do
|
||||
local item = output[i]
|
||||
if not item then break end
|
||||
coroutine.yield(i, item, x, y, self.size.x, lh)
|
||||
y = y + lh
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
|
||||
function ConsoleView:update(...)
|
||||
if self.last_output_id ~= output_id then
|
||||
if config.autoscroll_console then
|
||||
self.scroll.to.y = self:get_scrollable_size()
|
||||
end
|
||||
self.last_output_id = output_id
|
||||
end
|
||||
ConsoleView.super.update(self, ...)
|
||||
end
|
||||
|
||||
|
||||
function ConsoleView:draw()
|
||||
self:draw_background(style.background)
|
||||
local icon_w = style.icon_font:get_width("!")
|
||||
|
||||
for i, item, x, y, w, h in self:each_visible_line() do
|
||||
local tx = x + style.padding.x
|
||||
local time = os.date("%H:%M:%S", item.time)
|
||||
local color = style.text
|
||||
if self.hovered_idx == i then
|
||||
color = style.accent
|
||||
renderer.draw_rect(x, y, w, h, style.line_highlight)
|
||||
end
|
||||
if item.text == "!DIVIDER" then
|
||||
local w = style.font:get_width(time)
|
||||
renderer.draw_rect(tx, y + h / 2, w, math.ceil(SCALE * 1), style.dim)
|
||||
else
|
||||
tx = common.draw_text(style.font, style.dim, time, "left", tx, y, w, h)
|
||||
tx = tx + style.padding.x
|
||||
if item.icon then
|
||||
common.draw_text(style.icon_font, color, item.icon, "left", tx, y, w, h)
|
||||
end
|
||||
tx = tx + icon_w + style.padding.x
|
||||
common.draw_text(style.code_font, color, item.text, "left", tx, y, w, h)
|
||||
end
|
||||
end
|
||||
|
||||
self:draw_scrollbar(self)
|
||||
end
|
||||
|
||||
|
||||
-- init static bottom-of-screen console
|
||||
local view = ConsoleView()
|
||||
local node = core.root_view:get_active_node()
|
||||
node:split("down", view, true)
|
||||
|
||||
function view:update(...)
|
||||
local dest = visible and config.console_size or 0
|
||||
self:move_towards(self.size, "y", dest)
|
||||
ConsoleView.update(self, ...)
|
||||
end
|
||||
|
||||
|
||||
local last_command = ""
|
||||
|
||||
command.add(nil, {
|
||||
["console:reset-output"] = function()
|
||||
output = { { text = "", time = 0 } }
|
||||
end,
|
||||
|
||||
["console:open-console"] = function()
|
||||
local node = core.root_view:get_active_node()
|
||||
node:add_view(ConsoleView())
|
||||
end,
|
||||
|
||||
["console:toggle"] = function()
|
||||
visible = not visible
|
||||
end,
|
||||
|
||||
["console:run"] = function()
|
||||
core.command_view:set_text(last_command, true)
|
||||
core.command_view:enter("Run Console Command", function(cmd)
|
||||
console.run { command = cmd }
|
||||
last_command = cmd
|
||||
end)
|
||||
end
|
||||
})
|
||||
|
||||
keymap.add {
|
||||
["ctrl+."] = "console:toggle",
|
||||
["ctrl+shift+."] = "console:run",
|
||||
}
|
||||
|
||||
-- for `workspace` plugin:
|
||||
package.loaded["plugins.console.view"] = ConsoleView
|
||||
|
||||
console.clear()
|
||||
return console
|
|
@ -1,271 +0,0 @@
|
|||
local core = require "core"
|
||||
local common = require "core.common"
|
||||
local config = require "core.config"
|
||||
local command = require "core.command"
|
||||
local keymap = require "core.keymap"
|
||||
local style = require "core.style"
|
||||
local Object = require "core.object"
|
||||
local RootView = require "core.rootview"
|
||||
|
||||
local border_width = 1
|
||||
local divider_width = 1
|
||||
local DIVIDER = {}
|
||||
|
||||
local ContextMenu = Object:extend()
|
||||
|
||||
ContextMenu.DIVIDER = DIVIDER
|
||||
|
||||
function ContextMenu:new()
|
||||
self.itemset = {}
|
||||
self.show_context_menu = false
|
||||
self.selected = -1
|
||||
self.height = 0
|
||||
self.position = { x = 0, y = 0 }
|
||||
end
|
||||
|
||||
local function get_item_size(item)
|
||||
local lw, lh
|
||||
if item == DIVIDER then
|
||||
lw = 0
|
||||
lh = divider_width
|
||||
else
|
||||
lw = style.font:get_width(item.text)
|
||||
if item.info then
|
||||
lw = lw + style.padding.x + style.font:get_width(item.info)
|
||||
end
|
||||
lh = style.font:get_height() + style.padding.y
|
||||
end
|
||||
return lw, lh
|
||||
end
|
||||
|
||||
function ContextMenu:register(predicate, items)
|
||||
if type(predicate) == "string" then
|
||||
predicate = require(predicate)
|
||||
end
|
||||
if type(predicate) == "table" then
|
||||
local class = predicate
|
||||
predicate = function() return core.active_view:is(class) end
|
||||
end
|
||||
|
||||
local width, height = 0, 0 --precalculate the size of context menu
|
||||
for i, item in ipairs(items) do
|
||||
if item ~= DIVIDER then
|
||||
item.info = keymap.reverse_map[item.command]
|
||||
end
|
||||
local lw, lh = get_item_size(item)
|
||||
width = math.max(width, lw)
|
||||
height = height + lh
|
||||
end
|
||||
width = width + style.padding.x * 2
|
||||
items.width, items.height = width, height
|
||||
table.insert(self.itemset, { predicate = predicate, items = items })
|
||||
end
|
||||
|
||||
function ContextMenu:show(x, y)
|
||||
self.items = nil
|
||||
for _, items in ipairs(self.itemset) do
|
||||
if items.predicate(x, y) then
|
||||
self.items = items.items
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if self.items then
|
||||
local w, h = self.items.width, self.items.height
|
||||
|
||||
-- by default the box is opened on the right and below
|
||||
if x + w >= core.root_view.size.x then
|
||||
x = x - w
|
||||
end
|
||||
if y + h >= core.root_view.size.y then
|
||||
y = y - h
|
||||
end
|
||||
|
||||
self.position.x, self.position.y = x, y
|
||||
self.show_context_menu = true
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
function ContextMenu:hide()
|
||||
self.show_context_menu = false
|
||||
self.items = nil
|
||||
self.selected = -1
|
||||
self.height = 0
|
||||
end
|
||||
|
||||
function ContextMenu:each_item()
|
||||
local x, y, w = self.position.x, self.position.y, self.items.width
|
||||
local oy = y
|
||||
return coroutine.wrap(function()
|
||||
for i, item in ipairs(self.items) do
|
||||
local _, lh = get_item_size(item)
|
||||
if y - oy > self.height then break end
|
||||
coroutine.yield(i, item, x, y, w, lh)
|
||||
y = y + lh
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
function ContextMenu:on_mouse_moved(px, py)
|
||||
if not self.show_context_menu then return end
|
||||
|
||||
for i, item, x, y, w, h in self:each_item() do
|
||||
if px > x and px <= x + w and py > y and py <= y + h then
|
||||
system.set_cursor("arrow")
|
||||
self.selected = i
|
||||
return true
|
||||
end
|
||||
end
|
||||
self.selected = -1
|
||||
return true
|
||||
end
|
||||
|
||||
function ContextMenu:on_selected(item)
|
||||
if type(item.command) == "string" then
|
||||
command.perform(item.command)
|
||||
else
|
||||
item.command()
|
||||
end
|
||||
end
|
||||
|
||||
function ContextMenu:on_mouse_pressed(button, x, y, clicks)
|
||||
local selected = (self.items or {})[self.selected]
|
||||
local caught = false
|
||||
|
||||
self:hide()
|
||||
if button == "left" then
|
||||
if selected then
|
||||
self:on_selected(selected)
|
||||
caught = true
|
||||
end
|
||||
end
|
||||
|
||||
if button == "right" then
|
||||
caught = self:show(x, y)
|
||||
end
|
||||
return caught
|
||||
end
|
||||
|
||||
-- copied from core.docview
|
||||
function ContextMenu:move_towards(t, k, dest, rate)
|
||||
if type(t) ~= "table" then
|
||||
return self:move_towards(self, t, k, dest, rate)
|
||||
end
|
||||
local val = t[k]
|
||||
if math.abs(val - dest) < 0.5 then
|
||||
t[k] = dest
|
||||
else
|
||||
t[k] = common.lerp(val, dest, rate or 0.5)
|
||||
end
|
||||
if val ~= dest then
|
||||
core.redraw = true
|
||||
end
|
||||
end
|
||||
|
||||
function ContextMenu:update()
|
||||
if self.show_context_menu then
|
||||
self:move_towards("height", self.items.height)
|
||||
end
|
||||
end
|
||||
|
||||
function ContextMenu:draw()
|
||||
if not self.show_context_menu then return end
|
||||
core.root_view:defer_draw(self.draw_context_menu, self)
|
||||
end
|
||||
|
||||
function ContextMenu:draw_context_menu()
|
||||
if not self.items then return end
|
||||
local bx, by, bw, bh = self.position.x, self.position.y, self.items.width, self.height
|
||||
|
||||
renderer.draw_rect(
|
||||
bx - border_width,
|
||||
by - border_width,
|
||||
bw + (border_width * 2),
|
||||
bh + (border_width * 2),
|
||||
style.divider
|
||||
)
|
||||
renderer.draw_rect(bx, by, bw, bh, style.background3)
|
||||
|
||||
for i, item, x, y, w, h in self:each_item() do
|
||||
if item == DIVIDER then
|
||||
renderer.draw_rect(x, y, w, h, style.caret)
|
||||
else
|
||||
if i == self.selected then
|
||||
renderer.draw_rect(x, y, w, h, style.selection)
|
||||
end
|
||||
|
||||
common.draw_text(style.font, style.text, item.text, "left", x + style.padding.x, y, w, h)
|
||||
if item.info then
|
||||
common.draw_text(style.font, style.dim, item.info, "right", x, y, w - style.padding.x, h)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local menu = ContextMenu()
|
||||
local root_view_on_mouse_pressed = RootView.on_mouse_pressed
|
||||
local root_view_on_mouse_moved = RootView.on_mouse_moved
|
||||
local root_view_update = RootView.update
|
||||
local root_view_draw = RootView.draw
|
||||
|
||||
function RootView:on_mouse_moved(...)
|
||||
if menu:on_mouse_moved(...) then return end
|
||||
root_view_on_mouse_moved(self, ...)
|
||||
end
|
||||
|
||||
-- copied from core.rootview
|
||||
function RootView:on_mouse_pressed(button, x,y, clicks)
|
||||
local div = self.root_node:get_divider_overlapping_point(x, y)
|
||||
if div then
|
||||
self.dragged_divider = div
|
||||
return
|
||||
end
|
||||
local node = self.root_node:get_child_overlapping_point(x, y)
|
||||
local idx = node:get_tab_overlapping_point(x, y)
|
||||
if idx then
|
||||
node:set_active_view(node.views[idx])
|
||||
if button == "right" then --< @r-lyeh middle>right
|
||||
node:close_active_view(self.root_node)
|
||||
end
|
||||
else
|
||||
core.set_active_view(node.active_view)
|
||||
-- send to context menu first
|
||||
if not menu:on_mouse_pressed(button, x, y, clicks) then
|
||||
node.active_view:on_mouse_pressed(button, x, y, clicks)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function RootView:update(...)
|
||||
root_view_update(self, ...)
|
||||
menu:update()
|
||||
end
|
||||
|
||||
function RootView:draw(...)
|
||||
root_view_draw(self, ...)
|
||||
menu:draw()
|
||||
end
|
||||
|
||||
command.add(nil, {
|
||||
["context:show"] = function()
|
||||
menu:show(core.active_view.position.x, core.active_view.position.y)
|
||||
end
|
||||
})
|
||||
|
||||
keymap.add {
|
||||
["menu"] = "context:show"
|
||||
}
|
||||
|
||||
-- register some sensible defaults
|
||||
menu:register("core.docview", {
|
||||
{ text = "Cut", command = "doc:cut" },
|
||||
{ text = "Copy", command = "doc:copy" },
|
||||
{ text = "Paste", command = "doc:paste" },
|
||||
DIVIDER,
|
||||
{ text = "Command Palette...", command = "core:find-command" }
|
||||
})
|
||||
|
||||
return menu
|
|
@ -1,63 +0,0 @@
|
|||
local core = require "core"
|
||||
local command = require "core.command"
|
||||
local config = require "core.config"
|
||||
local DocView = require "core.docview"
|
||||
local Doc = require "core.doc"
|
||||
|
||||
local cache = setmetatable({}, { __mode = "k" })
|
||||
|
||||
|
||||
local function detect_indent(doc)
|
||||
for _, text in ipairs(doc.lines) do
|
||||
local str = text:match("^ +")
|
||||
if str then return "soft", #str end
|
||||
local str = text:match("^\t+")
|
||||
if str then return "hard" end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local function update_cache(doc)
|
||||
local type, size = detect_indent(doc)
|
||||
if type then
|
||||
cache[doc] = { type = type, size = size }
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local new = Doc.new
|
||||
function Doc:new(...)
|
||||
new(self, ...)
|
||||
update_cache(self)
|
||||
end
|
||||
|
||||
local clean = Doc.clean
|
||||
function Doc:clean(...)
|
||||
clean(self, ...)
|
||||
update_cache(self)
|
||||
end
|
||||
|
||||
|
||||
local function with_indent_override(doc, fn, ...)
|
||||
local c = cache[doc]
|
||||
if not c then
|
||||
return fn(...)
|
||||
end
|
||||
local type, size = config.tab_type, config.indent_size
|
||||
config.tab_type, config.indent_size = c.type, c.size or config.indent_size
|
||||
local r1, r2, r3 = fn(...)
|
||||
config.tab_type, config.indent_size = type, size
|
||||
return r1, r2, r3
|
||||
end
|
||||
|
||||
|
||||
local perform = command.perform
|
||||
function command.perform(...)
|
||||
return with_indent_override(core.active_view.doc, perform, ...)
|
||||
end
|
||||
|
||||
|
||||
local draw = DocView.draw
|
||||
function DocView:draw(...)
|
||||
return with_indent_override(self.doc, draw, self, ...)
|
||||
end
|
|
@ -1,37 +0,0 @@
|
|||
local common = require "core.common"
|
||||
local config = require "core.config"
|
||||
local style = require "core.style"
|
||||
local DocView = require "core.docview"
|
||||
local command = require "core.command"
|
||||
|
||||
-- originally written by luveti
|
||||
|
||||
config.whitespace_map = { [" "] = "·", ["\t"] = "»" }
|
||||
config.draw_whitespace = true
|
||||
|
||||
local draw_line_text = DocView.draw_line_text
|
||||
|
||||
function DocView:draw_line_text(idx, x, y)
|
||||
draw_line_text(self, idx, x, y)
|
||||
if not config.draw_whitespace then return end
|
||||
|
||||
local text = self.doc.lines[idx]
|
||||
local tx, ty = x, y + self:get_line_text_y_offset()
|
||||
local font = self:get_font()
|
||||
local color = style.whitespace or style.syntax.comment
|
||||
local map = config.whitespace_map
|
||||
|
||||
for chr in common.utf8_chars(text) do
|
||||
local rep = map[chr]
|
||||
if rep then
|
||||
renderer.draw_text(font, rep, tx, ty, color)
|
||||
end
|
||||
tx = tx + font:get_width(chr)
|
||||
end
|
||||
end
|
||||
|
||||
command.add("core.docview", {
|
||||
["draw-whitespace:toggle"] = function() config.draw_whitespace = not config.draw_whitespace end,
|
||||
["draw-whitespace:disable"] = function() config.draw_whitespace = false end,
|
||||
["draw-whitespace:enable"] = function() config.draw_whitespace = true end,
|
||||
})
|
|
@ -1,29 +0,0 @@
|
|||
local core = require "core"
|
||||
local command = require "core.command"
|
||||
local Doc = require "core.doc"
|
||||
|
||||
local function eof_newline(doc)
|
||||
local leof,neof = #doc.lines,#doc.lines
|
||||
for i = leof,1,-1 do
|
||||
if not string.match(doc.lines[i],"^%s*$") then break end
|
||||
neof = i
|
||||
end
|
||||
local eol,_ = string.find(doc.lines[neof],"\n")
|
||||
if eol then
|
||||
doc:remove(neof,eol,math.huge,math.huge)
|
||||
return
|
||||
end
|
||||
doc:insert(neof,math.huge,"\n")
|
||||
end
|
||||
|
||||
command.add("core.docview", {
|
||||
["eof-newline:eof-newline"] = function()
|
||||
eof_newline(core.active_view.doc)
|
||||
end,
|
||||
})
|
||||
|
||||
local save = Doc.save
|
||||
Doc.save = function(self, ...)
|
||||
eof_newline(self)
|
||||
save(self, ...)
|
||||
end
|
|
@ -1,167 +0,0 @@
|
|||
-- mod-version:1 -- lite-xl 1.16
|
||||
local core = require "core"
|
||||
local common = require "core.common"
|
||||
local command = require "core.command"
|
||||
|
||||
local fsutils = {}
|
||||
|
||||
function fsutils.iterdir(dir)
|
||||
local stack = { dir }
|
||||
return function()
|
||||
local path = table.remove(stack)
|
||||
if not path then return end
|
||||
|
||||
for _, file in ipairs(system.list_dir(path) or {}) do
|
||||
stack[#stack + 1] = path .. PATHSEP .. file
|
||||
end
|
||||
|
||||
return path, system.get_file_info(path)
|
||||
end
|
||||
end
|
||||
|
||||
function fsutils.delete(dir, yield)
|
||||
local dirs = {}
|
||||
local n = 0
|
||||
for filename, stat in fsutils.iterdir(dir) do
|
||||
if stat.type == "dir" then
|
||||
-- this will later allow us to delete the dirs in correct sequence
|
||||
table.insert(dirs, filename)
|
||||
else
|
||||
os.remove(filename)
|
||||
if yield then
|
||||
n = n + 1
|
||||
coroutine.yield(n)
|
||||
end
|
||||
end
|
||||
end
|
||||
for i = #dirs, 1, -1 do
|
||||
os.remove(dirs[i])
|
||||
if yield then
|
||||
n = n + 1
|
||||
coroutine.yield(n)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function fsutils.move(oldname, newname)
|
||||
os.rename(oldname, newname)
|
||||
end
|
||||
|
||||
function fsutils.split(path)
|
||||
local segments = {}
|
||||
local pos = 1
|
||||
while true do
|
||||
local s, e = string.find(path, "[/\\]+", pos)
|
||||
if not s then break end
|
||||
table.insert(segments, string.sub(path, pos, s - 1))
|
||||
pos = e + 1
|
||||
end
|
||||
table.insert(list, string.sub(path, pos))
|
||||
|
||||
if segments[#segments] == '' then
|
||||
table.remove(segments)
|
||||
end
|
||||
|
||||
return segments
|
||||
end
|
||||
|
||||
function fsutils.normalize(path)
|
||||
return table.concat(fsutils.split(path), PATHSEP)
|
||||
end
|
||||
|
||||
function fsutils.normalize_posix(path)
|
||||
return table.concat(fsutils.split(path), '/')
|
||||
end
|
||||
|
||||
function fsutils.mkdir(path)
|
||||
local segments = fsutils.split(path)
|
||||
if system.mkdir then
|
||||
local p = ""
|
||||
for i = 1, #segments do
|
||||
p = table.concat(segments, PATHSEP, 1, i)
|
||||
end
|
||||
|
||||
if p == "" then
|
||||
return nil, "path empty", p
|
||||
end
|
||||
|
||||
local stat = system.get_file_info(p)
|
||||
if stat and stat.type == "file" then
|
||||
return nil, "path exists as a file", p
|
||||
end
|
||||
local ok, err = system.mkdir(p)
|
||||
if not ok then
|
||||
return nil, err, p
|
||||
end
|
||||
else
|
||||
-- just wing it lol
|
||||
system.exec(string.format(PLATFORM == "Windows" and "setlocal enableextensions & mkdir %q" or "mkdir -p %q", fsutils.normalize(path)))
|
||||
end
|
||||
end
|
||||
|
||||
local function async_exec(f, cb)
|
||||
cb = cb or function() end
|
||||
local co = coroutine.create(f)
|
||||
local function resolve(...)
|
||||
local ok, exec_body = coroutine.resume(co, ...)
|
||||
if not ok then
|
||||
error(debug.traceback(co, exec_body))
|
||||
end
|
||||
if coroutine.status(co) ~= "dead" then
|
||||
exec_body(resolve)
|
||||
end
|
||||
end
|
||||
resolve(cb)
|
||||
end
|
||||
|
||||
local function prompt(text, suggest)
|
||||
return coroutine.yield(function(resolve)
|
||||
core.command_view:enter(text, resolve, suggest)
|
||||
end)
|
||||
end
|
||||
|
||||
command.add(nil, {
|
||||
["files:delete"] = function()
|
||||
async_exec(function()
|
||||
local path = prompt("Delete", common.path_suggest)
|
||||
|
||||
core.add_thread(function()
|
||||
-- we use a wrapping coroutine to get status
|
||||
local function delete()
|
||||
return coroutine.wrap(function() fsutils.delete(path, true) end)
|
||||
end
|
||||
|
||||
for n in delete() do
|
||||
if n % 100 == 0 then
|
||||
core.log("Deleted %d items...", n)
|
||||
coroutine.yield()
|
||||
end
|
||||
end
|
||||
core.log("%q deleted.", path)
|
||||
end)
|
||||
end)
|
||||
end,
|
||||
["files:move"] = function()
|
||||
async_exec(function()
|
||||
local oldname = prompt("Move", common.path_suggest)
|
||||
local newname = prompt("To", common.path_suggest)
|
||||
|
||||
fsutils.move(oldname, newname)
|
||||
core.log("Moved %q to %q", oldname, newname)
|
||||
end)
|
||||
end
|
||||
})
|
||||
|
||||
if not command.map["files:create-directory"] then
|
||||
command.add(nil, {
|
||||
["files:create-directory"] = function()
|
||||
async_exec(function()
|
||||
local path = prompt("Name", common.path_suggest)
|
||||
fsutils.mkdir(path)
|
||||
core.log("%q created.", path)
|
||||
end)
|
||||
end
|
||||
})
|
||||
end
|
||||
|
||||
return fsutils
|
|
@ -1,45 +0,0 @@
|
|||
local style = require "core.style"
|
||||
local config = require "core.config"
|
||||
local DocView = require "core.docview"
|
||||
|
||||
|
||||
local function get_line_spaces(doc, idx, dir)
|
||||
local text = doc.lines[idx]
|
||||
if not text then
|
||||
return 0
|
||||
end
|
||||
local s, e = text:find("^%s*")
|
||||
if e == #text then
|
||||
return get_line_spaces(doc, idx + dir, dir)
|
||||
end
|
||||
local n = 0
|
||||
for i = s, e do
|
||||
n = n + (text:byte(i) == 9 and config.indent_size or 1)
|
||||
end
|
||||
return n
|
||||
end
|
||||
|
||||
|
||||
local function get_line_indent_guide_spaces(doc, idx)
|
||||
if doc.lines[idx]:find("^%s*\n") then
|
||||
return math.max(
|
||||
get_line_spaces(doc, idx - 1, -1),
|
||||
get_line_spaces(doc, idx + 1, 1))
|
||||
end
|
||||
return get_line_spaces(doc, idx)
|
||||
end
|
||||
|
||||
|
||||
local draw_line_text = DocView.draw_line_text
|
||||
|
||||
function DocView:draw_line_text(idx, x, y)
|
||||
local spaces = get_line_indent_guide_spaces(self.doc, idx)
|
||||
local sw = self:get_font():get_width(" ")
|
||||
local w = math.ceil(1 * SCALE)
|
||||
local h = self:get_line_height()
|
||||
for i = 0, spaces - 1, config.indent_size do
|
||||
local color = style.guide or style.selection
|
||||
renderer.draw_rect(x + sw * i, y, w, h, color)
|
||||
end
|
||||
draw_line_text(self, idx, x, y)
|
||||
end
|
|
@ -1,64 +0,0 @@
|
|||
local core = require "core"
|
||||
local command = require "core.command"
|
||||
local config = require "core.config"
|
||||
local keymap = require "core.keymap"
|
||||
|
||||
config.lfautoinsert_map = {
|
||||
["{%s*\n"] = "}",
|
||||
["%(%s*\n"] = ")",
|
||||
["%f[[]%[%s*\n"] = "]",
|
||||
["%[%[%s*\n"] = "]]",
|
||||
["=%s*\n"] = false,
|
||||
[":%s*\n"] = false,
|
||||
["^#if.*\n"] = "#endif",
|
||||
["^#else.*\n"] = "#endif",
|
||||
["%f[%w]do%s*\n"] = "end",
|
||||
["%f[%w]then%s*\n"] = "end",
|
||||
["%f[%w]else%s*\n"] = "end",
|
||||
["%f[%w]repeat%s*\n"] = "until",
|
||||
["%f[%w]function.*%)%s*\n"] = "end",
|
||||
["^%s*<([^/][^%s>]*)[^>]*>%s*\n"] = "</$TEXT>",
|
||||
}
|
||||
|
||||
|
||||
local function indent_size(doc, line)
|
||||
local text = doc.lines[line] or ""
|
||||
local s, e = text:find("^[\t ]*")
|
||||
return e - s
|
||||
end
|
||||
|
||||
command.add("core.docview", {
|
||||
["autoinsert:newline"] = function()
|
||||
command.perform("doc:newline")
|
||||
|
||||
local doc = core.active_view.doc
|
||||
local line, col = doc:get_selection()
|
||||
local text = doc.lines[line - 1]
|
||||
|
||||
for ptn, close in pairs(config.lfautoinsert_map) do
|
||||
local s, _, str = text:find(ptn)
|
||||
if s then
|
||||
if close
|
||||
and col == #doc.lines[line]
|
||||
and indent_size(doc, line + 1) <= indent_size(doc, line - 1)
|
||||
then
|
||||
close = str and close:gsub("$TEXT", str) or close
|
||||
command.perform("doc:newline")
|
||||
core.active_view:on_text_input(close)
|
||||
command.perform("doc:move-to-previous-line")
|
||||
if doc.lines[line+1] == doc.lines[line+2] then
|
||||
doc:remove(line+1, 1, line+2, 1)
|
||||
end
|
||||
elseif col < #doc.lines[line] then
|
||||
command.perform("doc:newline")
|
||||
command.perform("doc:move-to-previous-line")
|
||||
end
|
||||
command.perform("doc:indent")
|
||||
end
|
||||
end
|
||||
end
|
||||
})
|
||||
|
||||
keymap.add {
|
||||
["return"] = { "command:submit", "autoinsert:newline" }
|
||||
}
|
|
@ -1,18 +0,0 @@
|
|||
local config = require "core.config"
|
||||
local style = require "core.style"
|
||||
local DocView = require "core.docview"
|
||||
|
||||
local draw = DocView.draw
|
||||
|
||||
function DocView:draw(...)
|
||||
draw(self, ...)
|
||||
|
||||
local offset = self:get_font():get_width("n") * config.line_limit
|
||||
local x = self:get_line_screen_position(1) + offset
|
||||
local y = self.position.y
|
||||
local w = math.ceil(SCALE * 1)
|
||||
local h = self.size.y
|
||||
|
||||
local color = style.guide or style.selection
|
||||
renderer.draw_rect(x, y, w, h, color)
|
||||
end
|
|
@ -1,70 +0,0 @@
|
|||
-- mod-version:3
|
||||
local core = require "core"
|
||||
local command = require "core.command"
|
||||
local keymap = require "core.keymap"
|
||||
|
||||
local handled_events = {
|
||||
["keypressed"] = true,
|
||||
["keyreleased"] = true,
|
||||
["textinput"] = true,
|
||||
}
|
||||
|
||||
local state = "stopped"
|
||||
local event_buffer = {}
|
||||
local modkeys = {}
|
||||
|
||||
local on_event = core.on_event
|
||||
|
||||
core.on_event = function(type, ...)
|
||||
local res = on_event(type, ...)
|
||||
if state == "recording" and handled_events[type] then
|
||||
table.insert(event_buffer, { type, ... })
|
||||
end
|
||||
return res
|
||||
end
|
||||
|
||||
|
||||
local function clone(t)
|
||||
local res = {}
|
||||
for k, v in pairs(t) do res[k] = v end
|
||||
return res
|
||||
end
|
||||
|
||||
|
||||
local function predicate()
|
||||
return state ~= "playing"
|
||||
end
|
||||
|
||||
|
||||
command.add(predicate, {
|
||||
["macro:toggle-record"] = function()
|
||||
if state == "stopped" then
|
||||
state = "recording"
|
||||
event_buffer = {}
|
||||
modkeys = clone(keymap.modkeys)
|
||||
core.log("Recording macro...")
|
||||
else
|
||||
state = "stopped"
|
||||
core.log("Stopped recording macro (%d events)", #event_buffer)
|
||||
end
|
||||
end,
|
||||
|
||||
["macro:play"] = function()
|
||||
state = "playing"
|
||||
core.log("Playing macro... (%d events)", #event_buffer)
|
||||
local mk = keymap.modkeys
|
||||
keymap.modkeys = clone(modkeys)
|
||||
for _, ev in ipairs(event_buffer) do
|
||||
on_event(table.unpack(ev))
|
||||
core.root_view:update()
|
||||
end
|
||||
keymap.modkeys = mk
|
||||
state = "stopped"
|
||||
end,
|
||||
})
|
||||
|
||||
|
||||
keymap.add {
|
||||
["ctrl+shift+;"] = "macro:toggle-record",
|
||||
["ctrl+;"] = "macro:play",
|
||||
}
|
|
@ -1,103 +0,0 @@
|
|||
-- Markers plugin for lite text editor
|
||||
-- original implementation by Petri Häkkinen
|
||||
|
||||
local core = require "core"
|
||||
local command = require "core.command"
|
||||
local keymap = require "core.keymap"
|
||||
local style = require "core.style"
|
||||
local DocView = require "core.docview"
|
||||
local Doc = require "core.doc"
|
||||
|
||||
local cache = {} -- this table contains subtables for each document, each subtable is a set of line numbers
|
||||
setmetatable(cache, {
|
||||
__mode = "k",
|
||||
__index = function(t, k)
|
||||
t[k] = {}
|
||||
return t[k]
|
||||
end,
|
||||
})
|
||||
|
||||
|
||||
local function shift_lines(doc, at, diff)
|
||||
if diff == 0 then return end
|
||||
local t = {}
|
||||
for line in pairs(cache[doc]) do
|
||||
line = line >= at and line + diff or line
|
||||
t[line] = true
|
||||
end
|
||||
cache[doc] = t
|
||||
end
|
||||
|
||||
|
||||
local raw_insert = Doc.raw_insert
|
||||
|
||||
function Doc:raw_insert(line, col, text, ...)
|
||||
raw_insert(self, line, col, text, ...)
|
||||
local line_count = 0
|
||||
for _ in text:gmatch("\n") do
|
||||
line_count = line_count + 1
|
||||
end
|
||||
shift_lines(self, line, line_count)
|
||||
end
|
||||
|
||||
|
||||
local raw_remove = Doc.raw_remove
|
||||
|
||||
function Doc:raw_remove(line1, col1, line2, col2, ...)
|
||||
raw_remove(self, line1, col1, line2, col2, ...)
|
||||
shift_lines(self, line2, line1 - line2)
|
||||
end
|
||||
|
||||
|
||||
local draw_line_gutter = DocView.draw_line_gutter
|
||||
|
||||
function DocView:draw_line_gutter(idx, x, y)
|
||||
if cache[self.doc] and cache[self.doc][idx] then
|
||||
local h = self:get_line_height()
|
||||
renderer.draw_rect(x, y, style.padding.x * 0.4, h, style.selection)
|
||||
end
|
||||
draw_line_gutter(self, idx, x, y)
|
||||
end
|
||||
|
||||
|
||||
command.add("core.docview", {
|
||||
["markers:toggle-marker"] = function()
|
||||
local doc = core.active_view.doc
|
||||
local line = doc:get_selection()
|
||||
local markers = cache[doc]
|
||||
|
||||
if markers[line] then
|
||||
markers[line] = nil
|
||||
else
|
||||
markers[line] = true
|
||||
end
|
||||
end,
|
||||
|
||||
["markers:go-to-next-marker"] = function()
|
||||
local doc = core.active_view.doc
|
||||
local line = doc:get_selection()
|
||||
local markers = cache[doc]
|
||||
|
||||
local first_marker = math.huge
|
||||
local next_marker = math.huge
|
||||
for l, _ in pairs(markers) do
|
||||
if l > line and l < next_marker then
|
||||
next_marker = l
|
||||
end
|
||||
first_marker = math.min(first_marker, l)
|
||||
end
|
||||
if next_marker == math.huge then
|
||||
next_marker = first_marker
|
||||
end
|
||||
if next_marker ~= math.huge then
|
||||
doc:set_selection(next_marker, 1)
|
||||
core.active_view:scroll_to_line(next_marker, true)
|
||||
end
|
||||
end,
|
||||
})
|
||||
|
||||
|
||||
keymap.add {
|
||||
["ctrl+f2"] = "markers:toggle-marker",
|
||||
["f2"] = "markers:go-to-next-marker",
|
||||
}
|
|
@ -1,295 +0,0 @@
|
|||
local command = require "core.command"
|
||||
local common = require "core.common"
|
||||
local config = require "core.config"
|
||||
local style = require "core.style"
|
||||
local DocView = require "core.docview"
|
||||
|
||||
-- General plugin settings
|
||||
config.minimap_enabled = true
|
||||
config.minimap_width = 100
|
||||
config.minimap_instant_scroll = false
|
||||
config.minimap_syntax_highlight = true
|
||||
config.minimap_scale = 1
|
||||
config.minimap_draw_background = true
|
||||
|
||||
-- Configure size for rendering each char in the minimap
|
||||
local char_height = 1 * SCALE * config.minimap_scale
|
||||
local char_spacing = 0.8 * SCALE * config.minimap_scale
|
||||
local line_spacing = 2 * SCALE * config.minimap_scale
|
||||
|
||||
-- Overloaded since the default implementation adds a extra x3 size of hotspot for the mouse to hit the scrollbar.
|
||||
local prev_scrollbar_overlaps_point = DocView.scrollbar_overlaps_point
|
||||
DocView.scrollbar_overlaps_point = function(self, x, y)
|
||||
if not config.minimap_enabled then return prev_scrollbar_overlaps_point(self, x, y) end
|
||||
|
||||
local sx, sy, sw, sh = self:get_scrollbar_rect()
|
||||
return x >= sx and x < sx + sw and y >= sy and y < sy + sh
|
||||
end
|
||||
|
||||
-- Helper function to determine if current file is too large to be shown fully inside the minimap area.
|
||||
local function is_file_too_large(self)
|
||||
local line_count = #self.doc.lines
|
||||
local _, _, _, sh = self:get_scrollbar_rect()
|
||||
|
||||
-- check if line count is too large to fit inside the minimap area
|
||||
local max_minmap_lines = math.floor(sh / line_spacing)
|
||||
return line_count > 1 and line_count > max_minmap_lines
|
||||
end
|
||||
|
||||
-- Overloaded with an extra check if the user clicked inside the minimap to automatically scroll to that line.
|
||||
local prev_on_mouse_pressed = DocView.on_mouse_pressed
|
||||
DocView.on_mouse_pressed = function(self, button, x, y, clicks)
|
||||
if not config.minimap_enabled then return prev_on_mouse_pressed(self, button, x, y, clicks) end
|
||||
|
||||
-- check if user clicked in the minimap area and jump directly to that line
|
||||
-- unless they are actually trying to perform a drag
|
||||
local minimap_hit = self:scrollbar_overlaps_point(x, y)
|
||||
if minimap_hit then
|
||||
local line_count = #self.doc.lines
|
||||
local minimap_height = line_count * line_spacing
|
||||
|
||||
-- check if line count is too large to fit inside the minimap area
|
||||
local is_too_large = is_file_too_large(self)
|
||||
if is_too_large then
|
||||
local _, _, _, sh = self:get_scrollbar_rect()
|
||||
minimap_height = sh
|
||||
end
|
||||
|
||||
-- calc which line to jump to
|
||||
local dy = y - self.position.y
|
||||
local jump_to_line = math.floor((dy / minimap_height) * line_count) + 1
|
||||
|
||||
local _, cy, _, cy2 = self:get_content_bounds()
|
||||
local lh = self:get_line_height()
|
||||
local visible_lines_count = math.max(1, (cy2 - cy) / lh)
|
||||
local visible_lines_start = math.max(1, math.floor(cy / lh))
|
||||
|
||||
-- calc if user hit the currently visible area
|
||||
local hit_visible_area = true
|
||||
if is_too_large then
|
||||
|
||||
local visible_height = visible_lines_count * line_spacing
|
||||
local scroll_pos = (visible_lines_start-1) / (line_count - visible_lines_count - 1)
|
||||
scroll_pos = math.min(1.0, scroll_pos) -- 0..1
|
||||
local visible_y = self.position.y + scroll_pos * (minimap_height - visible_height)
|
||||
|
||||
local t = (line_count - visible_lines_start) / visible_lines_count
|
||||
if t <= 1 then
|
||||
visible_y = visible_y + visible_height * (1.0 - t)
|
||||
end
|
||||
|
||||
if y < visible_y or y > visible_y + visible_height then
|
||||
hit_visible_area = false
|
||||
end
|
||||
else
|
||||
|
||||
-- If the click is on the currently visible line numbers,
|
||||
-- ignore it since then they probably want to initiate a drag instead.
|
||||
if jump_to_line < visible_lines_start or jump_to_line > visible_lines_start + visible_lines_count then
|
||||
hit_visible_area = false
|
||||
end
|
||||
end
|
||||
|
||||
-- if user didn't click on the visible area (ie not dragging), scroll accordingly
|
||||
if not hit_visible_area then
|
||||
self:scroll_to_line(jump_to_line, false, config.minimap_instant_scroll)
|
||||
return
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
return prev_on_mouse_pressed(self, button, x, y, clicks)
|
||||
end
|
||||
|
||||
-- Overloaded with pretty much the same logic as original DocView implementation,
|
||||
-- with the exception of the dragging scrollbar delta. We want it to behave a bit snappier
|
||||
-- since the "scrollbar" essentially represents the lines visible in the content view.
|
||||
local prev_on_mouse_moved = DocView.on_mouse_moved
|
||||
DocView.on_mouse_moved = function(self, x, y, dx, dy)
|
||||
if not config.minimap_enabled then return prev_on_mouse_moved(self, x, y, dx, dy) end
|
||||
|
||||
if self.dragging_scrollbar then
|
||||
local line_count = #self.doc.lines
|
||||
local lh = self:get_line_height()
|
||||
local delta = lh / line_spacing * dy
|
||||
|
||||
if is_file_too_large(self) then
|
||||
local _, sy, _, sh = self:get_scrollbar_rect()
|
||||
delta = (line_count * lh) / sh * dy
|
||||
end
|
||||
|
||||
self.scroll.to.y = self.scroll.to.y + delta
|
||||
end
|
||||
|
||||
-- we need to "hide" that the scrollbar is dragging so that View doesnt does its own scrolling logic
|
||||
local t = self.dragging_scrollbar
|
||||
self.dragging_scrollbar = false
|
||||
local r = prev_on_mouse_moved(self, x, y, dx, dy)
|
||||
self.dragging_scrollbar = t
|
||||
return r
|
||||
end
|
||||
|
||||
-- Overloaded since we want the mouse to interact with the full size of the minimap area,
|
||||
-- not juse the scrollbar.
|
||||
local prev_get_scrollbar_rect = DocView.get_scrollbar_rect
|
||||
DocView.get_scrollbar_rect = function (self)
|
||||
if not config.minimap_enabled then return prev_get_scrollbar_rect(self) end
|
||||
|
||||
return
|
||||
self.position.x + self.size.x - config.minimap_width * SCALE,
|
||||
self.position.y,
|
||||
config.minimap_width * SCALE,
|
||||
self.size.y
|
||||
end
|
||||
|
||||
-- Overloaded so we can render the minimap in the "scrollbar area".
|
||||
local prev_draw_scrollbar = DocView.draw_scrollbar
|
||||
DocView.draw_scrollbar = function (self)
|
||||
if not config.minimap_enabled then return prev_draw_scrollbar(self) end
|
||||
|
||||
local x, y, w, h = self:get_scrollbar_rect()
|
||||
|
||||
local highlight = self.hovered_scrollbar or self.dragging_scrollbar
|
||||
local visual_color = highlight and style.scrollbar2 or style.scrollbar
|
||||
|
||||
local _, cy, _, cy2 = self:get_content_bounds()
|
||||
local lh = self:get_line_height()
|
||||
local visible_lines_count = math.max(1, (cy2 - cy) / lh)
|
||||
local visible_lines_start = math.max(1, math.floor(cy / lh))
|
||||
local scroller_height = visible_lines_count * line_spacing
|
||||
local line_count = #self.doc.lines
|
||||
|
||||
local visible_y = self.position.y + (visible_lines_start-1) * line_spacing
|
||||
|
||||
|
||||
-- check if file is too large to fit inside the minimap area
|
||||
local max_minmap_lines = math.floor(h / line_spacing)
|
||||
local minimap_start_line = 1
|
||||
if is_file_too_large(self) then
|
||||
|
||||
local scroll_pos = (visible_lines_start-1) / (line_count - visible_lines_count - 1)
|
||||
scroll_pos = math.min(1.0, scroll_pos) -- 0..1, procent of visual area scrolled
|
||||
|
||||
local scroll_pos_pixels = scroll_pos * (h - scroller_height)
|
||||
visible_y = self.position.y + scroll_pos_pixels
|
||||
|
||||
-- offset visible area if user is scrolling past end
|
||||
local t = (line_count - visible_lines_start) / visible_lines_count
|
||||
if t <= 1 then
|
||||
visible_y = visible_y + scroller_height * (1.0 - t)
|
||||
end
|
||||
|
||||
minimap_start_line = visible_lines_start - math.floor(scroll_pos_pixels / line_spacing)
|
||||
minimap_start_line = math.max(1, math.min(minimap_start_line, line_count - max_minmap_lines))
|
||||
end
|
||||
|
||||
if config.minimap_draw_background then
|
||||
renderer.draw_rect(x, y, w, h, style.minimap_background or style.background)
|
||||
end
|
||||
-- draw visual rect
|
||||
renderer.draw_rect(x, visible_y, w, scroller_height, visual_color)
|
||||
|
||||
-- time to draw the actual code, setup some local vars that are used in both highlighted and plain renderind.
|
||||
local line_y = y
|
||||
|
||||
-- when not using syntax highlighted rendering, just use the normal color but dim it 50%.
|
||||
local color = style.syntax["normal"]
|
||||
color = { color[1],color[2],color[3],color[4] * 0.5 }
|
||||
|
||||
-- we try to "batch" characters so that they can be rendered as just one rectangle instead of one for each.
|
||||
local batch_width = 0
|
||||
local batch_start = x
|
||||
local minimap_cutoff_x = x + config.minimap_width * SCALE
|
||||
|
||||
-- render lines with syntax highlighting
|
||||
if config.minimap_syntax_highlight then
|
||||
|
||||
-- keep track of the highlight type, since this needs to break batches as well
|
||||
local batch_syntax_type = nil
|
||||
|
||||
local function flush_batch(type)
|
||||
if batch_width > 0 then
|
||||
-- fetch and dim colors
|
||||
color = style.syntax[batch_syntax_type]
|
||||
color = { color[1], color[2], color[3], color[4] * 0.5 }
|
||||
renderer.draw_rect(batch_start, line_y, batch_width, char_height, color)
|
||||
end
|
||||
batch_syntax_type = type
|
||||
batch_start = batch_start + batch_width
|
||||
batch_width = 0
|
||||
end
|
||||
|
||||
-- per line
|
||||
local endidx = minimap_start_line + max_minmap_lines
|
||||
endidx = math.min(endidx, line_count)
|
||||
for idx=minimap_start_line,endidx do
|
||||
batch_syntax_type = nil
|
||||
batch_start = x
|
||||
batch_width = 0
|
||||
|
||||
-- per token
|
||||
for _, type, text in self.doc.highlighter:each_token(idx) do
|
||||
-- flush prev batch
|
||||
if not batch_syntax_type then batch_syntax_type = type end
|
||||
if batch_syntax_type ~= type then
|
||||
flush_batch(type)
|
||||
end
|
||||
|
||||
-- per character
|
||||
for char in common.utf8_chars(text) do
|
||||
if char == " " or char == "\n" then
|
||||
flush_batch(type)
|
||||
batch_start = batch_start + char_spacing
|
||||
elseif batch_start + batch_width > minimap_cutoff_x then
|
||||
flush_batch(type)
|
||||
break
|
||||
else
|
||||
batch_width = batch_width + char_spacing
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
flush_batch(nil)
|
||||
line_y = line_y + line_spacing
|
||||
end
|
||||
|
||||
else -- render lines without syntax highlighting
|
||||
|
||||
local function flush_batch()
|
||||
if batch_width > 0 then
|
||||
renderer.draw_rect(batch_start, line_y, batch_width, char_height, color)
|
||||
end
|
||||
batch_start = batch_start + batch_width
|
||||
batch_width = 0
|
||||
end
|
||||
|
||||
for idx=1,line_count-1 do
|
||||
batch_start = x
|
||||
batch_width = 0
|
||||
|
||||
for char in common.utf8_chars(self.doc.lines[idx]) do
|
||||
if char == " " or char == "\n" then
|
||||
flush_batch()
|
||||
batch_start = batch_start + char_spacing
|
||||
elseif batch_start + batch_width > minimap_cutoff_x then
|
||||
flush_batch()
|
||||
else
|
||||
batch_width = batch_width + char_spacing
|
||||
end
|
||||
end
|
||||
flush_batch()
|
||||
line_y = line_y + line_spacing
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
command.add(nil, {
|
||||
["minimap:toggle-visibility"] = function()
|
||||
config.minimap_enabled = not config.minimap_enabled
|
||||
end,
|
||||
["minimap:toggle-syntax-highlighting"] = function()
|
||||
config.minimap_syntax_highlight = not config.minimap_syntax_highlight
|
||||
end,
|
||||
})
|
|
@ -1,56 +0,0 @@
|
|||
local core = require "core"
|
||||
local config = require "core.config"
|
||||
local style = require "core.style"
|
||||
local Doc = require "core.doc"
|
||||
local DocView = require "core.docview"
|
||||
|
||||
config.motiontrail_steps = config.motiontrail_steps or 50
|
||||
|
||||
local function doc()
|
||||
return core.active_view.doc
|
||||
end
|
||||
|
||||
local function lerp(a, b, t)
|
||||
return a + (b - a) * t
|
||||
end
|
||||
|
||||
|
||||
local function get_caret_rect(dv, idx)
|
||||
local line1, col1, line2, col2 = doc():get_selection_idx(idx)
|
||||
local x1, y1 = dv:get_line_screen_position(line1)
|
||||
x1 = x1 + dv:get_col_x_offset(line1, col1)
|
||||
return x1, y1, style.caret_width, dv:get_line_height()
|
||||
end
|
||||
|
||||
|
||||
local last_x = {}
|
||||
local last_y = {}
|
||||
local last_view = {}
|
||||
|
||||
local draw = DocView.draw
|
||||
|
||||
function DocView:draw(...)
|
||||
draw(self, ...)
|
||||
if self ~= core.active_view then return end
|
||||
|
||||
for idx, line1, col1, line2, col2 in doc():get_selections(true, true) do
|
||||
--if line1 == line2 and col1 == col2 then return false end
|
||||
|
||||
local x, y, w, h = get_caret_rect(self, idx)
|
||||
|
||||
if last_view[idx] == self and (x ~= last_x[idx] or y ~= last_y[idx]) then
|
||||
local lx = x
|
||||
for i = 0, 1, 1 / config.motiontrail_steps do
|
||||
local ix = lerp(x, last_x[idx], i)
|
||||
local iy = lerp(y, last_y[idx], i)
|
||||
local iw = math.max(w, math.ceil(math.abs(ix - lx)))
|
||||
renderer.draw_rect(ix, iy, iw, h, style.caret)
|
||||
lx = ix
|
||||
end
|
||||
core.redraw = true
|
||||
end
|
||||
|
||||
last_view[idx], last_x[idx], last_y[idx] = self, x, y
|
||||
end
|
||||
end
|
||||
|
|
@ -1,30 +0,0 @@
|
|||
local core = require "core"
|
||||
local command = require "core.command"
|
||||
local config = require "core.config"
|
||||
|
||||
|
||||
if PLATFORM == "Windows" then
|
||||
config.filemanager = "explorer"
|
||||
elseif PLATFORM == "Mac OS X" then
|
||||
config.filemanager = "open"
|
||||
else
|
||||
config.filemanager = "xdg-open"
|
||||
end
|
||||
|
||||
|
||||
command.add("core.docview", {
|
||||
["open-file-location:open-file-location"] = function()
|
||||
local doc = core.active_view.doc
|
||||
if not doc.filename then
|
||||
core.error "Cannot open location of unsaved doc"
|
||||
return
|
||||
end
|
||||
local folder = doc.filename:match("^(.*)[/\\].*$") or "."
|
||||
core.log("Opening \"%s\"", folder)
|
||||
if PLATFORM == "Windows" then
|
||||
system.exec(string.format("%s %s", config.filemanager, folder))
|
||||
else
|
||||
system.exec(string.format("%s %q", config.filemanager, folder))
|
||||
end
|
||||
end
|
||||
})
|
|
@ -1,399 +0,0 @@
|
|||
-- mod-version:3
|
||||
local core = require "core"
|
||||
local common = require "core.common"
|
||||
local keymap = require "core.keymap"
|
||||
local command = require "core.command"
|
||||
local style = require "core.style"
|
||||
local View = require "core.view"
|
||||
|
||||
---@class plugins.projectsearch.resultsview : core.view
|
||||
local ResultsView = View:extend()
|
||||
|
||||
ResultsView.context = "session"
|
||||
|
||||
function ResultsView:new(path, text, fn)
|
||||
ResultsView.super.new(self)
|
||||
self.scrollable = true
|
||||
self.brightness = 0
|
||||
self:begin_search(path, text, fn)
|
||||
end
|
||||
|
||||
|
||||
function ResultsView:get_name()
|
||||
return "Search Results"
|
||||
end
|
||||
|
||||
|
||||
local function find_all_matches_in_file(t, filename, fn)
|
||||
local fp = io.open(filename)
|
||||
if not fp then return t end
|
||||
local n = 1
|
||||
for line in fp:lines() do
|
||||
local s = fn(line)
|
||||
if s then
|
||||
-- Insert maximum 256 characters. If we insert more, for compiled files, which can have very long lines
|
||||
-- things tend to get sluggish. If our line is longer than 80 characters, begin to truncate the thing.
|
||||
local start_index = math.max(s - 80, 1)
|
||||
table.insert(t, { file = filename, text = (start_index > 1 and "..." or "") .. line:sub(start_index, 256 + start_index), line = n, col = s })
|
||||
core.redraw = true
|
||||
end
|
||||
if n % 100 == 0 then coroutine.yield(0) end
|
||||
n = n + 1
|
||||
core.redraw = true
|
||||
end
|
||||
fp:close()
|
||||
end
|
||||
|
||||
|
||||
function ResultsView:begin_search(path, text, fn)
|
||||
self.search_args = { path, text, fn }
|
||||
self.results = {}
|
||||
self.last_file_idx = 1
|
||||
self.query = text
|
||||
self.searching = true
|
||||
self.selected_idx = 0
|
||||
|
||||
core.add_thread(function()
|
||||
local i = 1
|
||||
for dir_name, file in core.get_project_files() do
|
||||
if file.type == "file" and (not path or (dir_name .. "/" .. file.filename):find(path, 1, true) == 1) then
|
||||
local truncated_path = (dir_name == core.project_dir and "" or (dir_name .. PATHSEP))
|
||||
find_all_matches_in_file(self.results, truncated_path .. file.filename, fn)
|
||||
end
|
||||
self.last_file_idx = i
|
||||
i = i + 1
|
||||
end
|
||||
self.searching = false
|
||||
self.brightness = 100
|
||||
core.redraw = true
|
||||
end, self.results)
|
||||
|
||||
self.scroll.to.y = 0
|
||||
end
|
||||
|
||||
|
||||
function ResultsView:refresh()
|
||||
self:begin_search(table.unpack(self.search_args))
|
||||
end
|
||||
|
||||
|
||||
function ResultsView:on_mouse_moved(mx, my, ...)
|
||||
ResultsView.super.on_mouse_moved(self, mx, my, ...)
|
||||
self.selected_idx = 0
|
||||
for i, item, x,y,w,h in self:each_visible_result() do
|
||||
if mx >= x and my >= y and mx < x + w and my < y + h then
|
||||
self.selected_idx = i
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function ResultsView:on_mouse_pressed(...)
|
||||
local caught = ResultsView.super.on_mouse_pressed(self, ...)
|
||||
if not caught then
|
||||
return self:open_selected_result()
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function ResultsView:open_selected_result()
|
||||
local res = self.results[self.selected_idx]
|
||||
if not res then
|
||||
return
|
||||
end
|
||||
core.try(function()
|
||||
local dv = core.root_view:open_doc(core.open_doc(res.file))
|
||||
core.root_view.root_node:update_layout()
|
||||
dv.doc:set_selection(res.line, res.col)
|
||||
dv:scroll_to_line(res.line, false, true)
|
||||
end)
|
||||
return true
|
||||
end
|
||||
|
||||
|
||||
function ResultsView:update()
|
||||
self:move_towards("brightness", 0, 0.1)
|
||||
ResultsView.super.update(self)
|
||||
end
|
||||
|
||||
|
||||
function ResultsView:get_results_yoffset()
|
||||
return style.font:get_height() + style.padding.y * 3
|
||||
end
|
||||
|
||||
|
||||
function ResultsView:get_line_height()
|
||||
return style.padding.y + style.font:get_height()
|
||||
end
|
||||
|
||||
|
||||
function ResultsView:get_scrollable_size()
|
||||
return self:get_results_yoffset() + #self.results * self:get_line_height()
|
||||
end
|
||||
|
||||
|
||||
function ResultsView:get_visible_results_range()
|
||||
local lh = self:get_line_height()
|
||||
local oy = self:get_results_yoffset()
|
||||
local min = math.max(1, math.floor((self.scroll.y - oy) / lh))
|
||||
return min, min + math.floor(self.size.y / lh) + 1
|
||||
end
|
||||
|
||||
|
||||
function ResultsView:each_visible_result()
|
||||
return coroutine.wrap(function()
|
||||
local lh = self:get_line_height()
|
||||
local x, y = self:get_content_offset()
|
||||
local min, max = self:get_visible_results_range()
|
||||
y = y + self:get_results_yoffset() + lh * (min - 1)
|
||||
for i = min, max do
|
||||
local item = self.results[i]
|
||||
if not item then break end
|
||||
coroutine.yield(i, item, x, y, self.size.x, lh)
|
||||
y = y + lh
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
|
||||
function ResultsView:scroll_to_make_selected_visible()
|
||||
local h = self:get_line_height()
|
||||
local y = self:get_results_yoffset() + h * (self.selected_idx - 1)
|
||||
self.scroll.to.y = math.min(self.scroll.to.y, y)
|
||||
self.scroll.to.y = math.max(self.scroll.to.y, y + h - self.size.y)
|
||||
end
|
||||
|
||||
|
||||
function ResultsView:draw()
|
||||
self:draw_background(style.background)
|
||||
|
||||
-- status
|
||||
local ox, oy = self:get_content_offset()
|
||||
local x, y = ox + style.padding.x, oy + style.padding.y
|
||||
local files_number = core.project_files_number()
|
||||
local per = common.clamp(files_number and self.last_file_idx / files_number or 1, 0, 1)
|
||||
local text
|
||||
if self.searching then
|
||||
if files_number then
|
||||
text = string.format("Searching %.f%% (%d of %d files, %d matches) for %q...",
|
||||
per * 100, self.last_file_idx, files_number,
|
||||
#self.results, self.query)
|
||||
else
|
||||
text = string.format("Searching (%d files, %d matches) for %q...",
|
||||
self.last_file_idx, #self.results, self.query)
|
||||
end
|
||||
else
|
||||
text = string.format("Found %d matches for %q",
|
||||
#self.results, self.query)
|
||||
end
|
||||
local color = common.lerp(style.text, style.accent, self.brightness / 100)
|
||||
renderer.draw_text(style.font, text, x, y, color)
|
||||
|
||||
-- horizontal line
|
||||
local yoffset = self:get_results_yoffset()
|
||||
local x = ox + style.padding.x
|
||||
local w = self.size.x - style.padding.x * 2
|
||||
local h = style.divider_size
|
||||
local color = common.lerp(style.dim, style.text, self.brightness / 100)
|
||||
renderer.draw_rect(x, oy + yoffset - style.padding.y, w, h, color)
|
||||
if self.searching then
|
||||
renderer.draw_rect(x, oy + yoffset - style.padding.y, w * per, h, style.text)
|
||||
end
|
||||
|
||||
-- results
|
||||
local y1, y2 = self.position.y, self.position.y + self.size.y
|
||||
for i, item, x,y,w,h in self:each_visible_result() do
|
||||
local color = style.text
|
||||
if i == self.selected_idx then
|
||||
color = style.accent
|
||||
renderer.draw_rect(x, y, w, h, style.line_highlight)
|
||||
end
|
||||
x = x + style.padding.x
|
||||
local text = string.format("%s at line %d (col %d): ", item.file, item.line, item.col)
|
||||
x = common.draw_text(style.font, style.dim, text, "left", x, y, w, h)
|
||||
x = common.draw_text(style.code_font, color, item.text, "left", x, y, w, h)
|
||||
end
|
||||
|
||||
self:draw_scrollbar()
|
||||
end
|
||||
|
||||
|
||||
---@param path string
|
||||
---@param text string
|
||||
---@param fn fun(line_text:string):...
|
||||
---@return plugins.projectsearch.resultsview?
|
||||
local function begin_search(path, text, fn)
|
||||
if text == "" then
|
||||
core.error("Expected non-empty string")
|
||||
return
|
||||
end
|
||||
local rv = ResultsView(path, text, fn)
|
||||
core.root_view:get_active_node_default():add_view(rv)
|
||||
return rv
|
||||
end
|
||||
|
||||
|
||||
local function get_selected_text()
|
||||
local view = core.active_view
|
||||
local doc = (view and view.doc) and view.doc or nil
|
||||
if doc then
|
||||
return doc:get_text(table.unpack({ doc:get_selection() }))
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local function normalize_path(path)
|
||||
if not path then return nil end
|
||||
path = common.normalize_path(path)
|
||||
for i, project_dir in ipairs(core.project_directories) do
|
||||
if common.path_belongs_to(path, project_dir.name) then
|
||||
return project_dir.item.filename .. PATHSEP .. common.relative_path(project_dir.name, path)
|
||||
end
|
||||
end
|
||||
return path
|
||||
end
|
||||
|
||||
---@class plugins.projectsearch
|
||||
local projectsearch = {}
|
||||
|
||||
---@type plugins.projectsearch.resultsview
|
||||
projectsearch.ResultsView = ResultsView
|
||||
|
||||
---@param text string
|
||||
---@param path string
|
||||
---@param insensitive? boolean
|
||||
---@return plugins.projectsearch.resultsview?
|
||||
function projectsearch.search_plain(text, path, insensitive)
|
||||
if insensitive then text = text:lower() end
|
||||
return begin_search(path, text, function(line_text)
|
||||
if insensitive then
|
||||
return line_text:lower():find(text, nil, true)
|
||||
else
|
||||
return line_text:find(text, nil, true)
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
---@param text string
|
||||
---@param path string
|
||||
---@param insensitive? boolean
|
||||
---@return plugins.projectsearch.resultsview?
|
||||
function projectsearch.search_regex(text, path, insensitive)
|
||||
local re, errmsg
|
||||
if insensitive then
|
||||
re, errmsg = regex.compile(text, "i")
|
||||
else
|
||||
re, errmsg = regex.compile(text)
|
||||
end
|
||||
if not re then core.log("%s", errmsg) return end
|
||||
return begin_search(path, text, function(line_text)
|
||||
return regex.cmatch(re, line_text)
|
||||
end)
|
||||
end
|
||||
|
||||
---@param text string
|
||||
---@param path string
|
||||
---@param insensitive? boolean
|
||||
---@return plugins.projectsearch.resultsview?
|
||||
function projectsearch.search_fuzzy(text, path, insensitive)
|
||||
if insensitive then text = text:lower() end
|
||||
return begin_search(path, text, function(line_text)
|
||||
if insensitive then
|
||||
return common.fuzzy_match(line_text:lower(), text) and 1
|
||||
else
|
||||
return common.fuzzy_match(line_text, text) and 1
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
|
||||
command.add(nil, {
|
||||
["project-search:find"] = function(path)
|
||||
core.command_view:enter("Find Text In " .. (normalize_path(path) or "Project"), {
|
||||
text = get_selected_text(),
|
||||
select_text = true,
|
||||
submit = function(text)
|
||||
projectsearch.search_plain(text, path, true)
|
||||
end
|
||||
})
|
||||
end,
|
||||
|
||||
["project-search:find-regex"] = function(path)
|
||||
core.command_view:enter("Find Regex In " .. (normalize_path(path) or "Project"), {
|
||||
submit = function(text)
|
||||
projectsearch.search_regex(text, path, true)
|
||||
end
|
||||
})
|
||||
end,
|
||||
|
||||
["project-search:fuzzy-find"] = function(path)
|
||||
core.command_view:enter("Fuzzy Find Text In " .. (normalize_path(path) or "Project"), {
|
||||
text = get_selected_text(),
|
||||
select_text = true,
|
||||
submit = function(text)
|
||||
projectsearch.search_fuzzy(text, path, true)
|
||||
end
|
||||
})
|
||||
end,
|
||||
})
|
||||
|
||||
|
||||
command.add(ResultsView, {
|
||||
["project-search:select-previous"] = function()
|
||||
local view = core.active_view
|
||||
view.selected_idx = math.max(view.selected_idx - 1, 1)
|
||||
view:scroll_to_make_selected_visible()
|
||||
end,
|
||||
|
||||
["project-search:select-next"] = function()
|
||||
local view = core.active_view
|
||||
view.selected_idx = math.min(view.selected_idx + 1, #view.results)
|
||||
view:scroll_to_make_selected_visible()
|
||||
end,
|
||||
|
||||
["project-search:open-selected"] = function()
|
||||
core.active_view:open_selected_result()
|
||||
end,
|
||||
|
||||
["project-search:refresh"] = function()
|
||||
core.active_view:refresh()
|
||||
end,
|
||||
|
||||
["project-search:move-to-previous-page"] = function()
|
||||
local view = core.active_view
|
||||
view.scroll.to.y = view.scroll.to.y - view.size.y
|
||||
end,
|
||||
|
||||
["project-search:move-to-next-page"] = function()
|
||||
local view = core.active_view
|
||||
view.scroll.to.y = view.scroll.to.y + view.size.y
|
||||
end,
|
||||
|
||||
["project-search:move-to-start-of-doc"] = function()
|
||||
local view = core.active_view
|
||||
view.scroll.to.y = 0
|
||||
end,
|
||||
|
||||
["project-search:move-to-end-of-doc"] = function()
|
||||
local view = core.active_view
|
||||
view.scroll.to.y = view:get_scrollable_size()
|
||||
end
|
||||
})
|
||||
|
||||
keymap.add {
|
||||
["f5"] = "project-search:refresh",
|
||||
["ctrl+shift+f"] = "project-search:find",
|
||||
["up"] = "project-search:select-previous",
|
||||
["down"] = "project-search:select-next",
|
||||
["return"] = "project-search:open-selected",
|
||||
["pageup"] = "project-search:move-to-previous-page",
|
||||
["pagedown"] = "project-search:move-to-next-page",
|
||||
["ctrl+home"] = "project-search:move-to-start-of-doc",
|
||||
["ctrl+end"] = "project-search:move-to-end-of-doc",
|
||||
["home"] = "project-search:move-to-start-of-doc",
|
||||
["end"] = "project-search:move-to-end-of-doc"
|
||||
}
|
||||
|
||||
|
||||
return projectsearch
|
|
@ -1,31 +0,0 @@
|
|||
-- mod-version:3
|
||||
local core = require "core"
|
||||
local command = require "core.command"
|
||||
local keymap = require "core.keymap"
|
||||
|
||||
|
||||
local escapes = {
|
||||
["\\"] = "\\\\",
|
||||
["\""] = "\\\"",
|
||||
["\n"] = "\\n",
|
||||
["\r"] = "\\r",
|
||||
["\t"] = "\\t",
|
||||
["\b"] = "\\b",
|
||||
}
|
||||
|
||||
local function replace(chr)
|
||||
return escapes[chr] or string.format("\\x%02x", chr:byte())
|
||||
end
|
||||
|
||||
|
||||
command.add("core.docview", {
|
||||
["quote:quote"] = function(dv)
|
||||
dv.doc:replace(function(text)
|
||||
return '"' .. text:gsub("[\0-\31\\\"]", replace) .. '"'
|
||||
end)
|
||||
end,
|
||||
})
|
||||
|
||||
keymap.add {
|
||||
["ctrl+'"] = "quote:quote",
|
||||
}
|
|
@ -1,58 +0,0 @@
|
|||
local tokenizer = require "core.tokenizer"
|
||||
local style = require "core.style"
|
||||
local common = require "core.common"
|
||||
|
||||
local tokenize = tokenizer.tokenize
|
||||
local closers = {
|
||||
["("] = ")",
|
||||
["["] = "]",
|
||||
["{"] = "}"
|
||||
}
|
||||
local function parenstyle(parenstack)
|
||||
return "paren" .. ((#parenstack % 5) + 1)
|
||||
end
|
||||
function tokenizer.tokenize(syntax, text, state)
|
||||
state = state or {}
|
||||
local res, istate = tokenize(syntax, text, state.istate)
|
||||
local parenstack = state.parenstack or ""
|
||||
local newres = {}
|
||||
-- split parens out
|
||||
-- the stock tokenizer can't do this because it merges identical adjacent tokens
|
||||
for i, type, text in tokenizer.each_token(res) do
|
||||
if type == "normal" or type == "symbol" then
|
||||
for normtext1, paren, normtext2 in text:gmatch("([^%(%[{}%]%)]*)([%(%[{}%]%)]?)([^%(%[{}%]%)]*)") do
|
||||
if #normtext1 > 0 then
|
||||
table.insert(newres, type)
|
||||
table.insert(newres, normtext1)
|
||||
end
|
||||
if #paren > 0 then
|
||||
if paren == parenstack:sub(-1) then -- expected closer
|
||||
parenstack = parenstack:sub(1, -2)
|
||||
table.insert(newres, parenstyle(parenstack))
|
||||
elseif closers[paren] then -- opener
|
||||
table.insert(newres, parenstyle(parenstack))
|
||||
parenstack = parenstack .. closers[paren]
|
||||
else -- unexpected closer
|
||||
table.insert(newres, "paren_unbalanced")
|
||||
end
|
||||
table.insert(newres, paren)
|
||||
end
|
||||
if #normtext2 > 0 then
|
||||
table.insert(newres, type)
|
||||
table.insert(newres, normtext2)
|
||||
end
|
||||
end
|
||||
else
|
||||
table.insert(newres, type)
|
||||
table.insert(newres, text)
|
||||
end
|
||||
end
|
||||
return newres, { parenstack = parenstack, istate = istate }
|
||||
end
|
||||
|
||||
style.syntax.paren_unbalanced = style.syntax.paren_unbalanced or { common.color "#DC0408" }
|
||||
style.syntax.paren1 = style.syntax.paren1 or { common.color "#FC6F71"}
|
||||
style.syntax.paren2 = style.syntax.paren2 or { common.color "#fcb053"}
|
||||
style.syntax.paren3 = style.syntax.paren3 or { common.color "#fcd476"}
|
||||
style.syntax.paren4 = style.syntax.paren4 or { common.color "#52dab2"}
|
||||
style.syntax.paren5 = style.syntax.paren5 or { common.color "#5a98cf"}
|
|
@ -1,63 +0,0 @@
|
|||
local core = require "core"
|
||||
local config = require "core.config"
|
||||
local command = require "core.command"
|
||||
local keymap = require "core.keymap"
|
||||
|
||||
|
||||
local function wordwrap_text(text, limit)
|
||||
local t = {}
|
||||
local n = 0
|
||||
|
||||
for word in text:gmatch("%S+") do
|
||||
if n + #word > limit then
|
||||
table.insert(t, "\n")
|
||||
n = 0
|
||||
elseif #t > 0 then
|
||||
table.insert(t, " ")
|
||||
end
|
||||
table.insert(t, word)
|
||||
n = n + #word + 1
|
||||
end
|
||||
|
||||
return table.concat(t)
|
||||
end
|
||||
|
||||
|
||||
command.add("core.docview", {
|
||||
["reflow:reflow"] = function()
|
||||
local doc = core.active_view.doc
|
||||
doc:replace(function(text)
|
||||
local prefix_set = "[^%w\n%[%](){}`'\"]*"
|
||||
|
||||
-- get line prefix and trailing whitespace
|
||||
local prefix1 = text:match("^\n*" .. prefix_set)
|
||||
local prefix2 = text:match("\n(" .. prefix_set .. ")", #prefix1+1)
|
||||
local trailing = text:match("%s*$")
|
||||
if not prefix2 or prefix2 == "" then
|
||||
prefix2 = prefix1
|
||||
end
|
||||
|
||||
-- strip all line prefixes and trailing whitespace
|
||||
text = text:sub(#prefix1+1, -#trailing - 1):gsub("\n" .. prefix_set, "\n")
|
||||
|
||||
-- split into blocks, wordwrap and join
|
||||
local line_limit = config.line_limit - #prefix1
|
||||
local blocks = {}
|
||||
text = text:gsub("\n\n", "\0")
|
||||
for block in text:gmatch("%Z+") do
|
||||
table.insert(blocks, wordwrap_text(block, line_limit))
|
||||
end
|
||||
text = table.concat(blocks, "\n\n")
|
||||
|
||||
-- add prefix to start of lines
|
||||
text = prefix1 .. text:gsub("\n", "\n" .. prefix2) .. trailing
|
||||
|
||||
return text
|
||||
end)
|
||||
end,
|
||||
})
|
||||
|
||||
|
||||
keymap.add {
|
||||
["ctrl+shift+q"] = "reflow:reflow"
|
||||
}
|
|
@ -1,110 +0,0 @@
|
|||
local core = require "core"
|
||||
local common = require "core.common"
|
||||
local command = require "core.command"
|
||||
local config = require "core.config"
|
||||
local keymap = require "core.keymap"
|
||||
local style = require "core.style"
|
||||
local RootView = require "core.rootview"
|
||||
local CommandView = require "core.commandview"
|
||||
|
||||
config.scale_mode = "code"
|
||||
config.scale_use_mousewheel = true
|
||||
|
||||
local font_cache = setmetatable({}, { __mode = "k" })
|
||||
|
||||
-- the following should be kept in sync with core.style's default font settings
|
||||
font_cache[style.font] = { DATADIR .. "/data/fonts/font.ttf", 14 * SCALE }
|
||||
font_cache[style.big_font] = { DATADIR .. "/data/fonts/font.ttf", 34 * SCALE }
|
||||
font_cache[style.icon_font] = { DATADIR .. "/data/fonts/icons.ttf", 14 * SCALE }
|
||||
font_cache[style.code_font] = { DATADIR .. "/data/fonts/monospace.ttf", 13.5 * SCALE }
|
||||
|
||||
|
||||
local load_font = renderer.font.load
|
||||
function renderer.font.load(...)
|
||||
local res = load_font(...)
|
||||
font_cache[res] = { ... }
|
||||
return res
|
||||
end
|
||||
|
||||
|
||||
local function scale_font(font, s)
|
||||
local fc = font_cache[font]
|
||||
return renderer.font.load(fc[1], fc[2] * s)
|
||||
end
|
||||
|
||||
|
||||
local current_scale = SCALE
|
||||
local default = current_scale
|
||||
|
||||
|
||||
local function get_scale() return current_scale end
|
||||
|
||||
|
||||
local function set_scale(scale)
|
||||
scale = common.clamp(scale, 0.2, 6)
|
||||
|
||||
-- save scroll positions
|
||||
local scrolls = {}
|
||||
for _, view in ipairs(core.root_view.root_node:get_children()) do
|
||||
local n = view:get_scrollable_size()
|
||||
if n ~= math.huge and not view:is(CommandView) then
|
||||
scrolls[view] = view.scroll.y / (n - view.size.y)
|
||||
end
|
||||
end
|
||||
|
||||
local s = scale / current_scale
|
||||
current_scale = scale
|
||||
|
||||
if config.scale_mode == "ui" then
|
||||
SCALE = current_scale
|
||||
|
||||
style.padding.x = style.padding.x * s
|
||||
style.padding.y = style.padding.y * s
|
||||
style.divider_size = style.divider_size * s
|
||||
style.scrollbar_size = style.scrollbar_size * s
|
||||
style.caret_width = style.caret_width * s
|
||||
style.tab_width = style.tab_width * s
|
||||
|
||||
style.big_font = scale_font(style.big_font, s)
|
||||
style.icon_font = scale_font(style.icon_font, s)
|
||||
style.font = scale_font(style.font, s)
|
||||
end
|
||||
|
||||
style.code_font = scale_font(style.code_font, s)
|
||||
|
||||
-- restore scroll positions
|
||||
for view, n in pairs(scrolls) do
|
||||
view.scroll.y = n * (view:get_scrollable_size() - view.size.y)
|
||||
view.scroll.to.y = view.scroll.y
|
||||
end
|
||||
|
||||
core.redraw = true
|
||||
end
|
||||
|
||||
|
||||
local on_mouse_wheel = RootView.on_mouse_wheel
|
||||
|
||||
function RootView:on_mouse_wheel(d, ...)
|
||||
if keymap.modkeys["ctrl"] and config.scale_use_mousewheel then
|
||||
if d < 0 then command.perform "scale:decrease" end
|
||||
if d > 0 then command.perform "scale:increase" end
|
||||
else
|
||||
return on_mouse_wheel(self, d, ...)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
command.add(nil, {
|
||||
["scale:reset" ] = function() set_scale(default) end,
|
||||
["scale:decrease"] = function() set_scale(current_scale * 0.9) end,
|
||||
["scale:increase"] = function() set_scale(current_scale * 1.1) end,
|
||||
})
|
||||
|
||||
keymap.add {
|
||||
["ctrl+0"] = "scale:reset",
|
||||
["ctrl+-"] = "scale:decrease",
|
||||
["ctrl++"] = "scale:increase",
|
||||
}
|
||||
|
||||
return { get_scale = get_scale, set_scale = set_scale }
|
||||
|
|
@ -1,40 +0,0 @@
|
|||
--[[
|
||||
scalestatus.lua
|
||||
displays current scale (zoom) in status view
|
||||
version: 20200628_155804
|
||||
originally by SwissalpS
|
||||
|
||||
Depends on plugin scale.lua version >= 20200628_154010
|
||||
--]]
|
||||
local scale = require "plugins.scale"
|
||||
-- make sure plugin is installed and has get_scale field
|
||||
if not scale.get_scale then
|
||||
local core = require "core"
|
||||
core.error("Plugin 'scale' needs to be updated, scalestatus inactive.")
|
||||
return false
|
||||
end
|
||||
|
||||
local config = require "core.config"
|
||||
local StatusView = require "core.statusview"
|
||||
|
||||
config.scalestatus_format = '%.0f%%'
|
||||
|
||||
local get_items = StatusView.get_items
|
||||
function StatusView:get_items()
|
||||
|
||||
local left, right = get_items(self)
|
||||
|
||||
local t = {
|
||||
self.separator,
|
||||
string.format(config.scalestatus_format, scale.get_scale() * 100),
|
||||
}
|
||||
|
||||
for _, item in ipairs(t) do
|
||||
table.insert(right, item)
|
||||
end
|
||||
|
||||
return left, right
|
||||
|
||||
end
|
||||
|
||||
return true
|
|
@ -1,37 +0,0 @@
|
|||
local style = require "core.style"
|
||||
local DocView = require "core.docview"
|
||||
|
||||
-- originally written by luveti
|
||||
|
||||
local function draw_box(x, y, w, h, color)
|
||||
local r = renderer.draw_rect
|
||||
local s = math.ceil(SCALE)
|
||||
r(x, y, w, s, color)
|
||||
r(x, y + h - s, w, s, color)
|
||||
r(x, y + s, s, h - s * 2, color)
|
||||
r(x + w - s, y + s, s, h - s * 2, color)
|
||||
end
|
||||
|
||||
|
||||
local draw_line_body = DocView.draw_line_body
|
||||
|
||||
function DocView:draw_line_body(idx, x, y)
|
||||
local line1, col1, line2, col2 = self.doc:get_selection(true)
|
||||
if line1 == line2 and col1 ~= col2 then
|
||||
local lh = self:get_line_height()
|
||||
local selected_text = self.doc.lines[line1]:sub(col1, col2 - 1)
|
||||
local current_line_text = self.doc.lines[idx]
|
||||
local last_col = 1
|
||||
while true do
|
||||
local start_col, end_col = current_line_text:find(selected_text, last_col, true)
|
||||
if start_col == nil then break end
|
||||
local x1 = x + self:get_col_x_offset(idx, start_col)
|
||||
local x2 = x + self:get_col_x_offset(idx, end_col + 1)
|
||||
local color = style.selectionhighlight or style.syntax.comment
|
||||
draw_box(x1, y, x2 - x1, lh, color)
|
||||
last_col = end_col + 1
|
||||
end
|
||||
end
|
||||
draw_line_body(self, idx, x, y)
|
||||
end
|
||||
|
|
@ -1,30 +0,0 @@
|
|||
-- mod-version:2 -- lite-xl 2.0
|
||||
local core = require "core"
|
||||
local command = require "core.command"
|
||||
local translate = require "core.doc.translate"
|
||||
|
||||
local function split_lines(text)
|
||||
local res = {}
|
||||
for line in (text .. "\n"):gmatch("(.-)\n") do
|
||||
table.insert(res, line)
|
||||
end
|
||||
return res
|
||||
end
|
||||
|
||||
command.add("core.docview", {
|
||||
["sort:sort"] = function()
|
||||
local doc = core.active_view.doc
|
||||
|
||||
local l1, c1, l2, c2, swap = doc:get_selection(true)
|
||||
l1, c1 = translate.start_of_line(doc, l1, c1)
|
||||
l2, c2 = translate.end_of_line(doc, l2, c2)
|
||||
doc:set_selection(l1, c1, l2, c2, swap)
|
||||
|
||||
doc:replace(function(text)
|
||||
local head, body, foot = text:match("(\n*)(.-)(\n*)$")
|
||||
local lines = split_lines(body)
|
||||
table.sort(lines, function(a, b) return a:lower() < b:lower() end)
|
||||
return head .. table.concat(lines, "\n") .. foot
|
||||
end)
|
||||
end,
|
||||
})
|
|
@ -1,63 +0,0 @@
|
|||
-- mod-version:3
|
||||
local core = require "core"
|
||||
local command = require "core.command"
|
||||
local translate = require "core.doc.translate"
|
||||
|
||||
|
||||
local function gmatch_to_array(text, ptn)
|
||||
local res = {}
|
||||
for x in text:gmatch(ptn) do
|
||||
table.insert(res, x)
|
||||
end
|
||||
return res
|
||||
end
|
||||
|
||||
|
||||
local function tabularize_lines(lines, delim)
|
||||
local rows = {}
|
||||
local cols = {}
|
||||
|
||||
-- split lines at delimiters and get maximum width of columns
|
||||
local ptn = "[^" .. delim:sub(1,1):gsub("%W", "%%%1") .. "]+"
|
||||
for i, line in ipairs(lines) do
|
||||
rows[i] = gmatch_to_array(line, ptn)
|
||||
for j, col in ipairs(rows[i]) do
|
||||
cols[j] = math.max(#col, cols[j] or 0)
|
||||
end
|
||||
end
|
||||
|
||||
-- pad columns with space
|
||||
for _, row in ipairs(rows) do
|
||||
for i = 1, #row - 1 do
|
||||
row[i] = row[i] .. string.rep(" ", cols[i] - #row[i])
|
||||
end
|
||||
end
|
||||
|
||||
-- write columns back to lines array
|
||||
for i, line in ipairs(lines) do
|
||||
lines[i] = table.concat(rows[i], delim)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
command.add("core.docview", {
|
||||
["tabularize:tabularize"] = function(dv)
|
||||
core.command_view:enter("Tabularize On Delimiter", {
|
||||
submit = function(delim)
|
||||
if delim == "" then delim = " " end
|
||||
|
||||
local doc = dv.doc
|
||||
local line1, col1, line2, col2, swap = doc:get_selection(true)
|
||||
line1, col1 = doc:position_offset(line1, col1, translate.start_of_line)
|
||||
line2, col2 = doc:position_offset(line2, col2, translate.end_of_line)
|
||||
doc:set_selection(line1, col1, line2, col2, swap)
|
||||
|
||||
doc:replace(function(text)
|
||||
local lines = gmatch_to_array(text, "[^\n]*\n?")
|
||||
tabularize_lines(lines, delim)
|
||||
return table.concat(lines)
|
||||
end)
|
||||
end
|
||||
})
|
||||
end,
|
||||
})
|
|
@ -1,389 +0,0 @@
|
|||
local core = require "core"
|
||||
local common = require "core.common"
|
||||
local command = require "core.command"
|
||||
local config = require "core.config"
|
||||
local keymap = require "core.keymap"
|
||||
local style = require "core.style"
|
||||
local View = require "core.view"
|
||||
|
||||
|
||||
local TodoTreeView = View:extend()
|
||||
|
||||
config.todo_tags = { --"TODO", "BUG", "FIX", "FIXME", "IMPROVEMENT",
|
||||
"@todo", "@fixme", "@testme", "@leak" } --< @r-lyeh
|
||||
|
||||
-- Paths or files to be ignored
|
||||
config.todo_ignore_paths = {
|
||||
"tools/tcc", --< @r-lyeh
|
||||
"tools\\tcc", --< @r-lyeh
|
||||
"engine/fwk", --< @r-lyeh
|
||||
"engine\\fwk", --< @r-lyeh
|
||||
"engine/joint", --< @r-lyeh
|
||||
"engine\\joint", --< @r-lyeh
|
||||
}
|
||||
|
||||
-- 'tag' mode can be used to group the todos by tags
|
||||
-- 'file' mode can be used to group the todos by files
|
||||
config.todo_mode = "tag"
|
||||
|
||||
-- Tells if the plugin should start with the nodes expanded. default: true for tag mode
|
||||
config.todo_expanded = config.todo_mode == "tag"
|
||||
|
||||
-- list of allowed extensions: items must start and end with a dot character
|
||||
config.todo_allowed_extensions = '.h.c.m.hh.cc.hpp.cpp.cxx.lua.py.cs.vs.fs.bat.' --< @r-lyeh
|
||||
|
||||
-- whether the sidebar treeview is initially visible or not
|
||||
config.todo_visible = false
|
||||
|
||||
|
||||
function TodoTreeView:new()
|
||||
TodoTreeView.super.new(self)
|
||||
self.scrollable = true
|
||||
self.focusable = false
|
||||
self.visible = config.todo_visible
|
||||
self.times_cache = {}
|
||||
self.cache = {}
|
||||
self.cache_updated = false
|
||||
self.init_size = true
|
||||
|
||||
-- Items are generated from cache according to the mode
|
||||
self.items = {}
|
||||
end
|
||||
|
||||
local function is_file_ignored(filename)
|
||||
for _, path in ipairs(config.todo_ignore_paths) do
|
||||
local s, _ = filename:find(path)
|
||||
if s then
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
function TodoTreeView:refresh_cache()
|
||||
local items = {}
|
||||
if not next(self.items) then
|
||||
items = self.items
|
||||
end
|
||||
self.updating_cache = true
|
||||
|
||||
core.add_thread(function()
|
||||
for _, item in ipairs(core.project_files) do
|
||||
local ignored = is_file_ignored(item.filename)
|
||||
if not ignored and item.type == "file" then
|
||||
local cached = self:get_cached(item)
|
||||
|
||||
if config.todo_mode == "file" then
|
||||
items[cached.filename] = cached
|
||||
else
|
||||
for _, todo in ipairs(cached.todos) do
|
||||
local tag = todo.tag
|
||||
if not items[tag] then
|
||||
local t = {}
|
||||
t.expanded = config.todo_expanded
|
||||
t.type = "group"
|
||||
t.todos = {}
|
||||
t.tag = tag
|
||||
items[tag] = t
|
||||
end
|
||||
|
||||
table.insert(items[tag].todos, todo)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Copy expanded from old items
|
||||
if config.todo_mode == "tag" and next(self.items) then
|
||||
for tag, data in pairs(self.items) do
|
||||
if items[tag] then
|
||||
items[tag].expanded = data.expanded
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
self.items = items
|
||||
core.redraw = true
|
||||
self.cache_updated = true
|
||||
self.updating_cache = false
|
||||
end, self)
|
||||
end
|
||||
|
||||
|
||||
local function find_file_todos(t, filename)
|
||||
--< @r-lyeh
|
||||
local ext = (filename:match "[^.]+$") .. '.'
|
||||
if not string.find(config.todo_allowed_extensions,ext) then
|
||||
return
|
||||
end
|
||||
--<
|
||||
|
||||
local fp = io.open(filename)
|
||||
if not fp then return t end
|
||||
|
||||
--< @r-lyeh: optimized loops: early exit if quicksearch fails
|
||||
local function lines(str)
|
||||
local result = {}
|
||||
for line in string.gmatch(str, "(.-)%c") do -- line in str:gmatch '[^\n]+' do
|
||||
-- Add spaces at the start and end of line so the pattern will pick
|
||||
-- tags at the start and at the end of lines
|
||||
table.insert(result, " "..line.." ")
|
||||
end
|
||||
return result
|
||||
end
|
||||
local before = #t
|
||||
local content = fp:read("*all")
|
||||
for _, todo_tag in ipairs(config.todo_tags) do
|
||||
if string.find(content, todo_tag) then
|
||||
local n = 0
|
||||
for _, line in ipairs(lines(content)) do
|
||||
n = n + 1
|
||||
local match_str = todo_tag[1] == '@' and todo_tag or "[^a-zA-Z_\"'`]"..todo_tag.."[^a-zA-Z_\"'`]+"
|
||||
local s, e = line:find(match_str)
|
||||
if s then
|
||||
local d = {}
|
||||
d.tag = string.sub(string.upper(todo_tag), todo_tag:byte(1) == 64 and 2 or 1) .. 's'
|
||||
d.filename = filename
|
||||
d.text = line:sub(e+1)
|
||||
if d.text == "" then
|
||||
d.text = config.todo_mode == "tag" and filename:match("^.+[/\\](.+)$") or "blank"
|
||||
end
|
||||
d.line = n
|
||||
d.col = s
|
||||
table.insert(t, d)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
fp:close()
|
||||
if #t ~= before then
|
||||
coroutine.yield()
|
||||
core.redraw = true
|
||||
end
|
||||
--<
|
||||
end
|
||||
|
||||
|
||||
function TodoTreeView:get_cached(item)
|
||||
local t = self.cache[item.filename]
|
||||
if not t then
|
||||
t = {}
|
||||
t.expanded = config.todo_expanded
|
||||
t.filename = item.filename
|
||||
t.abs_filename = system.absolute_path(item.filename)
|
||||
t.type = item.type
|
||||
t.todos = {}
|
||||
find_file_todos(t.todos, t.filename)
|
||||
self.cache[t.filename] = t
|
||||
end
|
||||
return t
|
||||
end
|
||||
|
||||
|
||||
function TodoTreeView:get_name()
|
||||
return "Todo Tree"
|
||||
end
|
||||
|
||||
|
||||
function TodoTreeView:get_item_height()
|
||||
return style.font:get_height() + style.padding.y
|
||||
end
|
||||
|
||||
|
||||
function TodoTreeView:get_cached_time(doc)
|
||||
local t = self.times_cache[doc]
|
||||
if not t then
|
||||
local info = system.get_file_info(doc.filename)
|
||||
if not info then return nil end
|
||||
self.times_cache[doc] = info.modified
|
||||
end
|
||||
return t
|
||||
end
|
||||
|
||||
|
||||
function TodoTreeView:check_cache()
|
||||
for _, doc in ipairs(core.docs) do
|
||||
if doc.filename then
|
||||
local info = system.get_file_info(doc.filename)
|
||||
local cached = self:get_cached_time(doc)
|
||||
if not info and cached then
|
||||
-- document deleted
|
||||
self.times_cache[doc] = nil
|
||||
self.cache[doc.filename] = nil
|
||||
self.cache_updated = false
|
||||
elseif cached and cached ~= info.modified then
|
||||
-- document modified
|
||||
self.times_cache[doc] = info.modified
|
||||
self.cache[doc.filename] = nil
|
||||
self.cache_updated = false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if core.project_files ~= self.last_project_files then
|
||||
self.last_project_files = core.project_files
|
||||
self.cache_updated = false
|
||||
end
|
||||
end
|
||||
|
||||
function TodoTreeView:each_item()
|
||||
self:check_cache()
|
||||
if not self.updating_cache and not self.cache_updated then
|
||||
self:refresh_cache()
|
||||
end
|
||||
|
||||
return coroutine.wrap(function()
|
||||
local ox, oy = self:get_content_offset()
|
||||
local y = oy + style.padding.y
|
||||
local w = self.size.x
|
||||
local h = self:get_item_height()
|
||||
|
||||
for _, item in pairs(self.items) do
|
||||
if #item.todos > 0 then
|
||||
coroutine.yield(item, ox, y, w, h)
|
||||
y = y + h
|
||||
|
||||
for _, todo in ipairs(item.todos) do
|
||||
if item.expanded then
|
||||
coroutine.yield(todo, ox, y, w, h)
|
||||
y = y + h
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
|
||||
function TodoTreeView:on_mouse_moved(px, py)
|
||||
self.hovered_item = nil
|
||||
for item, x,y,w,h in self:each_item() do
|
||||
if px > x and py > y and px <= x + w and py <= y + h then
|
||||
self.hovered_item = item
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function TodoTreeView:on_mouse_pressed(button, x, y)
|
||||
if not self.hovered_item then
|
||||
return
|
||||
elseif self.hovered_item.type == "file"
|
||||
or self.hovered_item.type == "group" then
|
||||
self.hovered_item.expanded = not self.hovered_item.expanded
|
||||
else
|
||||
core.try(function()
|
||||
local i = self.hovered_item
|
||||
local dv = core.root_view:open_doc(core.open_doc(i.filename))
|
||||
core.root_view.root_node:update_layout()
|
||||
dv.doc:set_selection(i.line, i.col)
|
||||
dv:scroll_to_line(i.line, false, true)
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function TodoTreeView:update()
|
||||
self.scroll.to.y = math.max(0, self.scroll.to.y)
|
||||
|
||||
-- update width
|
||||
local dest = self.visible and config.treeview_size or 0
|
||||
if self.init_size then
|
||||
self.size.x = dest
|
||||
self.init_size = false
|
||||
else
|
||||
self:move_towards(self.size, "x", dest)
|
||||
end
|
||||
|
||||
TodoTreeView.super.update(self)
|
||||
end
|
||||
|
||||
|
||||
function TodoTreeView:draw()
|
||||
self:draw_background(style.background2)
|
||||
|
||||
--local h = self:get_item_height()
|
||||
local icon_width = style.icon_font:get_width("D")
|
||||
local spacing = style.font:get_width(" ") * 2
|
||||
local root_depth = 0
|
||||
|
||||
for item, x,y,w,h in self:each_item() do
|
||||
local color = style.text
|
||||
|
||||
-- hovered item background
|
||||
if item == self.hovered_item then
|
||||
renderer.draw_rect(x, y, w, h, style.line_highlight)
|
||||
color = style.accent
|
||||
end
|
||||
|
||||
-- icons
|
||||
local item_depth = 0
|
||||
x = x + (item_depth - root_depth) * style.padding.x + style.padding.x
|
||||
if item.type == "file" then
|
||||
local icon1 = item.expanded and "-" or "+"
|
||||
common.draw_text(style.icon_font, color, icon1, nil, x, y, 0, h)
|
||||
x = x + style.padding.x
|
||||
common.draw_text(style.icon_font, color, "f", nil, x, y, 0, h)
|
||||
x = x + icon_width
|
||||
elseif item.type == "group" then
|
||||
local icon1 = item.expanded and "-" or "+"
|
||||
common.draw_text(style.icon_font, color, icon1, nil, x, y, 0, h)
|
||||
x = x + icon_width / 2
|
||||
else
|
||||
if config.todo_mode == "tag" then
|
||||
x = x + style.padding.x
|
||||
else
|
||||
x = x + style.padding.x * 1.5
|
||||
end
|
||||
common.draw_text(style.icon_font, color, "i", nil, x, y, 0, h)
|
||||
x = x + icon_width
|
||||
end
|
||||
|
||||
-- text
|
||||
x = x + spacing
|
||||
if item.type == "file" then
|
||||
common.draw_text(style.font, color, item.filename, nil, x, y, 0, h)
|
||||
elseif item.type == "group" then
|
||||
common.draw_text(style.font, color, item.tag, nil, x, y, 0, h)
|
||||
else
|
||||
if config.todo_mode == "file" then
|
||||
common.draw_text(style.font, color, item.tag.." - "..item.text, nil, x, y, 0, h)
|
||||
else
|
||||
common.draw_text(style.font, color, item.text, nil, x, y, 0, h)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-- init
|
||||
local view = TodoTreeView()
|
||||
local node = core.root_view:get_active_node()
|
||||
view.size.x = config.treeview_size
|
||||
node:split("right", view, true)
|
||||
|
||||
-- register commands and keymap
|
||||
command.add(nil, {
|
||||
["todotreeview:toggle"] = function()
|
||||
view.visible = not view.visible
|
||||
end,
|
||||
|
||||
["todotreeview:expand-items"] = function()
|
||||
for _, item in pairs(view.items) do
|
||||
item.expanded = true
|
||||
end
|
||||
end,
|
||||
|
||||
["todotreeview:hide-items"] = function()
|
||||
for _, item in pairs(view.items) do
|
||||
item.expanded = false
|
||||
end
|
||||
end,
|
||||
})
|
||||
|
||||
keymap.add { ["ctrl+shift+t"] = "todotreeview:toggle" }
|
||||
keymap.add { ["ctrl+shift+e"] = "todotreeview:expand-items" }
|
||||
keymap.add { ["ctrl+shift+h"] = "todotreeview:hide-items" }
|
||||
|
|
@ -1,294 +0,0 @@
|
|||
local core = require "core"
|
||||
local common = require "core.common"
|
||||
local command = require "core.command"
|
||||
local config = require "core.config"
|
||||
local keymap = require "core.keymap"
|
||||
local style = require "core.style"
|
||||
local View = require "core.view"
|
||||
|
||||
config.treeview_size = 200 * SCALE
|
||||
|
||||
local function get_depth(filename)
|
||||
local n = 0
|
||||
for sep in filename:gmatch("[\\/]") do
|
||||
n = n + 1
|
||||
end
|
||||
return n
|
||||
end
|
||||
|
||||
|
||||
local TreeView = View:extend()
|
||||
|
||||
function TreeView:new()
|
||||
TreeView.super.new(self)
|
||||
self.scrollable = true
|
||||
self.visible = false --< @r-lyeh true>false
|
||||
self.init_size = true
|
||||
self.cache = {}
|
||||
end
|
||||
|
||||
|
||||
function TreeView:get_cached(item)
|
||||
local t = self.cache[item.filename]
|
||||
if not t then
|
||||
t = {}
|
||||
t.filename = item.filename
|
||||
t.abs_filename = system.absolute_path(item.filename)
|
||||
t.name = t.filename:match("[^\\/]+$")
|
||||
t.depth = get_depth(t.filename)
|
||||
t.type = item.type
|
||||
self.cache[t.filename] = t
|
||||
end
|
||||
return t
|
||||
end
|
||||
|
||||
|
||||
function TreeView:get_name()
|
||||
return "Project"
|
||||
end
|
||||
|
||||
|
||||
function TreeView:get_item_height()
|
||||
return style.font:get_height() + style.padding.y
|
||||
end
|
||||
|
||||
|
||||
function TreeView:check_cache()
|
||||
-- invalidate cache's skip values if project_files has changed
|
||||
if core.project_files ~= self.last_project_files then
|
||||
for _, v in pairs(self.cache) do
|
||||
v.skip = nil
|
||||
end
|
||||
self.last_project_files = core.project_files
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function TreeView:each_item()
|
||||
return coroutine.wrap(function()
|
||||
self:check_cache()
|
||||
local ox, oy = self:get_content_offset()
|
||||
local y = oy + style.padding.y
|
||||
local w = self.size.x
|
||||
local h = self:get_item_height()
|
||||
|
||||
local i = 1
|
||||
while i <= #core.project_files do
|
||||
local item = core.project_files[i]
|
||||
local cached = self:get_cached(item)
|
||||
|
||||
coroutine.yield(cached, ox, y, w, h)
|
||||
y = y + h
|
||||
i = i + 1
|
||||
|
||||
if not cached.expanded then
|
||||
if cached.skip then
|
||||
i = cached.skip
|
||||
else
|
||||
local depth = cached.depth
|
||||
while i <= #core.project_files do
|
||||
local filename = core.project_files[i].filename
|
||||
if get_depth(filename) <= depth then break end
|
||||
i = i + 1
|
||||
end
|
||||
cached.skip = i
|
||||
end
|
||||
end
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
|
||||
function TreeView:on_mouse_moved(px, py)
|
||||
self.hovered_item = nil
|
||||
for item, x,y,w,h in self:each_item() do
|
||||
if px > x and py > y and px <= x + w and py <= y + h then
|
||||
self.hovered_item = item
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function TreeView:on_mouse_pressed(button, x, y)
|
||||
if not self.hovered_item then
|
||||
return
|
||||
elseif self.hovered_item.type == "dir" then
|
||||
self.hovered_item.expanded = not self.hovered_item.expanded
|
||||
else
|
||||
core.try(function()
|
||||
core.root_view:open_doc(core.open_doc(self.hovered_item.filename))
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function TreeView:update()
|
||||
-- update width
|
||||
local dest = self.visible and config.treeview_size or 0
|
||||
if self.init_size then
|
||||
self.size.x = dest
|
||||
self.init_size = false
|
||||
else
|
||||
self:move_towards(self.size, "x", dest)
|
||||
end
|
||||
|
||||
TreeView.super.update(self)
|
||||
end
|
||||
|
||||
|
||||
function TreeView:draw()
|
||||
self:draw_background(style.background2)
|
||||
|
||||
local icon_width = style.icon_font:get_width("D")
|
||||
local spacing = style.font:get_width(" ") * 2
|
||||
|
||||
local doc = core.active_view.doc
|
||||
local active_filename = doc and system.absolute_path(doc.filename or "")
|
||||
|
||||
for item, x,y,w,h in self:each_item() do
|
||||
local color = style.text
|
||||
|
||||
-- highlight active_view doc
|
||||
if item.abs_filename == active_filename then
|
||||
color = style.accent
|
||||
end
|
||||
|
||||
-- hovered item background
|
||||
if item == self.hovered_item then
|
||||
renderer.draw_rect(x, y, w, h, style.line_highlight)
|
||||
color = style.accent
|
||||
end
|
||||
|
||||
-- icons
|
||||
x = x + item.depth * style.padding.x + style.padding.x
|
||||
if item.type == "dir" then
|
||||
local icon1 = item.expanded and "-" or "+"
|
||||
local icon2 = item.expanded and "D" or "d"
|
||||
common.draw_text(style.icon_font, color, icon1, nil, x, y, 0, h)
|
||||
x = x + style.padding.x
|
||||
common.draw_text(style.icon_font, color, icon2, nil, x, y, 0, h)
|
||||
x = x + icon_width
|
||||
else
|
||||
x = x + style.padding.x
|
||||
common.draw_text(style.icon_font, color, "f", nil, x, y, 0, h)
|
||||
x = x + icon_width
|
||||
end
|
||||
|
||||
-- text
|
||||
x = x + spacing
|
||||
x = common.draw_text(style.font, color, item.name, nil, x, y, 0, h)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-- init
|
||||
local view = TreeView()
|
||||
local node = core.root_view:get_active_node()
|
||||
node:split("left", view, true)
|
||||
|
||||
-- register commands and keymap
|
||||
command.add(nil, {
|
||||
["treeview:toggle"] = function()
|
||||
view.visible = not view.visible
|
||||
end,
|
||||
})
|
||||
|
||||
keymap.add { ["ctrl+t"] = "treeview:toggle" } --< @r-lyeh ctrl+// > ctrl+t
|
||||
|
||||
-- register some context menu items, if available
|
||||
local has_menu, menu = core.try(require, "plugins.contextmenu")
|
||||
local has_fsutils, fsutils = core.try(require, "plugins.fsutils")
|
||||
|
||||
if has_menu and has_fsutils then
|
||||
local function new_file_f(path)
|
||||
command.perform "core:new-doc"
|
||||
end
|
||||
|
||||
local function new_file()
|
||||
new_file_f(view.hovered_item.abs_filename)
|
||||
end
|
||||
|
||||
local function new_dir_f(path)
|
||||
core.command_view:enter("New directory name", function(dir)
|
||||
fsutils.mkdir(dir)
|
||||
end)
|
||||
core.command_view:set_text(path .. PATHSEP .. "New folder")
|
||||
end
|
||||
|
||||
local function new_dir()
|
||||
new_dir_f(view.hovered_item.abs_filename)
|
||||
end
|
||||
|
||||
local function delete_f(path)
|
||||
core.add_thread(function()
|
||||
local function wrap()
|
||||
return coroutine.wrap(function() fsutils.delete(path, true) end)
|
||||
end
|
||||
|
||||
for n in wrap() do
|
||||
if n % 100 == 0 then
|
||||
core.log("Deleted %d items.", n)
|
||||
coroutine.yield(0)
|
||||
end
|
||||
end
|
||||
|
||||
core.log("%q deleted.", path)
|
||||
end)
|
||||
end
|
||||
|
||||
local function delete()
|
||||
local path = view.hovered_item.abs_filename
|
||||
if view.hovered_item.type == "dir"
|
||||
and system.show_confirm_dialog("Delete confirmation", string.format("Do you really want to delete %q ?", path)) then
|
||||
delete_f(path)
|
||||
else
|
||||
delete_f(path)
|
||||
end
|
||||
end
|
||||
|
||||
local function dirname(path)
|
||||
local p = fsutils.split(path)
|
||||
table.remove(p)
|
||||
return table.concat(p, PATHSEP)
|
||||
end
|
||||
|
||||
local function rename()
|
||||
local oldname = view.hovered_item.abs_filename
|
||||
core.command_view:enter("Rename to", function(newname)
|
||||
fsutils.move(oldname, newname)
|
||||
core.log("Moved %q to %q", oldname, newname)
|
||||
end, common.path_suggest)
|
||||
core.command_view:set_text(dirname(oldname))
|
||||
end
|
||||
|
||||
local function copy_path()
|
||||
system.set_clipboard(view.hovered_item.abs_filename)
|
||||
end
|
||||
|
||||
menu:register(function() return view.hovered_item and view.hovered_item.type == "dir" end, {
|
||||
{ text = "New file", command = new_file },
|
||||
{ text = "New folder", command = new_dir },
|
||||
menu.DIVIDER,
|
||||
{ text = "Rename", command = rename },
|
||||
{ text = "Delete", command = delete },
|
||||
menu.DIVIDER,
|
||||
{ text = "Copy directory name", command = copy_path }
|
||||
})
|
||||
menu:register(function() return view.hovered_item and view.hovered_item.type == "file" end, {
|
||||
{ text = "Rename", command = rename },
|
||||
{ text = "Delete", command = delete },
|
||||
menu.DIVIDER,
|
||||
{ text = "Copy filename", command = copy_path }
|
||||
})
|
||||
-- general region of the treeview
|
||||
menu:register(function(x, y)
|
||||
local x1, y1, x2, y2 = view:get_content_bounds()
|
||||
return not view.hovered_item and x > x1 and x <= x2 and y > y1 and y <= y2
|
||||
end, {
|
||||
{ text = "New file", command = function() new_file_f(system.absolute_path('.')) end },
|
||||
{ text = "New folder", command = function() new_dir_f(system.absolute_path('.')) end }
|
||||
})
|
||||
end
|
||||
|
||||
return view --< @r-lyeh
|
|
@ -1,36 +0,0 @@
|
|||
local core = require "core"
|
||||
local command = require "core.command"
|
||||
local Doc = require "core.doc"
|
||||
|
||||
|
||||
local function trim_trailing_whitespace(doc)
|
||||
local cline, ccol = doc:get_selection()
|
||||
for i = 1, #doc.lines do
|
||||
local old_text = doc:get_text(i, 1, i, math.huge)
|
||||
local new_text = old_text:gsub("%s*$", "")
|
||||
|
||||
-- don't remove whitespace which would cause the caret to reposition
|
||||
if cline == i and ccol > #new_text then
|
||||
new_text = old_text:sub(1, ccol - 1)
|
||||
end
|
||||
|
||||
if old_text ~= new_text then
|
||||
doc:insert(i, 1, new_text)
|
||||
doc:remove(i, #new_text + 1, i, math.huge)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
command.add("core.docview", {
|
||||
["trim-whitespace:trim-trailing-whitespace"] = function()
|
||||
trim_trailing_whitespace(core.active_view.doc)
|
||||
end,
|
||||
})
|
||||
|
||||
|
||||
local save = Doc.save
|
||||
Doc.save = function(self, ...)
|
||||
trim_trailing_whitespace(self)
|
||||
save(self, ...)
|
||||
end
|
|
@ -1,179 +0,0 @@
|
|||
local core = require "core"
|
||||
local DocView = require "core.docview"
|
||||
|
||||
local workspace_filename = ".lite_workspace.lua"
|
||||
|
||||
|
||||
local function serialize(val)
|
||||
if type(val) == "string" then
|
||||
return string.format("%q", val)
|
||||
elseif type(val) == "table" then
|
||||
local t = {}
|
||||
for k, v in pairs(val) do
|
||||
table.insert(t, "[" .. serialize(k) .. "]=" .. serialize(v))
|
||||
end
|
||||
return "{" .. table.concat(t, ",") .. "}"
|
||||
end
|
||||
return tostring(val)
|
||||
end
|
||||
|
||||
|
||||
local function has_no_locked_children(node)
|
||||
if node.locked then return false end
|
||||
if node.type == "leaf" then return true end
|
||||
return has_no_locked_children(node.a) and has_no_locked_children(node.b)
|
||||
end
|
||||
|
||||
|
||||
local function get_unlocked_root(node)
|
||||
if node.type == "leaf" then
|
||||
return not node.locked and node
|
||||
end
|
||||
if has_no_locked_children(node) then
|
||||
return node
|
||||
end
|
||||
return get_unlocked_root(node.a) or get_unlocked_root(node.b)
|
||||
end
|
||||
|
||||
|
||||
local function save_view(view)
|
||||
local mt = getmetatable(view)
|
||||
if mt == DocView then
|
||||
return {
|
||||
type = "doc",
|
||||
active = (core.active_view == view),
|
||||
filename = view.doc.filename,
|
||||
selection = { view.doc:get_selection() },
|
||||
scroll = { x = view.scroll.to.x, y = view.scroll.to.y },
|
||||
text = not view.doc.filename and view.doc:get_text(1, 1, math.huge, math.huge)
|
||||
}
|
||||
end
|
||||
for name, mod in pairs(package.loaded) do
|
||||
if mod == mt then
|
||||
return {
|
||||
type = "view",
|
||||
active = (core.active_view == view),
|
||||
module = name
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local function load_view(t)
|
||||
if t.type == "doc" then
|
||||
local ok, doc = pcall(core.open_doc, t.filename)
|
||||
if not ok then
|
||||
return DocView(core.open_doc())
|
||||
end
|
||||
local dv = DocView(doc)
|
||||
if t.text then doc:insert(1, 1, t.text) end
|
||||
doc:set_selection(table.unpack(t.selection))
|
||||
dv.last_line, dv.last_col = doc:get_selection()
|
||||
dv.scroll.x, dv.scroll.to.x = t.scroll.x, t.scroll.x
|
||||
dv.scroll.y, dv.scroll.to.y = t.scroll.y, t.scroll.y
|
||||
return dv
|
||||
end
|
||||
return require(t.module)()
|
||||
end
|
||||
|
||||
|
||||
local function save_node(node)
|
||||
local res = {}
|
||||
res.type = node.type
|
||||
if node.type == "leaf" then
|
||||
res.views = {}
|
||||
for _, view in ipairs(node.views) do
|
||||
local t = save_view(view)
|
||||
if t then
|
||||
table.insert(res.views, t)
|
||||
if node.active_view == view then
|
||||
res.active_view = #res.views
|
||||
end
|
||||
end
|
||||
end
|
||||
else
|
||||
res.divider = node.divider
|
||||
res.a = save_node(node.a)
|
||||
res.b = save_node(node.b)
|
||||
end
|
||||
return res
|
||||
end
|
||||
|
||||
|
||||
local function load_node(node, t)
|
||||
if t.type == "leaf" then
|
||||
local res
|
||||
for _, v in ipairs(t.views) do
|
||||
local view = load_view(v)
|
||||
if v.active then res = view end
|
||||
node:add_view(view)
|
||||
end
|
||||
if t.active_view then
|
||||
node:set_active_view(node.views[t.active_view])
|
||||
end
|
||||
return res
|
||||
else
|
||||
node:split(t.type == "hsplit" and "right" or "down")
|
||||
node.divider = t.divider
|
||||
local res1 = load_node(node.a, t.a)
|
||||
local res2 = load_node(node.b, t.b)
|
||||
return res1 or res2
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local function save_workspace()
|
||||
local root = get_unlocked_root(core.root_view.root_node)
|
||||
local fp = io.open(workspace_filename, "w")
|
||||
if fp then
|
||||
fp:write("return ", serialize(save_node(root)), "\n")
|
||||
fp:close()
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local function load_workspace()
|
||||
local ok, t = pcall(dofile, workspace_filename)
|
||||
os.remove(workspace_filename)
|
||||
if ok then
|
||||
local root = get_unlocked_root(core.root_view.root_node)
|
||||
local active_view = load_node(root, t)
|
||||
if active_view then
|
||||
core.set_active_view(active_view)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local run = core.run
|
||||
function core.run(...)
|
||||
if #core.docs == 0 then
|
||||
core.try(load_workspace)
|
||||
|
||||
local exit = os.exit
|
||||
function os.exit(...)
|
||||
save_workspace()
|
||||
exit(...)
|
||||
end
|
||||
end
|
||||
|
||||
core.run = run
|
||||
return core.run(...)
|
||||
end
|
||||
|
||||
local run1 = core.run1
|
||||
function core.run1(...)
|
||||
if #core.docs == 0 then
|
||||
core.try(load_workspace)
|
||||
|
||||
local exit = os.exit
|
||||
function os.exit(...)
|
||||
save_workspace()
|
||||
exit(...)
|
||||
end
|
||||
end
|
||||
|
||||
core.run1 = run1
|
||||
return core.run1(...)
|
||||
end
|
File diff suppressed because it is too large
Load Diff
|
@ -1,11 +1,11 @@
|
|||
uniform samplerCube u_cubemap;
|
||||
|
||||
|
||||
in vec3 v_direction;
|
||||
out vec4 fragcolor;
|
||||
|
||||
|
||||
void main() {
|
||||
fragcolor = vec4(texture(u_cubemap, v_direction).rgb, 1.0);
|
||||
fragcolor.rgb = pow(fragcolor.rgb, vec3(1.0/2.2));
|
||||
uniform samplerCube u_cubemap;
|
||||
|
||||
|
||||
in vec3 v_direction;
|
||||
out vec4 fragcolor;
|
||||
|
||||
|
||||
void main() {
|
||||
fragcolor = vec4(texture(u_cubemap, v_direction).rgb, 1.0);
|
||||
fragcolor.rgb = pow(fragcolor.rgb, vec3(1.0/2.2));
|
||||
}
|
|
@ -1,154 +1,154 @@
|
|||
uniform vec3 uSunPos;
|
||||
uniform vec3 uRayOrigin;
|
||||
uniform float uSunIntensity;
|
||||
uniform float uPlanetRadius;
|
||||
uniform float uAtmosphereRadius;
|
||||
uniform vec3 uRayleighScattering;
|
||||
uniform float uMieScattering;
|
||||
uniform float uRayleighScaleHeight;
|
||||
uniform float uMieScaleHeight;
|
||||
uniform float uMiePreferredDirection;
|
||||
|
||||
|
||||
in vec3 v_direction;
|
||||
out vec4 fragcolor;
|
||||
|
||||
|
||||
vec3 atmosphere(vec3 r, vec3 r0, vec3 pSun, float iSun, float rPlanet, float rAtmos, vec3 kRlh, float kMie, float shRlh, float shMie, float g);
|
||||
|
||||
|
||||
void main() {
|
||||
vec3 color = atmosphere(
|
||||
normalize(v_direction), // normalized ray direction
|
||||
uRayOrigin, // ray origin
|
||||
uSunPos, // position of the sun
|
||||
uSunIntensity, // intensity of the sun
|
||||
uPlanetRadius, // radius of the planet in meters
|
||||
uAtmosphereRadius, // radius of the atmosphere in meters
|
||||
uRayleighScattering, // Rayleigh scattering coefficient
|
||||
uMieScattering, // Mie scattering coefficient
|
||||
uRayleighScaleHeight, // Rayleigh scale height
|
||||
uMieScaleHeight, // Mie scale height
|
||||
uMiePreferredDirection // Mie preferred scattering direction
|
||||
);
|
||||
|
||||
// Apply exposure.
|
||||
color = 1.0 - exp(-1.0 * color);
|
||||
|
||||
fragcolor = vec4(color, 1);
|
||||
}
|
||||
|
||||
|
||||
// [src] https://github.com/wwwtyro/glsl-atmosphere by wwwtyro (Unlicensed)
|
||||
// For more information, please refer to <http://unlicense.org>
|
||||
|
||||
|
||||
#define PI 3.141592
|
||||
#define iSteps 16
|
||||
#define jSteps 8
|
||||
|
||||
|
||||
vec2 rsi(vec3 r0, vec3 rd, float sr) {
|
||||
// ray-sphere intersection that assumes
|
||||
// the sphere is centered at the origin.
|
||||
// No intersection when result.x > result.y
|
||||
float a = dot(rd, rd);
|
||||
float b = 2.0 * dot(rd, r0);
|
||||
float c = dot(r0, r0) - (sr * sr);
|
||||
float d = (b*b) - 4.0*a*c;
|
||||
if (d < 0.0) return vec2(1e5,-1e5);
|
||||
return vec2(
|
||||
(-b - sqrt(d))/(2.0*a),
|
||||
(-b + sqrt(d))/(2.0*a)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
vec3 atmosphere(vec3 r, vec3 r0, vec3 pSun, float iSun, float rPlanet, float rAtmos, vec3 kRlh, float kMie, float shRlh, float shMie, float g) {
|
||||
// Normalize the sun and view directions.
|
||||
pSun = normalize(pSun);
|
||||
r = normalize(r);
|
||||
|
||||
// Calculate the step size of the primary ray.
|
||||
vec2 p = rsi(r0, r, rAtmos);
|
||||
if (p.x > p.y) return vec3(0,0,0);
|
||||
p.y = min(p.y, rsi(r0, r, rPlanet).x);
|
||||
float iStepSize = (p.y - p.x) / float(iSteps);
|
||||
|
||||
// Initialize the primary ray time.
|
||||
float iTime = 0.0;
|
||||
|
||||
// Initialize accumulators for Rayleigh and Mie scattering.
|
||||
vec3 totalRlh = vec3(0,0,0);
|
||||
vec3 totalMie = vec3(0,0,0);
|
||||
|
||||
// Initialize optical depth accumulators for the primary ray.
|
||||
float iOdRlh = 0.0;
|
||||
float iOdMie = 0.0;
|
||||
|
||||
// Calculate the Rayleigh and Mie phases.
|
||||
float mu = dot(r, pSun);
|
||||
float mumu = mu * mu;
|
||||
float gg = g * g;
|
||||
float pRlh = 3.0 / (16.0 * PI) * (1.0 + mumu);
|
||||
float pMie = 3.0 / (8.0 * PI) * ((1.0 - gg) * (mumu + 1.0)) / (pow(1.0 + gg - 2.0 * mu * g, 1.5) * (2.0 + gg));
|
||||
|
||||
// Sample the primary ray.
|
||||
for (int i = 0; i < iSteps; i++) {
|
||||
|
||||
// Calculate the primary ray sample position.
|
||||
vec3 iPos = r0 + r * (iTime + iStepSize * 0.5);
|
||||
|
||||
// Calculate the height of the sample.
|
||||
float iHeight = length(iPos) - rPlanet;
|
||||
|
||||
// Calculate the optical depth of the Rayleigh and Mie scattering for this step.
|
||||
float odStepRlh = exp(-iHeight / shRlh) * iStepSize;
|
||||
float odStepMie = exp(-iHeight / shMie) * iStepSize;
|
||||
|
||||
// Accumulate optical depth.
|
||||
iOdRlh += odStepRlh;
|
||||
iOdMie += odStepMie;
|
||||
|
||||
// Calculate the step size of the secondary ray.
|
||||
float jStepSize = rsi(iPos, pSun, rAtmos).y / float(jSteps);
|
||||
|
||||
// Initialize the secondary ray time.
|
||||
float jTime = 0.0;
|
||||
|
||||
// Initialize optical depth accumulators for the secondary ray.
|
||||
float jOdRlh = 0.0;
|
||||
float jOdMie = 0.0;
|
||||
|
||||
// Sample the secondary ray.
|
||||
for (int j = 0; j < jSteps; j++) {
|
||||
|
||||
// Calculate the secondary ray sample position.
|
||||
vec3 jPos = iPos + pSun * (jTime + jStepSize * 0.5);
|
||||
|
||||
// Calculate the height of the sample.
|
||||
float jHeight = length(jPos) - rPlanet;
|
||||
|
||||
// Accumulate the optical depth.
|
||||
jOdRlh += exp(-jHeight / shRlh) * jStepSize;
|
||||
jOdMie += exp(-jHeight / shMie) * jStepSize;
|
||||
|
||||
// Increment the secondary ray time.
|
||||
jTime += jStepSize;
|
||||
}
|
||||
|
||||
// Calculate attenuation.
|
||||
vec3 attn = exp(-(kMie * (iOdMie + jOdMie) + kRlh * (iOdRlh + jOdRlh)));
|
||||
|
||||
// Accumulate scattering.
|
||||
totalRlh += odStepRlh * attn;
|
||||
totalMie += odStepMie * attn;
|
||||
|
||||
// Increment the primary ray time.
|
||||
iTime += iStepSize;
|
||||
|
||||
}
|
||||
|
||||
// Calculate and return the final color.
|
||||
return iSun * (pRlh * kRlh * totalRlh + pMie * kMie * totalMie);
|
||||
uniform vec3 uSunPos;
|
||||
uniform vec3 uRayOrigin;
|
||||
uniform float uSunIntensity;
|
||||
uniform float uPlanetRadius;
|
||||
uniform float uAtmosphereRadius;
|
||||
uniform vec3 uRayleighScattering;
|
||||
uniform float uMieScattering;
|
||||
uniform float uRayleighScaleHeight;
|
||||
uniform float uMieScaleHeight;
|
||||
uniform float uMiePreferredDirection;
|
||||
|
||||
|
||||
in vec3 v_direction;
|
||||
out vec4 fragcolor;
|
||||
|
||||
|
||||
vec3 atmosphere(vec3 r, vec3 r0, vec3 pSun, float iSun, float rPlanet, float rAtmos, vec3 kRlh, float kMie, float shRlh, float shMie, float g);
|
||||
|
||||
|
||||
void main() {
|
||||
vec3 color = atmosphere(
|
||||
normalize(v_direction), // normalized ray direction
|
||||
uRayOrigin, // ray origin
|
||||
uSunPos, // position of the sun
|
||||
uSunIntensity, // intensity of the sun
|
||||
uPlanetRadius, // radius of the planet in meters
|
||||
uAtmosphereRadius, // radius of the atmosphere in meters
|
||||
uRayleighScattering, // Rayleigh scattering coefficient
|
||||
uMieScattering, // Mie scattering coefficient
|
||||
uRayleighScaleHeight, // Rayleigh scale height
|
||||
uMieScaleHeight, // Mie scale height
|
||||
uMiePreferredDirection // Mie preferred scattering direction
|
||||
);
|
||||
|
||||
// Apply exposure.
|
||||
color = 1.0 - exp(-1.0 * color);
|
||||
|
||||
fragcolor = vec4(color, 1);
|
||||
}
|
||||
|
||||
|
||||
// [src] https://github.com/wwwtyro/glsl-atmosphere by wwwtyro (Unlicensed)
|
||||
// For more information, please refer to <http://unlicense.org>
|
||||
|
||||
|
||||
#define PI 3.141592
|
||||
#define iSteps 16
|
||||
#define jSteps 8
|
||||
|
||||
|
||||
vec2 rsi(vec3 r0, vec3 rd, float sr) {
|
||||
// ray-sphere intersection that assumes
|
||||
// the sphere is centered at the origin.
|
||||
// No intersection when result.x > result.y
|
||||
float a = dot(rd, rd);
|
||||
float b = 2.0 * dot(rd, r0);
|
||||
float c = dot(r0, r0) - (sr * sr);
|
||||
float d = (b*b) - 4.0*a*c;
|
||||
if (d < 0.0) return vec2(1e5,-1e5);
|
||||
return vec2(
|
||||
(-b - sqrt(d))/(2.0*a),
|
||||
(-b + sqrt(d))/(2.0*a)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
vec3 atmosphere(vec3 r, vec3 r0, vec3 pSun, float iSun, float rPlanet, float rAtmos, vec3 kRlh, float kMie, float shRlh, float shMie, float g) {
|
||||
// Normalize the sun and view directions.
|
||||
pSun = normalize(pSun);
|
||||
r = normalize(r);
|
||||
|
||||
// Calculate the step size of the primary ray.
|
||||
vec2 p = rsi(r0, r, rAtmos);
|
||||
if (p.x > p.y) return vec3(0,0,0);
|
||||
p.y = min(p.y, rsi(r0, r, rPlanet).x);
|
||||
float iStepSize = (p.y - p.x) / float(iSteps);
|
||||
|
||||
// Initialize the primary ray time.
|
||||
float iTime = 0.0;
|
||||
|
||||
// Initialize accumulators for Rayleigh and Mie scattering.
|
||||
vec3 totalRlh = vec3(0,0,0);
|
||||
vec3 totalMie = vec3(0,0,0);
|
||||
|
||||
// Initialize optical depth accumulators for the primary ray.
|
||||
float iOdRlh = 0.0;
|
||||
float iOdMie = 0.0;
|
||||
|
||||
// Calculate the Rayleigh and Mie phases.
|
||||
float mu = dot(r, pSun);
|
||||
float mumu = mu * mu;
|
||||
float gg = g * g;
|
||||
float pRlh = 3.0 / (16.0 * PI) * (1.0 + mumu);
|
||||
float pMie = 3.0 / (8.0 * PI) * ((1.0 - gg) * (mumu + 1.0)) / (pow(1.0 + gg - 2.0 * mu * g, 1.5) * (2.0 + gg));
|
||||
|
||||
// Sample the primary ray.
|
||||
for (int i = 0; i < iSteps; i++) {
|
||||
|
||||
// Calculate the primary ray sample position.
|
||||
vec3 iPos = r0 + r * (iTime + iStepSize * 0.5);
|
||||
|
||||
// Calculate the height of the sample.
|
||||
float iHeight = length(iPos) - rPlanet;
|
||||
|
||||
// Calculate the optical depth of the Rayleigh and Mie scattering for this step.
|
||||
float odStepRlh = exp(-iHeight / shRlh) * iStepSize;
|
||||
float odStepMie = exp(-iHeight / shMie) * iStepSize;
|
||||
|
||||
// Accumulate optical depth.
|
||||
iOdRlh += odStepRlh;
|
||||
iOdMie += odStepMie;
|
||||
|
||||
// Calculate the step size of the secondary ray.
|
||||
float jStepSize = rsi(iPos, pSun, rAtmos).y / float(jSteps);
|
||||
|
||||
// Initialize the secondary ray time.
|
||||
float jTime = 0.0;
|
||||
|
||||
// Initialize optical depth accumulators for the secondary ray.
|
||||
float jOdRlh = 0.0;
|
||||
float jOdMie = 0.0;
|
||||
|
||||
// Sample the secondary ray.
|
||||
for (int j = 0; j < jSteps; j++) {
|
||||
|
||||
// Calculate the secondary ray sample position.
|
||||
vec3 jPos = iPos + pSun * (jTime + jStepSize * 0.5);
|
||||
|
||||
// Calculate the height of the sample.
|
||||
float jHeight = length(jPos) - rPlanet;
|
||||
|
||||
// Accumulate the optical depth.
|
||||
jOdRlh += exp(-jHeight / shRlh) * jStepSize;
|
||||
jOdMie += exp(-jHeight / shMie) * jStepSize;
|
||||
|
||||
// Increment the secondary ray time.
|
||||
jTime += jStepSize;
|
||||
}
|
||||
|
||||
// Calculate attenuation.
|
||||
vec3 attn = exp(-(kMie * (iOdMie + jOdMie) + kRlh * (iOdRlh + jOdRlh)));
|
||||
|
||||
// Accumulate scattering.
|
||||
totalRlh += odStepRlh * attn;
|
||||
totalMie += odStepMie * attn;
|
||||
|
||||
// Increment the primary ray time.
|
||||
iTime += iStepSize;
|
||||
|
||||
}
|
||||
|
||||
// Calculate and return the final color.
|
||||
return iSun * (pRlh * kRlh * totalRlh + pMie * kMie * totalMie);
|
||||
}
|
|
@ -1,485 +1,485 @@
|
|||
#version 400
|
||||
|
||||
// original PBR shader by @seece (Public Domain). link: https://github.com/Gargaj/Foxotron/pull/12
|
||||
|
||||
//#define textureQueryLod(t,c) vec2(0.0,0.0) // #version 400 required
|
||||
|
||||
uniform vec2 resolution = vec2(640.0,480.0); // debug options below use this (USE_MAP_DEBUGGING, USE_AMBIENT_DEBUGGING)
|
||||
|
||||
#define USE_BRUTEFORCE_IRRADIANCE false // Samples irradiance from tex_skysphere when enabled.
|
||||
#define USE_WRAPAROUND_SPECULAR true // Makes silhouettes more reflective to avoid black pixels.
|
||||
#define USE_SPECULAR_AO_ATTENUATION true // Dampens IBL specular ambient with AO if enabled.
|
||||
#define USE_NORMAL_VARIATION_TO_ROUGHNESS true // Increases roughness if normal map has variation and was minified.
|
||||
#define USE_MAP_DEBUGGING false // Shows all ColorMaps as horizontal bars
|
||||
#define USE_AMBIENT_DEBUGGING false // Splits the screen in two and shows image-based specular (left), full shading (middle), diffuse shading (right).
|
||||
#define BOOST_LIGHTING 2.00f // Multiplies analytic light's color with this constant because otherwise they look really pathetic.
|
||||
#define BOOST_SPECULAR 1.50f
|
||||
#define BOOST_NOISE 2.50f
|
||||
|
||||
struct Light
|
||||
{
|
||||
vec3 direction;
|
||||
vec3 color;
|
||||
};
|
||||
|
||||
struct ColorMap
|
||||
{
|
||||
bool has_tex;
|
||||
vec4 color;
|
||||
};
|
||||
|
||||
uniform ColorMap map_albedo; uniform sampler2D map_albedo_tex;
|
||||
uniform ColorMap map_diffuse; uniform sampler2D map_diffuse_tex;
|
||||
uniform ColorMap map_specular; uniform sampler2D map_specular_tex; // not used
|
||||
uniform ColorMap map_normals; uniform sampler2D map_normals_tex;
|
||||
uniform ColorMap map_roughness; uniform sampler2D map_roughness_tex;
|
||||
uniform ColorMap map_metallic; uniform sampler2D map_metallic_tex;
|
||||
uniform ColorMap map_ao; uniform sampler2D map_ao_tex;
|
||||
uniform ColorMap map_ambient; uniform sampler2D map_ambient_tex;
|
||||
uniform ColorMap map_emissive; uniform sampler2D map_emissive_tex;
|
||||
|
||||
#define sample_colormap(ColorMap_, uv_) \
|
||||
(ColorMap_.has_tex ? texture( ColorMap_##_tex, uv_ ) : ColorMap_.color)
|
||||
|
||||
in vec3 out_normal;
|
||||
in vec3 out_tangent;
|
||||
in vec3 out_binormal;
|
||||
in vec2 out_texcoord;
|
||||
in vec3 out_worldpos;
|
||||
in vec3 out_to_camera;
|
||||
|
||||
uniform float skysphere_rotation;
|
||||
uniform float skysphere_mip_count;
|
||||
uniform float exposure;
|
||||
uniform uint frame_count;
|
||||
uniform float specular_shininess;
|
||||
|
||||
uniform vec3 camera_position;
|
||||
uniform Light lights[3];
|
||||
|
||||
uniform sampler2D tex_skysphere;
|
||||
uniform sampler2D tex_skyenv;
|
||||
uniform sampler2D tex_brdf_lut;
|
||||
|
||||
uniform bool has_tex_skysphere;
|
||||
uniform bool has_tex_skyenv;
|
||||
|
||||
out vec4 frag_color;
|
||||
|
||||
const float PI = 3.1415926536;
|
||||
|
||||
// MurMurHash 3 finalizer. Implementation is in public domain.
|
||||
uint hash( uint h )
|
||||
{
|
||||
h ^= h >> 16;
|
||||
h *= 0x85ebca6bU;
|
||||
h ^= h >> 13;
|
||||
h *= 0xc2b2ae35U;
|
||||
h ^= h >> 16;
|
||||
return h;
|
||||
}
|
||||
|
||||
// Random function using the idea of StackOverflow user "Spatial" https://stackoverflow.com/a/17479300
|
||||
// Creates random 23 bits and puts them into the fraction bits of an 32-bit float.
|
||||
float random( uvec3 h )
|
||||
{
|
||||
uint m = hash(h.x ^ hash( h.y ) ^ hash( h.z ));
|
||||
return uintBitsToFloat( ( m & 0x007FFFFFu ) | 0x3f800000u ) - 1.;
|
||||
}
|
||||
|
||||
float random( vec3 v )
|
||||
{
|
||||
return random(floatBitsToUint( v ));
|
||||
}
|
||||
|
||||
vec3 fresnel_schlick( vec3 H, vec3 V, vec3 F0 )
|
||||
{
|
||||
float cosTheta = clamp( dot( H, V ), 0., 1. );
|
||||
return F0 + ( vec3( 1.0 ) - F0 ) * pow( 1. - cosTheta, 5.0 );
|
||||
}
|
||||
|
||||
// A Fresnel term that dampens rough specular reflections.
|
||||
// https://seblagarde.wordpress.com/2011/08/17/hello-world/
|
||||
vec3 fresnel_schlick_roughness( vec3 H, vec3 V, vec3 F0, float roughness )
|
||||
{
|
||||
float cosTheta = clamp( dot( H, V ), 0., 1. );
|
||||
return F0 + ( max( vec3( 1.0 - roughness ), F0 ) - F0 ) * pow( 1. - cosTheta, 5.0 );
|
||||
}
|
||||
|
||||
float distribution_ggx( vec3 N, vec3 H, float roughness )
|
||||
{
|
||||
float a = roughness * roughness;
|
||||
float a2 = a * a;
|
||||
float NdotH = max( 0., dot( N, H ) );
|
||||
float factor = NdotH * NdotH * ( a2 - 1. ) + 1.;
|
||||
|
||||
return a2 / ( PI * factor * factor );
|
||||
}
|
||||
|
||||
float geometry_schlick_ggx( vec3 N, vec3 V, float k )
|
||||
{
|
||||
float NdotV = max( 0., dot( N, V ) );
|
||||
return NdotV / (NdotV * ( 1. - k ) + k );
|
||||
}
|
||||
|
||||
float geometry_smith( vec3 N, vec3 V, vec3 L, float roughness )
|
||||
{
|
||||
#if 1 // original
|
||||
float r = roughness + 1.;
|
||||
float k = (r * r) / 8.;
|
||||
#elif 0 // vries
|
||||
float a = roughness;
|
||||
float k = (a * a) / 2.0;
|
||||
#elif 0 // vries improved?
|
||||
float a = roughness * roughness;
|
||||
float k = a / 2.0;
|
||||
#endif
|
||||
return geometry_schlick_ggx( N, V, k ) * geometry_schlick_ggx( N, L, k );
|
||||
}
|
||||
|
||||
vec2 sphere_to_polar( vec3 normal )
|
||||
{
|
||||
normal = normalize( normal );
|
||||
return vec2( ( atan( normal.z, normal.x ) + skysphere_rotation ) / PI / 2.0 + 0.5, acos( normal.y ) / PI );
|
||||
}
|
||||
|
||||
// Our vertically GL_CLAMPed textures seem to blend towards black when sampling the half-pixel edge.
|
||||
// Not sure if it has a border, or this if is a driver bug, but can repro on multiple nvidia cards.
|
||||
// Knowing the texture height we can limit sampling to the centers of the top and bottom pixel rows.
|
||||
vec2 sphere_to_polar_clamp_y( vec3 normal, float texture_height )
|
||||
{
|
||||
normal = normalize( normal );
|
||||
return vec2( ( atan( normal.z, normal.x ) + skysphere_rotation ) / PI / 2.0 + 0.5, clamp(acos( normal.y ) / PI, 0.5 / texture_height, 1.0 - 0.5 / texture_height) );
|
||||
}
|
||||
|
||||
vec3 sample_sky( vec3 normal )
|
||||
{
|
||||
vec2 polar = sphere_to_polar( normal );
|
||||
return texture( tex_skysphere, polar ).rgb * exposure;
|
||||
}
|
||||
|
||||
// Takes samples around the hemisphere, converts them to radiances via weighting and
|
||||
// returns a normalized sum.
|
||||
vec3 sample_irradiance_slow( vec3 normal, vec3 vertex_tangent )
|
||||
{
|
||||
float delta = 0.10;
|
||||
|
||||
vec3 up = abs( normal.y ) < 0.999 ? vec3( 0., 1., 0. ) : vec3( 0., 0., 1. );
|
||||
vec3 tangent_x = normalize( cross( up, normal ) );
|
||||
vec3 tangent_y = cross( normal, tangent_x );
|
||||
|
||||
int numIrradianceSamples = 0;
|
||||
|
||||
vec3 irradiance = vec3(0.);
|
||||
|
||||
for ( float phi = 0.; phi < 2. * PI ; phi += delta )
|
||||
{
|
||||
for ( float theta = 0.; theta < 0.5 * PI; theta += delta )
|
||||
{
|
||||
vec3 tangent_space = vec3(
|
||||
sin( theta ) * cos( phi ),
|
||||
sin( theta ) * sin( phi ),
|
||||
cos( theta ) );
|
||||
|
||||
vec3 world_space = tangent_space.x * tangent_x + tangent_space.y + tangent_y + tangent_space.z * normal;
|
||||
|
||||
vec3 color = sample_sky( world_space );
|
||||
irradiance += color * cos( theta ) * sin( theta );
|
||||
numIrradianceSamples++;
|
||||
}
|
||||
}
|
||||
|
||||
irradiance = PI * irradiance / float( numIrradianceSamples );
|
||||
return irradiance;
|
||||
}
|
||||
|
||||
vec3 sample_irradiance_fast( vec3 normal, vec3 vertex_tangent )
|
||||
{
|
||||
// Sample the irradiance map if it exists, otherwise fall back to blurred reflection map.
|
||||
if ( has_tex_skyenv )
|
||||
{
|
||||
vec2 polar = sphere_to_polar_clamp_y( normal, 180.0 );
|
||||
return textureLod( tex_skyenv, polar, 0.0 ).rgb * exposure;
|
||||
}
|
||||
else
|
||||
{
|
||||
vec2 polar = sphere_to_polar( normal );
|
||||
return textureLod( tex_skysphere, polar, 0.80 * skysphere_mip_count ).rgb * exposure;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
vec3 specular_ibl( vec3 V, vec3 N, float roughness, vec3 fresnel )
|
||||
{
|
||||
// What we'd like to do here is take a LOT of skybox samples around the reflection
|
||||
// vector R according to the BRDF lobe.
|
||||
//
|
||||
// Unfortunately it's not possible in real time so we use the following UE4 style approximations:
|
||||
// 1. Integrate incoming light and BRDF separately ("split sum approximation")
|
||||
// 2. Assume V = R = N so that we can just blur the skybox and sample that.
|
||||
// 3. Bake the BRDF integral into a lookup texture so that it can be computed in constant time.
|
||||
//
|
||||
// Here we also simplify approximation #2 by using bilinear mipmaps with a magic formula instead
|
||||
// of properly convolving it with a GGX lobe.
|
||||
//
|
||||
// For details, see Brian Karis, "Real Shading in Unreal Engine 4", 2013.
|
||||
|
||||
vec3 R = 2. * dot( V, N ) * N - V;
|
||||
|
||||
vec2 polar = sphere_to_polar( R );
|
||||
|
||||
// Map roughness from range [0, 1] into a mip LOD [0, skysphere_mip_count].
|
||||
// The magic numbers were chosen empirically.
|
||||
|
||||
float mip = 0.9 * skysphere_mip_count * pow(roughness, 0.25 * BOOST_SPECULAR);
|
||||
|
||||
vec3 prefiltered = textureLod( tex_skysphere, polar, mip ).rgb * exposure;
|
||||
|
||||
float NdotV = dot( N, V );
|
||||
|
||||
// dot( N, V ) seems to produce negative values so we can try to stretch it a bit behind the silhouette
|
||||
// to avoid black pixels.
|
||||
if (USE_WRAPAROUND_SPECULAR)
|
||||
{
|
||||
NdotV = NdotV * 0.9 + 0.1;
|
||||
}
|
||||
|
||||
NdotV = min(0.99, max(0.01, NdotV));
|
||||
|
||||
// A precomputed lookup table contains a scale and a bias term for specular intensity (called "fresnel" here).
|
||||
// See equation (8) in Karis' course notes mentioned above.
|
||||
vec2 envBRDF = texture( tex_brdf_lut, vec2(NdotV, 1.0-roughness) ).xy; // (NdotV,1-roughtness) for green top-left (NdotV,roughness) for green bottom-left
|
||||
vec3 specular = prefiltered * (fresnel * envBRDF.x + vec3(envBRDF.y));
|
||||
|
||||
return specular;
|
||||
}
|
||||
|
||||
|
||||
void main(void)
|
||||
{
|
||||
vec3 baseColor = vec3( 0.5, 0.5, 0.5 );
|
||||
float roughness = 1.0;
|
||||
float metallic = 0.0;
|
||||
float ao = 1.0;
|
||||
float alpha = 1.0;
|
||||
|
||||
vec4 baseColor_alpha;
|
||||
if ( map_albedo.has_tex )
|
||||
baseColor_alpha = sample_colormap( map_albedo, out_texcoord );
|
||||
else
|
||||
baseColor_alpha = sample_colormap( map_diffuse, out_texcoord );
|
||||
baseColor = baseColor_alpha.xyz;
|
||||
alpha = baseColor_alpha.w;
|
||||
|
||||
if( map_metallic.has_tex && map_roughness.has_tex ) {
|
||||
metallic = sample_colormap( map_metallic, out_texcoord ).x;
|
||||
roughness = sample_colormap( map_roughness, out_texcoord ).x;
|
||||
}
|
||||
else if( map_roughness.has_tex ) {
|
||||
//< @r-lyeh, metalness B, roughness G, (@todo: self-shadowing occlusion R; for now, any of R/B are metallic)
|
||||
metallic = sample_colormap( map_roughness, out_texcoord ).b + sample_colormap( map_roughness, out_texcoord ).r;
|
||||
roughness = sample_colormap( map_roughness, out_texcoord ).g;
|
||||
}
|
||||
|
||||
if ( map_ao.has_tex )
|
||||
ao = sample_colormap( map_ao, out_texcoord ).x;
|
||||
else if ( map_ambient.has_tex )
|
||||
ao = sample_colormap( map_ambient, out_texcoord ).x;
|
||||
|
||||
vec3 emissive = sample_colormap( map_emissive, out_texcoord ).rgb;
|
||||
|
||||
vec3 normalmap = texture( map_normals_tex, out_texcoord ).xyz * vec3(2.0) - vec3(1.0);
|
||||
float normalmap_mip = textureQueryLod( map_normals_tex, out_texcoord ).x;
|
||||
float normalmap_length = length(normalmap);
|
||||
normalmap /= normalmap_length;
|
||||
|
||||
vec3 normal = out_normal;
|
||||
|
||||
if ( map_normals.has_tex )
|
||||
{
|
||||
// Mikkelsen's tangent space normal map decoding. See http://mikktspace.com/ for rationale.
|
||||
vec3 bi = cross( out_normal, out_tangent );
|
||||
vec3 nmap = normalmap.xyz;
|
||||
normal = nmap.x * out_tangent + nmap.y * bi + nmap.z * out_normal;
|
||||
}
|
||||
|
||||
normal = normalize( normal );
|
||||
|
||||
if( USE_MAP_DEBUGGING && !USE_AMBIENT_DEBUGGING )
|
||||
{
|
||||
vec3 c = vec3(1., 0., 0.);
|
||||
float x = gl_FragCoord.x / resolution.x;
|
||||
float y = gl_FragCoord.y / resolution.y;
|
||||
if ( y < (7.0/7.0) ) c = vec3(.5) + .5*out_normal;
|
||||
if ( y < (6.0/7.0) ) c = vec3(.5) + .5*normalmap;
|
||||
if ( y < (5.0/7.0) ) c = vec3(ao);
|
||||
if ( y < (4.0/7.0) ) c = vec3(emissive);
|
||||
if ( y < (3.0/7.0) ) c = vec3(metallic);
|
||||
if ( y < (2.0/7.0) ) c = vec3(roughness);
|
||||
if ( y < (1.0/7.0) ) c = baseColor;
|
||||
frag_color = vec4(c, 1.);
|
||||
return;
|
||||
}
|
||||
|
||||
if (USE_NORMAL_VARIATION_TO_ROUGHNESS)
|
||||
{
|
||||
// Try to reduce specular aliasing by increasing roughness when minified normal maps have high variation.
|
||||
float variation = 1. - pow( normalmap_length, 8. );
|
||||
float minification = clamp( normalmap_mip - 2., 0., 1. );
|
||||
roughness = mix( roughness, 1.0, variation * minification );
|
||||
}
|
||||
|
||||
|
||||
vec3 N = normal;
|
||||
vec3 V = normalize( out_to_camera );
|
||||
|
||||
vec3 Lo = vec3(0.);
|
||||
vec3 F0 = vec3(0.04);
|
||||
F0 = mix( F0, baseColor, metallic );
|
||||
|
||||
bool use_ibl = has_tex_skysphere;
|
||||
|
||||
// Add contributions from analytic lights only if we don't have a skybox.
|
||||
|
||||
{
|
||||
int num_lights = use_ibl ? 1 : lights.length();
|
||||
for ( int i = 0; i < num_lights; i++ )
|
||||
{
|
||||
vec3 radiance = lights[ i ].color * BOOST_LIGHTING;
|
||||
|
||||
vec3 L = -normalize( lights[ i ].direction );
|
||||
vec3 H = normalize( V + L );
|
||||
|
||||
vec3 F = fresnel_schlick( H, V, F0 );
|
||||
vec3 kS = F;
|
||||
vec3 kD = vec3(1.0) - kS;
|
||||
kD *= 1.0 - metallic;
|
||||
|
||||
// Premultiplied alpha applied to the diffuse component only
|
||||
kD *= alpha;
|
||||
|
||||
float D = distribution_ggx( N, H, roughness );
|
||||
float G = geometry_smith( N, V, L, roughness );
|
||||
|
||||
vec3 num = D * F * G;
|
||||
float denom = 4. * max( 0., dot( N, V ) ) * max( 0., dot( N, L ) );
|
||||
|
||||
vec3 specular = kS * (num / max( 0.001, denom ));
|
||||
|
||||
float NdotL = max( 0., dot( N, L ) );
|
||||
|
||||
Lo += ( kD * ( baseColor / PI ) + specular ) * radiance * NdotL;
|
||||
}
|
||||
}
|
||||
|
||||
vec3 ambient = sample_colormap( map_ambient, out_texcoord ).xyz;
|
||||
vec3 diffuse_ambient;
|
||||
vec3 specular_ambient;
|
||||
|
||||
if ( use_ibl )
|
||||
{
|
||||
// Image based lighting.
|
||||
// Based on https://learnopengl.com/PBR/IBL/Diffuse-irradiance
|
||||
|
||||
vec3 irradiance = vec3(0.);
|
||||
|
||||
if ( USE_BRUTEFORCE_IRRADIANCE )
|
||||
{
|
||||
irradiance = sample_irradiance_slow( normal, out_tangent );
|
||||
}
|
||||
else
|
||||
{
|
||||
irradiance = sample_irradiance_fast( normal, out_tangent );
|
||||
}
|
||||
|
||||
// Compute the Fresnel term for a perfect mirror reflection with L = R.
|
||||
// In this case the halfway vector H = N.
|
||||
//
|
||||
// We use a modified Fresnel function that dampens specular reflections of very
|
||||
// rough surfaces to avoid too bright pixels at grazing angles.
|
||||
vec3 F = fresnel_schlick_roughness( N, V, F0, roughness );
|
||||
vec3 kS = F;
|
||||
|
||||
// Subtract the amount of reflected light (specular) to get the energy left for
|
||||
// absorbed (diffuse) light.
|
||||
vec3 kD = vec3(1.) - kS;
|
||||
|
||||
// Metallic surfaces have only a specular reflection.
|
||||
kD *= 1.0 - metallic;
|
||||
|
||||
// Premultiplied alpha applied to the diffuse component only
|
||||
kD *= alpha;
|
||||
|
||||
// Modulate the incoming lighting with the diffuse color: some wavelengths get absorbed.
|
||||
diffuse_ambient = irradiance * baseColor;
|
||||
|
||||
// Ambient light also has a specular part.
|
||||
specular_ambient = specular_ibl( V, normal, roughness, F );
|
||||
|
||||
// Ambient occlusion tells us the fraction of sky light that reaches this point.
|
||||
if (USE_SPECULAR_AO_ATTENUATION)
|
||||
{
|
||||
ambient = ao * (kD * diffuse_ambient + specular_ambient);
|
||||
}
|
||||
else
|
||||
{
|
||||
// We don't attenuate specular_ambient ambient here with AO which might cause flickering in dark cavities.
|
||||
ambient = ao * (kD * diffuse_ambient) + specular_ambient;
|
||||
}
|
||||
}
|
||||
|
||||
vec3 color = (ambient + Lo) + emissive;
|
||||
|
||||
if ( USE_AMBIENT_DEBUGGING )
|
||||
{
|
||||
float y = gl_FragCoord.y / resolution.y;
|
||||
if( USE_MAP_DEBUGGING && y > 0.5 )
|
||||
{
|
||||
if ( (y-0.5) < (7.0/7.0/2.0) ) color = vec3(.5) + .5*out_normal;
|
||||
if ( (y-0.5) < (6.0/7.0/2.0) ) color = vec3(.5) + .5*normalmap;
|
||||
if ( (y-0.5) < (5.0/7.0/2.0) ) color = vec3(ao);
|
||||
if ( (y-0.5) < (4.0/7.0/2.0) ) color = vec3(emissive);
|
||||
if ( (y-0.5) < (3.0/7.0/2.0) ) color = vec3(metallic);
|
||||
if ( (y-0.5) < (2.0/7.0/2.0) ) color = vec3(roughness);
|
||||
if ( (y-0.5) < (1.0/7.0/2.0) ) color = baseColor;
|
||||
} else {
|
||||
float x = gl_FragCoord.x / resolution.x;
|
||||
if ( x < 0.33 )
|
||||
color = specular_ambient;
|
||||
else if( x > 0.66 )
|
||||
color = diffuse_ambient;
|
||||
}
|
||||
}
|
||||
|
||||
#if 0 // original
|
||||
// basic tonemap and gamma correction
|
||||
color = color / ( vec3(1.) + color );
|
||||
color = pow( color, vec3(1. / 2.2) );
|
||||
#elif 0
|
||||
// filmic tonemapper
|
||||
vec3 linearColor = color;
|
||||
vec3 x = max(vec3(0.0), linearColor - 0.004);
|
||||
color = (x * (6.2 * x + 0.5)) / (x * (6.2 * x + 1.7) + 0.06);
|
||||
// gamma correction
|
||||
// color = pow( color, vec3(1. / 2.2) );
|
||||
#elif 1
|
||||
// aces film (CC0, src: https://knarkowicz.wordpress.com/2016/01/06/aces-filmic-tone-mapping-curve/)
|
||||
vec3 x = color;
|
||||
float a = 2.51f;
|
||||
float b = 0.03f;
|
||||
float c = 2.43f;
|
||||
float d = 0.59f;
|
||||
float e = 0.14f;
|
||||
color = clamp((x*(a*x+b))/(x*(c*x+d)+e), 0.0, 1.0);
|
||||
// gamma correction
|
||||
color = pow( color, vec3(1. / 2.2) );
|
||||
#endif
|
||||
|
||||
// dither with noise.
|
||||
// float dither = random( uvec3( floatBitsToUint( gl_FragCoord.xy ), frame_count ) );
|
||||
// color += BOOST_NOISE * vec3( (-1.0/256.) + (2./256.) * dither );
|
||||
|
||||
// Technically this alpha may be too transparent, if there is a lot of reflected light we wouldn't
|
||||
// see the background, maybe we can approximate it well enough by adding a fresnel term
|
||||
frag_color = vec4( color, alpha );
|
||||
}
|
||||
#version 400
|
||||
|
||||
// original PBR shader by @seece (Public Domain). link: https://github.com/Gargaj/Foxotron/pull/12
|
||||
|
||||
//#define textureQueryLod(t,c) vec2(0.0,0.0) // #version 400 required
|
||||
|
||||
uniform vec2 resolution = vec2(640.0,480.0); // debug options below use this (USE_MAP_DEBUGGING, USE_AMBIENT_DEBUGGING)
|
||||
|
||||
#define USE_BRUTEFORCE_IRRADIANCE false // Samples irradiance from tex_skysphere when enabled.
|
||||
#define USE_WRAPAROUND_SPECULAR true // Makes silhouettes more reflective to avoid black pixels.
|
||||
#define USE_SPECULAR_AO_ATTENUATION true // Dampens IBL specular ambient with AO if enabled.
|
||||
#define USE_NORMAL_VARIATION_TO_ROUGHNESS true // Increases roughness if normal map has variation and was minified.
|
||||
#define USE_MAP_DEBUGGING false // Shows all ColorMaps as horizontal bars
|
||||
#define USE_AMBIENT_DEBUGGING false // Splits the screen in two and shows image-based specular (left), full shading (middle), diffuse shading (right).
|
||||
#define BOOST_LIGHTING 2.00f // Multiplies analytic light's color with this constant because otherwise they look really pathetic.
|
||||
#define BOOST_SPECULAR 1.50f
|
||||
#define BOOST_NOISE 2.50f
|
||||
|
||||
struct Light
|
||||
{
|
||||
vec3 direction;
|
||||
vec3 color;
|
||||
};
|
||||
|
||||
struct ColorMap
|
||||
{
|
||||
bool has_tex;
|
||||
vec4 color;
|
||||
};
|
||||
|
||||
uniform ColorMap map_albedo; uniform sampler2D map_albedo_tex;
|
||||
uniform ColorMap map_diffuse; uniform sampler2D map_diffuse_tex;
|
||||
uniform ColorMap map_specular; uniform sampler2D map_specular_tex; // not used
|
||||
uniform ColorMap map_normals; uniform sampler2D map_normals_tex;
|
||||
uniform ColorMap map_roughness; uniform sampler2D map_roughness_tex;
|
||||
uniform ColorMap map_metallic; uniform sampler2D map_metallic_tex;
|
||||
uniform ColorMap map_ao; uniform sampler2D map_ao_tex;
|
||||
uniform ColorMap map_ambient; uniform sampler2D map_ambient_tex;
|
||||
uniform ColorMap map_emissive; uniform sampler2D map_emissive_tex;
|
||||
|
||||
#define sample_colormap(ColorMap_, uv_) \
|
||||
(ColorMap_.has_tex ? texture( ColorMap_##_tex, uv_ ) : ColorMap_.color)
|
||||
|
||||
in vec3 out_normal;
|
||||
in vec3 out_tangent;
|
||||
in vec3 out_binormal;
|
||||
in vec2 out_texcoord;
|
||||
in vec3 out_worldpos;
|
||||
in vec3 out_to_camera;
|
||||
|
||||
uniform float skysphere_rotation;
|
||||
uniform float skysphere_mip_count;
|
||||
uniform float exposure;
|
||||
uniform uint frame_count;
|
||||
uniform float specular_shininess;
|
||||
|
||||
uniform vec3 camera_position;
|
||||
uniform Light lights[3];
|
||||
|
||||
uniform sampler2D tex_skysphere;
|
||||
uniform sampler2D tex_skyenv;
|
||||
uniform sampler2D tex_brdf_lut;
|
||||
|
||||
uniform bool has_tex_skysphere;
|
||||
uniform bool has_tex_skyenv;
|
||||
|
||||
out vec4 frag_color;
|
||||
|
||||
const float PI = 3.1415926536;
|
||||
|
||||
// MurMurHash 3 finalizer. Implementation is in public domain.
|
||||
uint hash( uint h )
|
||||
{
|
||||
h ^= h >> 16;
|
||||
h *= 0x85ebca6bU;
|
||||
h ^= h >> 13;
|
||||
h *= 0xc2b2ae35U;
|
||||
h ^= h >> 16;
|
||||
return h;
|
||||
}
|
||||
|
||||
// Random function using the idea of StackOverflow user "Spatial" https://stackoverflow.com/a/17479300
|
||||
// Creates random 23 bits and puts them into the fraction bits of an 32-bit float.
|
||||
float random( uvec3 h )
|
||||
{
|
||||
uint m = hash(h.x ^ hash( h.y ) ^ hash( h.z ));
|
||||
return uintBitsToFloat( ( m & 0x007FFFFFu ) | 0x3f800000u ) - 1.;
|
||||
}
|
||||
|
||||
float random( vec3 v )
|
||||
{
|
||||
return random(floatBitsToUint( v ));
|
||||
}
|
||||
|
||||
vec3 fresnel_schlick( vec3 H, vec3 V, vec3 F0 )
|
||||
{
|
||||
float cosTheta = clamp( dot( H, V ), 0., 1. );
|
||||
return F0 + ( vec3( 1.0 ) - F0 ) * pow( 1. - cosTheta, 5.0 );
|
||||
}
|
||||
|
||||
// A Fresnel term that dampens rough specular reflections.
|
||||
// https://seblagarde.wordpress.com/2011/08/17/hello-world/
|
||||
vec3 fresnel_schlick_roughness( vec3 H, vec3 V, vec3 F0, float roughness )
|
||||
{
|
||||
float cosTheta = clamp( dot( H, V ), 0., 1. );
|
||||
return F0 + ( max( vec3( 1.0 - roughness ), F0 ) - F0 ) * pow( 1. - cosTheta, 5.0 );
|
||||
}
|
||||
|
||||
float distribution_ggx( vec3 N, vec3 H, float roughness )
|
||||
{
|
||||
float a = roughness * roughness;
|
||||
float a2 = a * a;
|
||||
float NdotH = max( 0., dot( N, H ) );
|
||||
float factor = NdotH * NdotH * ( a2 - 1. ) + 1.;
|
||||
|
||||
return a2 / ( PI * factor * factor );
|
||||
}
|
||||
|
||||
float geometry_schlick_ggx( vec3 N, vec3 V, float k )
|
||||
{
|
||||
float NdotV = max( 0., dot( N, V ) );
|
||||
return NdotV / (NdotV * ( 1. - k ) + k );
|
||||
}
|
||||
|
||||
float geometry_smith( vec3 N, vec3 V, vec3 L, float roughness )
|
||||
{
|
||||
#if 1 // original
|
||||
float r = roughness + 1.;
|
||||
float k = (r * r) / 8.;
|
||||
#elif 0 // vries
|
||||
float a = roughness;
|
||||
float k = (a * a) / 2.0;
|
||||
#elif 0 // vries improved?
|
||||
float a = roughness * roughness;
|
||||
float k = a / 2.0;
|
||||
#endif
|
||||
return geometry_schlick_ggx( N, V, k ) * geometry_schlick_ggx( N, L, k );
|
||||
}
|
||||
|
||||
vec2 sphere_to_polar( vec3 normal )
|
||||
{
|
||||
normal = normalize( normal );
|
||||
return vec2( ( atan( normal.z, normal.x ) + skysphere_rotation ) / PI / 2.0 + 0.5, acos( normal.y ) / PI );
|
||||
}
|
||||
|
||||
// Our vertically GL_CLAMPed textures seem to blend towards black when sampling the half-pixel edge.
|
||||
// Not sure if it has a border, or this if is a driver bug, but can repro on multiple nvidia cards.
|
||||
// Knowing the texture height we can limit sampling to the centers of the top and bottom pixel rows.
|
||||
vec2 sphere_to_polar_clamp_y( vec3 normal, float texture_height )
|
||||
{
|
||||
normal = normalize( normal );
|
||||
return vec2( ( atan( normal.z, normal.x ) + skysphere_rotation ) / PI / 2.0 + 0.5, clamp(acos( normal.y ) / PI, 0.5 / texture_height, 1.0 - 0.5 / texture_height) );
|
||||
}
|
||||
|
||||
vec3 sample_sky( vec3 normal )
|
||||
{
|
||||
vec2 polar = sphere_to_polar( normal );
|
||||
return texture( tex_skysphere, polar ).rgb * exposure;
|
||||
}
|
||||
|
||||
// Takes samples around the hemisphere, converts them to radiances via weighting and
|
||||
// returns a normalized sum.
|
||||
vec3 sample_irradiance_slow( vec3 normal, vec3 vertex_tangent )
|
||||
{
|
||||
float delta = 0.10;
|
||||
|
||||
vec3 up = abs( normal.y ) < 0.999 ? vec3( 0., 1., 0. ) : vec3( 0., 0., 1. );
|
||||
vec3 tangent_x = normalize( cross( up, normal ) );
|
||||
vec3 tangent_y = cross( normal, tangent_x );
|
||||
|
||||
int numIrradianceSamples = 0;
|
||||
|
||||
vec3 irradiance = vec3(0.);
|
||||
|
||||
for ( float phi = 0.; phi < 2. * PI ; phi += delta )
|
||||
{
|
||||
for ( float theta = 0.; theta < 0.5 * PI; theta += delta )
|
||||
{
|
||||
vec3 tangent_space = vec3(
|
||||
sin( theta ) * cos( phi ),
|
||||
sin( theta ) * sin( phi ),
|
||||
cos( theta ) );
|
||||
|
||||
vec3 world_space = tangent_space.x * tangent_x + tangent_space.y + tangent_y + tangent_space.z * normal;
|
||||
|
||||
vec3 color = sample_sky( world_space );
|
||||
irradiance += color * cos( theta ) * sin( theta );
|
||||
numIrradianceSamples++;
|
||||
}
|
||||
}
|
||||
|
||||
irradiance = PI * irradiance / float( numIrradianceSamples );
|
||||
return irradiance;
|
||||
}
|
||||
|
||||
vec3 sample_irradiance_fast( vec3 normal, vec3 vertex_tangent )
|
||||
{
|
||||
// Sample the irradiance map if it exists, otherwise fall back to blurred reflection map.
|
||||
if ( has_tex_skyenv )
|
||||
{
|
||||
vec2 polar = sphere_to_polar_clamp_y( normal, 180.0 );
|
||||
return textureLod( tex_skyenv, polar, 0.0 ).rgb * exposure;
|
||||
}
|
||||
else
|
||||
{
|
||||
vec2 polar = sphere_to_polar( normal );
|
||||
return textureLod( tex_skysphere, polar, 0.80 * skysphere_mip_count ).rgb * exposure;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
vec3 specular_ibl( vec3 V, vec3 N, float roughness, vec3 fresnel )
|
||||
{
|
||||
// What we'd like to do here is take a LOT of skybox samples around the reflection
|
||||
// vector R according to the BRDF lobe.
|
||||
//
|
||||
// Unfortunately it's not possible in real time so we use the following UE4 style approximations:
|
||||
// 1. Integrate incoming light and BRDF separately ("split sum approximation")
|
||||
// 2. Assume V = R = N so that we can just blur the skybox and sample that.
|
||||
// 3. Bake the BRDF integral into a lookup texture so that it can be computed in constant time.
|
||||
//
|
||||
// Here we also simplify approximation #2 by using bilinear mipmaps with a magic formula instead
|
||||
// of properly convolving it with a GGX lobe.
|
||||
//
|
||||
// For details, see Brian Karis, "Real Shading in Unreal Engine 4", 2013.
|
||||
|
||||
vec3 R = 2. * dot( V, N ) * N - V;
|
||||
|
||||
vec2 polar = sphere_to_polar( R );
|
||||
|
||||
// Map roughness from range [0, 1] into a mip LOD [0, skysphere_mip_count].
|
||||
// The magic numbers were chosen empirically.
|
||||
|
||||
float mip = 0.9 * skysphere_mip_count * pow(roughness, 0.25 * BOOST_SPECULAR);
|
||||
|
||||
vec3 prefiltered = textureLod( tex_skysphere, polar, mip ).rgb * exposure;
|
||||
|
||||
float NdotV = dot( N, V );
|
||||
|
||||
// dot( N, V ) seems to produce negative values so we can try to stretch it a bit behind the silhouette
|
||||
// to avoid black pixels.
|
||||
if (USE_WRAPAROUND_SPECULAR)
|
||||
{
|
||||
NdotV = NdotV * 0.9 + 0.1;
|
||||
}
|
||||
|
||||
NdotV = min(0.99, max(0.01, NdotV));
|
||||
|
||||
// A precomputed lookup table contains a scale and a bias term for specular intensity (called "fresnel" here).
|
||||
// See equation (8) in Karis' course notes mentioned above.
|
||||
vec2 envBRDF = texture( tex_brdf_lut, vec2(NdotV, 1.0-roughness) ).xy; // (NdotV,1-roughtness) for green top-left (NdotV,roughness) for green bottom-left
|
||||
vec3 specular = prefiltered * (fresnel * envBRDF.x + vec3(envBRDF.y));
|
||||
|
||||
return specular;
|
||||
}
|
||||
|
||||
|
||||
void main(void)
|
||||
{
|
||||
vec3 baseColor = vec3( 0.5, 0.5, 0.5 );
|
||||
float roughness = 1.0;
|
||||
float metallic = 0.0;
|
||||
float ao = 1.0;
|
||||
float alpha = 1.0;
|
||||
|
||||
vec4 baseColor_alpha;
|
||||
if ( map_albedo.has_tex )
|
||||
baseColor_alpha = sample_colormap( map_albedo, out_texcoord );
|
||||
else
|
||||
baseColor_alpha = sample_colormap( map_diffuse, out_texcoord );
|
||||
baseColor = baseColor_alpha.xyz;
|
||||
alpha = baseColor_alpha.w;
|
||||
|
||||
if( map_metallic.has_tex && map_roughness.has_tex ) {
|
||||
metallic = sample_colormap( map_metallic, out_texcoord ).x;
|
||||
roughness = sample_colormap( map_roughness, out_texcoord ).x;
|
||||
}
|
||||
else if( map_roughness.has_tex ) {
|
||||
//< @r-lyeh, metalness B, roughness G, (@todo: self-shadowing occlusion R; for now, any of R/B are metallic)
|
||||
metallic = sample_colormap( map_roughness, out_texcoord ).b + sample_colormap( map_roughness, out_texcoord ).r;
|
||||
roughness = sample_colormap( map_roughness, out_texcoord ).g;
|
||||
}
|
||||
|
||||
if ( map_ao.has_tex )
|
||||
ao = sample_colormap( map_ao, out_texcoord ).x;
|
||||
else if ( map_ambient.has_tex )
|
||||
ao = sample_colormap( map_ambient, out_texcoord ).x;
|
||||
|
||||
vec3 emissive = sample_colormap( map_emissive, out_texcoord ).rgb;
|
||||
|
||||
vec3 normalmap = texture( map_normals_tex, out_texcoord ).xyz * vec3(2.0) - vec3(1.0);
|
||||
float normalmap_mip = textureQueryLod( map_normals_tex, out_texcoord ).x;
|
||||
float normalmap_length = length(normalmap);
|
||||
normalmap /= normalmap_length;
|
||||
|
||||
vec3 normal = out_normal;
|
||||
|
||||
if ( map_normals.has_tex )
|
||||
{
|
||||
// Mikkelsen's tangent space normal map decoding. See http://mikktspace.com/ for rationale.
|
||||
vec3 bi = cross( out_normal, out_tangent );
|
||||
vec3 nmap = normalmap.xyz;
|
||||
normal = nmap.x * out_tangent + nmap.y * bi + nmap.z * out_normal;
|
||||
}
|
||||
|
||||
normal = normalize( normal );
|
||||
|
||||
if( USE_MAP_DEBUGGING && !USE_AMBIENT_DEBUGGING )
|
||||
{
|
||||
vec3 c = vec3(1., 0., 0.);
|
||||
float x = gl_FragCoord.x / resolution.x;
|
||||
float y = gl_FragCoord.y / resolution.y;
|
||||
if ( y < (7.0/7.0) ) c = vec3(.5) + .5*out_normal;
|
||||
if ( y < (6.0/7.0) ) c = vec3(.5) + .5*normalmap;
|
||||
if ( y < (5.0/7.0) ) c = vec3(ao);
|
||||
if ( y < (4.0/7.0) ) c = vec3(emissive);
|
||||
if ( y < (3.0/7.0) ) c = vec3(metallic);
|
||||
if ( y < (2.0/7.0) ) c = vec3(roughness);
|
||||
if ( y < (1.0/7.0) ) c = baseColor;
|
||||
frag_color = vec4(c, 1.);
|
||||
return;
|
||||
}
|
||||
|
||||
if (USE_NORMAL_VARIATION_TO_ROUGHNESS)
|
||||
{
|
||||
// Try to reduce specular aliasing by increasing roughness when minified normal maps have high variation.
|
||||
float variation = 1. - pow( normalmap_length, 8. );
|
||||
float minification = clamp( normalmap_mip - 2., 0., 1. );
|
||||
roughness = mix( roughness, 1.0, variation * minification );
|
||||
}
|
||||
|
||||
|
||||
vec3 N = normal;
|
||||
vec3 V = normalize( out_to_camera );
|
||||
|
||||
vec3 Lo = vec3(0.);
|
||||
vec3 F0 = vec3(0.04);
|
||||
F0 = mix( F0, baseColor, metallic );
|
||||
|
||||
bool use_ibl = has_tex_skysphere;
|
||||
|
||||
// Add contributions from analytic lights only if we don't have a skybox.
|
||||
|
||||
{
|
||||
int num_lights = use_ibl ? 1 : lights.length();
|
||||
for ( int i = 0; i < num_lights; i++ )
|
||||
{
|
||||
vec3 radiance = lights[ i ].color * BOOST_LIGHTING;
|
||||
|
||||
vec3 L = -normalize( lights[ i ].direction );
|
||||
vec3 H = normalize( V + L );
|
||||
|
||||
vec3 F = fresnel_schlick( H, V, F0 );
|
||||
vec3 kS = F;
|
||||
vec3 kD = vec3(1.0) - kS;
|
||||
kD *= 1.0 - metallic;
|
||||
|
||||
// Premultiplied alpha applied to the diffuse component only
|
||||
kD *= alpha;
|
||||
|
||||
float D = distribution_ggx( N, H, roughness );
|
||||
float G = geometry_smith( N, V, L, roughness );
|
||||
|
||||
vec3 num = D * F * G;
|
||||
float denom = 4. * max( 0., dot( N, V ) ) * max( 0., dot( N, L ) );
|
||||
|
||||
vec3 specular = kS * (num / max( 0.001, denom ));
|
||||
|
||||
float NdotL = max( 0., dot( N, L ) );
|
||||
|
||||
Lo += ( kD * ( baseColor / PI ) + specular ) * radiance * NdotL;
|
||||
}
|
||||
}
|
||||
|
||||
vec3 ambient = sample_colormap( map_ambient, out_texcoord ).xyz;
|
||||
vec3 diffuse_ambient;
|
||||
vec3 specular_ambient;
|
||||
|
||||
if ( use_ibl )
|
||||
{
|
||||
// Image based lighting.
|
||||
// Based on https://learnopengl.com/PBR/IBL/Diffuse-irradiance
|
||||
|
||||
vec3 irradiance = vec3(0.);
|
||||
|
||||
if ( USE_BRUTEFORCE_IRRADIANCE )
|
||||
{
|
||||
irradiance = sample_irradiance_slow( normal, out_tangent );
|
||||
}
|
||||
else
|
||||
{
|
||||
irradiance = sample_irradiance_fast( normal, out_tangent );
|
||||
}
|
||||
|
||||
// Compute the Fresnel term for a perfect mirror reflection with L = R.
|
||||
// In this case the halfway vector H = N.
|
||||
//
|
||||
// We use a modified Fresnel function that dampens specular reflections of very
|
||||
// rough surfaces to avoid too bright pixels at grazing angles.
|
||||
vec3 F = fresnel_schlick_roughness( N, V, F0, roughness );
|
||||
vec3 kS = F;
|
||||
|
||||
// Subtract the amount of reflected light (specular) to get the energy left for
|
||||
// absorbed (diffuse) light.
|
||||
vec3 kD = vec3(1.) - kS;
|
||||
|
||||
// Metallic surfaces have only a specular reflection.
|
||||
kD *= 1.0 - metallic;
|
||||
|
||||
// Premultiplied alpha applied to the diffuse component only
|
||||
kD *= alpha;
|
||||
|
||||
// Modulate the incoming lighting with the diffuse color: some wavelengths get absorbed.
|
||||
diffuse_ambient = irradiance * baseColor;
|
||||
|
||||
// Ambient light also has a specular part.
|
||||
specular_ambient = specular_ibl( V, normal, roughness, F );
|
||||
|
||||
// Ambient occlusion tells us the fraction of sky light that reaches this point.
|
||||
if (USE_SPECULAR_AO_ATTENUATION)
|
||||
{
|
||||
ambient = ao * (kD * diffuse_ambient + specular_ambient);
|
||||
}
|
||||
else
|
||||
{
|
||||
// We don't attenuate specular_ambient ambient here with AO which might cause flickering in dark cavities.
|
||||
ambient = ao * (kD * diffuse_ambient) + specular_ambient;
|
||||
}
|
||||
}
|
||||
|
||||
vec3 color = (ambient + Lo) + emissive;
|
||||
|
||||
if ( USE_AMBIENT_DEBUGGING )
|
||||
{
|
||||
float y = gl_FragCoord.y / resolution.y;
|
||||
if( USE_MAP_DEBUGGING && y > 0.5 )
|
||||
{
|
||||
if ( (y-0.5) < (7.0/7.0/2.0) ) color = vec3(.5) + .5*out_normal;
|
||||
if ( (y-0.5) < (6.0/7.0/2.0) ) color = vec3(.5) + .5*normalmap;
|
||||
if ( (y-0.5) < (5.0/7.0/2.0) ) color = vec3(ao);
|
||||
if ( (y-0.5) < (4.0/7.0/2.0) ) color = vec3(emissive);
|
||||
if ( (y-0.5) < (3.0/7.0/2.0) ) color = vec3(metallic);
|
||||
if ( (y-0.5) < (2.0/7.0/2.0) ) color = vec3(roughness);
|
||||
if ( (y-0.5) < (1.0/7.0/2.0) ) color = baseColor;
|
||||
} else {
|
||||
float x = gl_FragCoord.x / resolution.x;
|
||||
if ( x < 0.33 )
|
||||
color = specular_ambient;
|
||||
else if( x > 0.66 )
|
||||
color = diffuse_ambient;
|
||||
}
|
||||
}
|
||||
|
||||
#if 0 // original
|
||||
// basic tonemap and gamma correction
|
||||
color = color / ( vec3(1.) + color );
|
||||
color = pow( color, vec3(1. / 2.2) );
|
||||
#elif 0
|
||||
// filmic tonemapper
|
||||
vec3 linearColor = color;
|
||||
vec3 x = max(vec3(0.0), linearColor - 0.004);
|
||||
color = (x * (6.2 * x + 0.5)) / (x * (6.2 * x + 1.7) + 0.06);
|
||||
// gamma correction
|
||||
// color = pow( color, vec3(1. / 2.2) );
|
||||
#elif 1
|
||||
// aces film (CC0, src: https://knarkowicz.wordpress.com/2016/01/06/aces-filmic-tone-mapping-curve/)
|
||||
vec3 x = color;
|
||||
float a = 2.51f;
|
||||
float b = 0.03f;
|
||||
float c = 2.43f;
|
||||
float d = 0.59f;
|
||||
float e = 0.14f;
|
||||
color = clamp((x*(a*x+b))/(x*(c*x+d)+e), 0.0, 1.0);
|
||||
// gamma correction
|
||||
color = pow( color, vec3(1. / 2.2) );
|
||||
#endif
|
||||
|
||||
// dither with noise.
|
||||
// float dither = random( uvec3( floatBitsToUint( gl_FragCoord.xy ), frame_count ) );
|
||||
// color += BOOST_NOISE * vec3( (-1.0/256.) + (2./256.) * dither );
|
||||
|
||||
// Technically this alpha may be too transparent, if there is a lot of reflected light we wouldn't
|
||||
// see the background, maybe we can approximate it well enough by adding a fresnel term
|
||||
frag_color = vec4( color, alpha );
|
||||
}
|
||||
|
|
|
@ -1,180 +1,180 @@
|
|||
#ifndef MAX_BONES
|
||||
#define MAX_BONES 110
|
||||
#endif
|
||||
uniform mat3x4 vsBoneMatrix[MAX_BONES];
|
||||
uniform bool SKINNED = false;
|
||||
uniform mat4 M; // RIM
|
||||
uniform mat4 VP;
|
||||
uniform mat4 P;
|
||||
uniform int u_billboard;
|
||||
|
||||
#if 0
|
||||
// Fetch blend channels from all attached blend deformers.
|
||||
for (size_t di = 0; di < mesh->blend_deformers.count; di++) {
|
||||
ufbx_blend_deformer *deformer = mesh->blend_deformers.data[di];
|
||||
for (size_t ci = 0; ci < deformer->channels.count; ci++) {
|
||||
ufbx_blend_channel *chan = deformer->channels.data[ci];
|
||||
if (chan->keyframes.count == 0) continue;
|
||||
if (num_blend_shapes < MAX_BLEND_SHAPES) {
|
||||
blend_channels[num_blend_shapes] = chan;
|
||||
vmesh->blend_channel_indices[num_blend_shapes] = (int32_t)chan->typed_id;
|
||||
num_blend_shapes++;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (num_blend_shapes > 0) {
|
||||
vmesh->blend_shape_image = pack_blend_channels_to_image(mesh, blend_channels, num_blend_shapes);
|
||||
vmesh->num_blend_shapes = num_blend_shapes;
|
||||
}
|
||||
|
||||
|
||||
ubo.f_num_blend_shapes = (float)mesh->num_blend_shapes;
|
||||
for (size_t i = 0; i < mesh->num_blend_shapes; i++) {
|
||||
ubo.blend_weights[i] = view->scene.blend_channels[mesh->blend_channel_indices[i]].weight;
|
||||
}
|
||||
|
||||
|
||||
sg_image blend_shapes = mesh->num_blend_shapes > 0 ? mesh->blend_shape_image : view->empty_blend_shape_image;
|
||||
#endif
|
||||
|
||||
|
||||
// for blendshapes
|
||||
#ifndef MAX_BLENDSHAPES
|
||||
#define MAX_BLENDSHAPES 16
|
||||
#endif
|
||||
uniform vec4 blend_weights[MAX_BLENDSHAPES]; // @todo: implement me
|
||||
uniform float f_num_blend_shapes; // @todo: implement me
|
||||
uniform sampler2DArray blend_shapes; // @todo: implement me
|
||||
|
||||
|
||||
in vec3 att_position; // @todo: reorder ass2iqe to emit p3 n3 u2 t3 b3 c4B i4 w4 instead
|
||||
in vec2 att_texcoord;
|
||||
in vec3 att_normal;
|
||||
in vec4 att_tangent; // vec3 + bi sign
|
||||
in mat4 att_instanced_matrix; // for instanced rendering
|
||||
in vec4 att_indexes; // @fixme: gles might use ivec4 instead?
|
||||
in vec4 att_weights; // @todo: downgrade from float to byte
|
||||
in float att_vertexindex; // for blendshapes
|
||||
in vec4 att_color;
|
||||
in vec3 att_bitangent; // @todo: remove? also, ass2iqe might output this
|
||||
in vec2 att_texcoord2;
|
||||
out vec4 v_color;
|
||||
out vec3 v_position, v_position_ws;
|
||||
out vec3 v_normal, v_normal_ws;
|
||||
out vec2 v_texcoord, v_texcoord2;
|
||||
out vec3 v_tangent;
|
||||
out vec3 v_binormal;
|
||||
out vec3 v_viewpos;
|
||||
out vec3 v_to_camera;
|
||||
|
||||
|
||||
|
||||
|
||||
// shadow
|
||||
uniform mat4 model, view, inv_view;
|
||||
uniform mat4 cameraToShadowProjector;
|
||||
out vec4 vneye;
|
||||
out vec4 vpeye;
|
||||
out vec4 sc;
|
||||
void do_shadow() {
|
||||
vneye = view * model * vec4(att_normal, 0.0f);
|
||||
vpeye = view * model * vec4(att_position, 1.0);
|
||||
sc = cameraToShadowProjector * model * vec4(att_position, 1.0f);
|
||||
}
|
||||
|
||||
|
||||
// blendshapes
|
||||
vec3 evaluate_blend_shape(int vertex_index) {
|
||||
ivec2 coord = ivec2(vertex_index & (2048 - 1), vertex_index >> 11);
|
||||
int num_blend_shapes = int(f_num_blend_shapes);
|
||||
vec3 offset = vec3(0.0);
|
||||
for (int i = 0; i < num_blend_shapes; i++) {
|
||||
vec4 packedw = blend_weights[i >> 2];
|
||||
float weight = packedw[i & 3];
|
||||
offset += weight * texelFetch(blend_shapes, ivec3(coord, i), 0).xyz;
|
||||
}
|
||||
return offset;
|
||||
}
|
||||
|
||||
|
||||
void main() {
|
||||
vec3 objPos;
|
||||
if(!SKINNED) {
|
||||
objPos = att_position;
|
||||
v_normal = att_normal;
|
||||
} else {
|
||||
mat3x4 m = vsBoneMatrix[int(att_indexes.x)] * att_weights.x;
|
||||
m += vsBoneMatrix[int(att_indexes.y)] * att_weights.y;
|
||||
m += vsBoneMatrix[int(att_indexes.z)] * att_weights.z;
|
||||
m += vsBoneMatrix[int(att_indexes.w)] * att_weights.w;
|
||||
objPos = vec4(att_position, 1.0) * m;
|
||||
|
||||
// blendshapes
|
||||
// objPos += evaluate_blend_shape(int(att_vertexindex));
|
||||
|
||||
v_normal = vec4(att_normal, 0.0) * m;
|
||||
//@todo: tangents
|
||||
}
|
||||
|
||||
// vec3 tangent = att_tangent.xyz;
|
||||
// vec3 bitangent = cross(att_normal, att_tangent.xyz) * att_tangent.w;
|
||||
v_normal_ws = normalize(vec3(att_instanced_matrix * vec4(v_normal, 0.))); // normal to world/model space
|
||||
v_normal = normalize(v_normal);
|
||||
v_position = att_position;
|
||||
v_texcoord = att_texcoord;
|
||||
v_texcoord2 = att_texcoord2;
|
||||
v_color = att_color;
|
||||
mat4 modelView = view * att_instanced_matrix;
|
||||
mat4 l_model = att_instanced_matrix;
|
||||
v_position_ws = (l_model * vec4( objPos, 1.0 )).xyz;
|
||||
|
||||
if(u_billboard > 0) {
|
||||
vec3 cameraPosition = -transpose(mat3(view)) * view[3].xyz;
|
||||
vec3 lookDir = normalize(cameraPosition - v_position_ws);
|
||||
|
||||
vec3 up = vec3(modelView[0][1], modelView[1][1], modelView[2][1]);
|
||||
vec3 right = normalize(cross(up, lookDir));
|
||||
up = cross(lookDir, right);
|
||||
|
||||
vec3 scale;
|
||||
scale.x = length(vec3(l_model[0]));
|
||||
scale.y = length(vec3(l_model[1]));
|
||||
scale.z = length(vec3(l_model[2]));
|
||||
// scale.x *= sign(l_model[0][0]);
|
||||
// scale.y *= sign(l_model[1][1]);
|
||||
// scale.z *= sign(l_model[2][2]);
|
||||
|
||||
mat4 billboardRotation = mat4(
|
||||
vec4(right * scale.x, 0.0),
|
||||
vec4(-up * scale.y, 0.0),
|
||||
vec4(-lookDir * scale.z, 0.0),
|
||||
vec4(0.0, 0.0, 0.0, 1.0)
|
||||
);
|
||||
|
||||
if((u_billboard & 0x4) != 0) l_model[0] = billboardRotation[0];
|
||||
if((u_billboard & 0x2) != 0) l_model[1] = billboardRotation[1];
|
||||
if((u_billboard & 0x1) != 0) l_model[2] = billboardRotation[2];
|
||||
modelView = view * l_model;
|
||||
}
|
||||
v_position_ws = (l_model * vec4( objPos, 1.0 )).xyz;
|
||||
v_tangent = normalize(mat3(att_instanced_matrix) * att_tangent.xyz);
|
||||
#if 0
|
||||
// compute tangent T and bitangent B
|
||||
vec3 Q1 = dFdx(att_position);
|
||||
vec3 Q2 = dFdy(att_position);
|
||||
vec2 st1 = dFdx(att_texcoord);
|
||||
vec2 st2 = dFdy(att_texcoord);
|
||||
|
||||
vec3 T = normalize(Q1*st2.t - Q2*st1.t);
|
||||
vec3 B = normalize(-Q1*st2.s + Q2*st1.s);
|
||||
vec3 binormal = B;
|
||||
#else
|
||||
vec3 binormal = cross(att_normal, att_tangent.xyz) * att_tangent.w;
|
||||
#endif
|
||||
v_binormal = normalize(mat3(att_instanced_matrix) * binormal);
|
||||
vec4 finalPos = modelView * vec4( objPos, 1.0 );
|
||||
vec3 to_camera = normalize( -finalPos.xyz );
|
||||
v_to_camera = mat3( inv_view ) * to_camera;
|
||||
gl_Position = P * finalPos;
|
||||
do_shadow();
|
||||
#ifndef MAX_BONES
|
||||
#define MAX_BONES 110
|
||||
#endif
|
||||
uniform mat3x4 vsBoneMatrix[MAX_BONES];
|
||||
uniform bool SKINNED = false;
|
||||
uniform mat4 M; // RIM
|
||||
uniform mat4 VP;
|
||||
uniform mat4 P;
|
||||
uniform int u_billboard;
|
||||
|
||||
#if 0
|
||||
// Fetch blend channels from all attached blend deformers.
|
||||
for (size_t di = 0; di < mesh->blend_deformers.count; di++) {
|
||||
ufbx_blend_deformer *deformer = mesh->blend_deformers.data[di];
|
||||
for (size_t ci = 0; ci < deformer->channels.count; ci++) {
|
||||
ufbx_blend_channel *chan = deformer->channels.data[ci];
|
||||
if (chan->keyframes.count == 0) continue;
|
||||
if (num_blend_shapes < MAX_BLEND_SHAPES) {
|
||||
blend_channels[num_blend_shapes] = chan;
|
||||
vmesh->blend_channel_indices[num_blend_shapes] = (int32_t)chan->typed_id;
|
||||
num_blend_shapes++;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (num_blend_shapes > 0) {
|
||||
vmesh->blend_shape_image = pack_blend_channels_to_image(mesh, blend_channels, num_blend_shapes);
|
||||
vmesh->num_blend_shapes = num_blend_shapes;
|
||||
}
|
||||
|
||||
|
||||
ubo.f_num_blend_shapes = (float)mesh->num_blend_shapes;
|
||||
for (size_t i = 0; i < mesh->num_blend_shapes; i++) {
|
||||
ubo.blend_weights[i] = view->scene.blend_channels[mesh->blend_channel_indices[i]].weight;
|
||||
}
|
||||
|
||||
|
||||
sg_image blend_shapes = mesh->num_blend_shapes > 0 ? mesh->blend_shape_image : view->empty_blend_shape_image;
|
||||
#endif
|
||||
|
||||
|
||||
// for blendshapes
|
||||
#ifndef MAX_BLENDSHAPES
|
||||
#define MAX_BLENDSHAPES 16
|
||||
#endif
|
||||
uniform vec4 blend_weights[MAX_BLENDSHAPES]; // @todo: implement me
|
||||
uniform float f_num_blend_shapes; // @todo: implement me
|
||||
uniform sampler2DArray blend_shapes; // @todo: implement me
|
||||
|
||||
|
||||
in vec3 att_position; // @todo: reorder ass2iqe to emit p3 n3 u2 t3 b3 c4B i4 w4 instead
|
||||
in vec2 att_texcoord;
|
||||
in vec3 att_normal;
|
||||
in vec4 att_tangent; // vec3 + bi sign
|
||||
in mat4 att_instanced_matrix; // for instanced rendering
|
||||
in vec4 att_indexes; // @fixme: gles might use ivec4 instead?
|
||||
in vec4 att_weights; // @todo: downgrade from float to byte
|
||||
in float att_vertexindex; // for blendshapes
|
||||
in vec4 att_color;
|
||||
in vec3 att_bitangent; // @todo: remove? also, ass2iqe might output this
|
||||
in vec2 att_texcoord2;
|
||||
out vec4 v_color;
|
||||
out vec3 v_position, v_position_ws;
|
||||
out vec3 v_normal, v_normal_ws;
|
||||
out vec2 v_texcoord, v_texcoord2;
|
||||
out vec3 v_tangent;
|
||||
out vec3 v_binormal;
|
||||
out vec3 v_viewpos;
|
||||
out vec3 v_to_camera;
|
||||
|
||||
|
||||
|
||||
|
||||
// shadow
|
||||
uniform mat4 model, view, inv_view;
|
||||
uniform mat4 cameraToShadowProjector;
|
||||
out vec4 vneye;
|
||||
out vec4 vpeye;
|
||||
out vec4 sc;
|
||||
void do_shadow() {
|
||||
vneye = view * model * vec4(att_normal, 0.0f);
|
||||
vpeye = view * model * vec4(att_position, 1.0);
|
||||
sc = cameraToShadowProjector * model * vec4(att_position, 1.0f);
|
||||
}
|
||||
|
||||
|
||||
// blendshapes
|
||||
vec3 evaluate_blend_shape(int vertex_index) {
|
||||
ivec2 coord = ivec2(vertex_index & (2048 - 1), vertex_index >> 11);
|
||||
int num_blend_shapes = int(f_num_blend_shapes);
|
||||
vec3 offset = vec3(0.0);
|
||||
for (int i = 0; i < num_blend_shapes; i++) {
|
||||
vec4 packedw = blend_weights[i >> 2];
|
||||
float weight = packedw[i & 3];
|
||||
offset += weight * texelFetch(blend_shapes, ivec3(coord, i), 0).xyz;
|
||||
}
|
||||
return offset;
|
||||
}
|
||||
|
||||
|
||||
void main() {
|
||||
vec3 objPos;
|
||||
if(!SKINNED) {
|
||||
objPos = att_position;
|
||||
v_normal = att_normal;
|
||||
} else {
|
||||
mat3x4 m = vsBoneMatrix[int(att_indexes.x)] * att_weights.x;
|
||||
m += vsBoneMatrix[int(att_indexes.y)] * att_weights.y;
|
||||
m += vsBoneMatrix[int(att_indexes.z)] * att_weights.z;
|
||||
m += vsBoneMatrix[int(att_indexes.w)] * att_weights.w;
|
||||
objPos = vec4(att_position, 1.0) * m;
|
||||
|
||||
// blendshapes
|
||||
// objPos += evaluate_blend_shape(int(att_vertexindex));
|
||||
|
||||
v_normal = vec4(att_normal, 0.0) * m;
|
||||
//@todo: tangents
|
||||
}
|
||||
|
||||
// vec3 tangent = att_tangent.xyz;
|
||||
// vec3 bitangent = cross(att_normal, att_tangent.xyz) * att_tangent.w;
|
||||
v_normal_ws = normalize(vec3(att_instanced_matrix * vec4(v_normal, 0.))); // normal to world/model space
|
||||
v_normal = normalize(v_normal);
|
||||
v_position = att_position;
|
||||
v_texcoord = att_texcoord;
|
||||
v_texcoord2 = att_texcoord2;
|
||||
v_color = att_color;
|
||||
mat4 modelView = view * att_instanced_matrix;
|
||||
mat4 l_model = att_instanced_matrix;
|
||||
v_position_ws = (l_model * vec4( objPos, 1.0 )).xyz;
|
||||
|
||||
if(u_billboard > 0) {
|
||||
vec3 cameraPosition = -transpose(mat3(view)) * view[3].xyz;
|
||||
vec3 lookDir = normalize(cameraPosition - v_position_ws);
|
||||
|
||||
vec3 up = vec3(modelView[0][1], modelView[1][1], modelView[2][1]);
|
||||
vec3 right = normalize(cross(up, lookDir));
|
||||
up = cross(lookDir, right);
|
||||
|
||||
vec3 scale;
|
||||
scale.x = length(vec3(l_model[0]));
|
||||
scale.y = length(vec3(l_model[1]));
|
||||
scale.z = length(vec3(l_model[2]));
|
||||
// scale.x *= sign(l_model[0][0]);
|
||||
// scale.y *= sign(l_model[1][1]);
|
||||
// scale.z *= sign(l_model[2][2]);
|
||||
|
||||
mat4 billboardRotation = mat4(
|
||||
vec4(right * scale.x, 0.0),
|
||||
vec4(-up * scale.y, 0.0),
|
||||
vec4(-lookDir * scale.z, 0.0),
|
||||
vec4(0.0, 0.0, 0.0, 1.0)
|
||||
);
|
||||
|
||||
if((u_billboard & 0x4) != 0) l_model[0] = billboardRotation[0];
|
||||
if((u_billboard & 0x2) != 0) l_model[1] = billboardRotation[1];
|
||||
if((u_billboard & 0x1) != 0) l_model[2] = billboardRotation[2];
|
||||
modelView = view * l_model;
|
||||
}
|
||||
v_position_ws = (l_model * vec4( objPos, 1.0 )).xyz;
|
||||
v_tangent = normalize(mat3(att_instanced_matrix) * att_tangent.xyz);
|
||||
#if 0
|
||||
// compute tangent T and bitangent B
|
||||
vec3 Q1 = dFdx(att_position);
|
||||
vec3 Q2 = dFdy(att_position);
|
||||
vec2 st1 = dFdx(att_texcoord);
|
||||
vec2 st2 = dFdy(att_texcoord);
|
||||
|
||||
vec3 T = normalize(Q1*st2.t - Q2*st1.t);
|
||||
vec3 B = normalize(-Q1*st2.s + Q2*st1.s);
|
||||
vec3 binormal = B;
|
||||
#else
|
||||
vec3 binormal = cross(att_normal, att_tangent.xyz) * att_tangent.w;
|
||||
#endif
|
||||
v_binormal = normalize(mat3(att_instanced_matrix) * binormal);
|
||||
vec4 finalPos = modelView * vec4( objPos, 1.0 );
|
||||
vec3 to_camera = normalize( -finalPos.xyz );
|
||||
v_to_camera = mat3( inv_view ) * to_camera;
|
||||
gl_Position = P * finalPos;
|
||||
do_shadow();
|
||||
}
|
|
@ -1 +1 @@
|
|||
frame: 0-0 Idle
|
||||
frame: 0-0 Idle
|
||||
|
|
769810
engine/joint/v4k.h
769810
engine/joint/v4k.h
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -1,140 +1,140 @@
|
|||
// base64 de/encoder. Based on code by Jon Mayo - November 13, 2003 (PUBLIC DOMAIN).
|
||||
// - rlyeh, public domain
|
||||
|
||||
#ifndef BASE64_H
|
||||
#define BASE64_H
|
||||
|
||||
unsigned base64_bounds(unsigned size);
|
||||
char* base64_encode(const void *inp, unsigned inlen); // free() after use
|
||||
char* base64_decode(const char *inp, unsigned inlen); // array_free() after use
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef BASE64_C
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <stddef.h>
|
||||
#include <ctype.h>
|
||||
|
||||
#define BASE64_ENCODE_OUT_SIZE(s) ((unsigned int)((((s) + 2) / 3) * 4 + 1))
|
||||
#define BASE64_DECODE_OUT_SIZE(s) ((unsigned int)(((s) / 4) * 3))
|
||||
|
||||
unsigned base64_bounds(unsigned size) {
|
||||
return BASE64_ENCODE_OUT_SIZE(size);
|
||||
}
|
||||
|
||||
char* base64_encode(const void *inp, unsigned inlen) { // free() after use
|
||||
unsigned outlen = base64_bounds(inlen);
|
||||
char *out_ = MALLOC(outlen);
|
||||
out_[outlen] = 0;
|
||||
|
||||
uint_least32_t v;
|
||||
unsigned ii, io, rem;
|
||||
char *out = (char *)out_;
|
||||
const unsigned char *in = (const unsigned char *)inp;
|
||||
const uint8_t base64enc_tab[]= "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
||||
|
||||
for(io = 0, ii = 0, v = 0, rem = 0; ii < inlen; ii ++) {
|
||||
unsigned char ch;
|
||||
ch = in[ii];
|
||||
v = (v << 8) | ch;
|
||||
rem += 8;
|
||||
while (rem >= 6) {
|
||||
rem -= 6;
|
||||
if (io >= outlen)
|
||||
return (FREE(out_), NULL); /* truncation is failure */
|
||||
out[io ++] = base64enc_tab[(v >> rem) & 63];
|
||||
}
|
||||
}
|
||||
if (rem) {
|
||||
v <<= (6 - rem);
|
||||
if (io >= outlen)
|
||||
return (FREE(out_), NULL); /* truncation is failure */
|
||||
out[io ++] = base64enc_tab[v & 63];
|
||||
}
|
||||
while(io&3) {
|
||||
if(io>=outlen) return (FREE(out_), NULL); /* truncation is failure */
|
||||
out[io++]='=';
|
||||
}
|
||||
if(io>=outlen) return (FREE(out_), NULL); /* no room for null terminator */
|
||||
out[io]=0;
|
||||
return out_;
|
||||
}
|
||||
|
||||
#ifdef array_resize
|
||||
array(char) base64_decode(const char *inp, unsigned inlen) { // array_free() after use
|
||||
#if 0
|
||||
unsigned long outlen = BASE64_DECODE_OUT_SIZE(inlen);
|
||||
array(char) out_ = 0; array_resize(out_, outlen+1);
|
||||
|
||||
if( base64_decodex((const unsigned char *)inp, (unsigned long)inlen, (unsigned char *)out_, &outlen) != CRYPT_OK ) {
|
||||
array_free(out_);
|
||||
return 0;
|
||||
}
|
||||
|
||||
array_resize(out_, outlen);
|
||||
out_[outlen] = 0;
|
||||
return out_;
|
||||
#else
|
||||
unsigned outlen = BASE64_DECODE_OUT_SIZE(inlen);
|
||||
array(char) out_ = 0; array_resize(out_, outlen);
|
||||
|
||||
// based on code by Jon Mayo - November 13, 2003 (PUBLIC DOMAIN)
|
||||
uint_least32_t v;
|
||||
unsigned ii, io, rem;
|
||||
char *out = (char *)out_;
|
||||
const unsigned char *in = (const unsigned char *)inp;
|
||||
const uint8_t base64dec_tab[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, 255, 255, 255,
|
||||
255, 255, 255, 255, 255, 255, 255, 62, 255, 255, 255, 63,
|
||||
52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 255, 255,
|
||||
255, 254, 255, 255, 255, 0, 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, 255, 255, 255, 255, 255,
|
||||
255, 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, 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, 255, 255, 255 };
|
||||
|
||||
for (io = 0, ii = 0,v = 0, rem = 0; ii < inlen; ii ++) {
|
||||
unsigned char ch;
|
||||
if (isspace(in[ii]))
|
||||
continue;
|
||||
if ((in[ii]=='=') || (!in[ii]))
|
||||
break; /* stop at = or null character*/
|
||||
ch = base64dec_tab[(unsigned char)in[ii]];
|
||||
if (ch == 255)
|
||||
break; /* stop at a parse error */
|
||||
v = (v<<6) | ch;
|
||||
rem += 6;
|
||||
if (rem >= 8) {
|
||||
rem -= 8;
|
||||
if (io >= outlen)
|
||||
return (array_free(out_), NULL); /* truncation is failure */
|
||||
out[io ++] = (v >> rem) & 255;
|
||||
}
|
||||
}
|
||||
if (rem >= 8) {
|
||||
rem -= 8;
|
||||
if (io >= outlen)
|
||||
return (array_free(out_), NULL); /* truncation is failure */
|
||||
out[io ++] = (v >> rem) & 255;
|
||||
}
|
||||
return (array_resize(out_, io), out_);
|
||||
#endif
|
||||
}
|
||||
#endif // array_resize
|
||||
#endif // BASE64_C
|
||||
// base64 de/encoder. Based on code by Jon Mayo - November 13, 2003 (PUBLIC DOMAIN).
|
||||
// - rlyeh, public domain
|
||||
|
||||
#ifndef BASE64_H
|
||||
#define BASE64_H
|
||||
|
||||
unsigned base64_bounds(unsigned size);
|
||||
char* base64_encode(const void *inp, unsigned inlen); // free() after use
|
||||
char* base64_decode(const char *inp, unsigned inlen); // array_free() after use
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef BASE64_C
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <stddef.h>
|
||||
#include <ctype.h>
|
||||
|
||||
#define BASE64_ENCODE_OUT_SIZE(s) ((unsigned int)((((s) + 2) / 3) * 4 + 1))
|
||||
#define BASE64_DECODE_OUT_SIZE(s) ((unsigned int)(((s) / 4) * 3))
|
||||
|
||||
unsigned base64_bounds(unsigned size) {
|
||||
return BASE64_ENCODE_OUT_SIZE(size);
|
||||
}
|
||||
|
||||
char* base64_encode(const void *inp, unsigned inlen) { // free() after use
|
||||
unsigned outlen = base64_bounds(inlen);
|
||||
char *out_ = MALLOC(outlen);
|
||||
out_[outlen] = 0;
|
||||
|
||||
uint_least32_t v;
|
||||
unsigned ii, io, rem;
|
||||
char *out = (char *)out_;
|
||||
const unsigned char *in = (const unsigned char *)inp;
|
||||
const uint8_t base64enc_tab[]= "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
||||
|
||||
for(io = 0, ii = 0, v = 0, rem = 0; ii < inlen; ii ++) {
|
||||
unsigned char ch;
|
||||
ch = in[ii];
|
||||
v = (v << 8) | ch;
|
||||
rem += 8;
|
||||
while (rem >= 6) {
|
||||
rem -= 6;
|
||||
if (io >= outlen)
|
||||
return (FREE(out_), NULL); /* truncation is failure */
|
||||
out[io ++] = base64enc_tab[(v >> rem) & 63];
|
||||
}
|
||||
}
|
||||
if (rem) {
|
||||
v <<= (6 - rem);
|
||||
if (io >= outlen)
|
||||
return (FREE(out_), NULL); /* truncation is failure */
|
||||
out[io ++] = base64enc_tab[v & 63];
|
||||
}
|
||||
while(io&3) {
|
||||
if(io>=outlen) return (FREE(out_), NULL); /* truncation is failure */
|
||||
out[io++]='=';
|
||||
}
|
||||
if(io>=outlen) return (FREE(out_), NULL); /* no room for null terminator */
|
||||
out[io]=0;
|
||||
return out_;
|
||||
}
|
||||
|
||||
#ifdef array_resize
|
||||
array(char) base64_decode(const char *inp, unsigned inlen) { // array_free() after use
|
||||
#if 0
|
||||
unsigned long outlen = BASE64_DECODE_OUT_SIZE(inlen);
|
||||
array(char) out_ = 0; array_resize(out_, outlen+1);
|
||||
|
||||
if( base64_decodex((const unsigned char *)inp, (unsigned long)inlen, (unsigned char *)out_, &outlen) != CRYPT_OK ) {
|
||||
array_free(out_);
|
||||
return 0;
|
||||
}
|
||||
|
||||
array_resize(out_, outlen);
|
||||
out_[outlen] = 0;
|
||||
return out_;
|
||||
#else
|
||||
unsigned outlen = BASE64_DECODE_OUT_SIZE(inlen);
|
||||
array(char) out_ = 0; array_resize(out_, outlen);
|
||||
|
||||
// based on code by Jon Mayo - November 13, 2003 (PUBLIC DOMAIN)
|
||||
uint_least32_t v;
|
||||
unsigned ii, io, rem;
|
||||
char *out = (char *)out_;
|
||||
const unsigned char *in = (const unsigned char *)inp;
|
||||
const uint8_t base64dec_tab[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, 255, 255, 255,
|
||||
255, 255, 255, 255, 255, 255, 255, 62, 255, 255, 255, 63,
|
||||
52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 255, 255,
|
||||
255, 254, 255, 255, 255, 0, 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, 255, 255, 255, 255, 255,
|
||||
255, 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, 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, 255, 255, 255 };
|
||||
|
||||
for (io = 0, ii = 0,v = 0, rem = 0; ii < inlen; ii ++) {
|
||||
unsigned char ch;
|
||||
if (isspace(in[ii]))
|
||||
continue;
|
||||
if ((in[ii]=='=') || (!in[ii]))
|
||||
break; /* stop at = or null character*/
|
||||
ch = base64dec_tab[(unsigned char)in[ii]];
|
||||
if (ch == 255)
|
||||
break; /* stop at a parse error */
|
||||
v = (v<<6) | ch;
|
||||
rem += 6;
|
||||
if (rem >= 8) {
|
||||
rem -= 8;
|
||||
if (io >= outlen)
|
||||
return (array_free(out_), NULL); /* truncation is failure */
|
||||
out[io ++] = (v >> rem) & 255;
|
||||
}
|
||||
}
|
||||
if (rem >= 8) {
|
||||
rem -= 8;
|
||||
if (io >= outlen)
|
||||
return (array_free(out_), NULL); /* truncation is failure */
|
||||
out[io ++] = (v >> rem) & 255;
|
||||
}
|
||||
return (array_resize(out_, io), out_);
|
||||
#endif
|
||||
}
|
||||
#endif // array_resize
|
||||
#endif // BASE64_C
|
||||
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
12220
engine/split/3rd_enet.h
12220
engine/split/3rd_enet.h
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
23360
engine/split/3rd_glad.h
23360
engine/split/3rd_glad.h
File diff suppressed because it is too large
Load Diff
78380
engine/split/3rd_glfw3.h
78380
engine/split/3rd_glfw3.h
File diff suppressed because it is too large
Load Diff
93958
engine/split/3rd_https.h
93958
engine/split/3rd_https.h
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -1,407 +1,407 @@
|
|||
/* public domain Simple, Minimalistic MPEG Layer 1 decoder - http://jonolick.com
|
||||
*
|
||||
* Revision History:
|
||||
* 1.00 (2014-26-1) Initial release.
|
||||
*
|
||||
* Basic usage:
|
||||
* int hz, channels, outputSize;
|
||||
* short *output;
|
||||
* jo_read_mp1(input, inputSize, output, outputSize, hz, channels);
|
||||
* // Do something with the data here
|
||||
* free(output);
|
||||
*
|
||||
* */
|
||||
|
||||
#ifndef JO_INCLUDE_MP1_H
|
||||
#define JO_INCLUDE_MP1_H
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
extern bool jo_read_mp1(const void *input, int inputSize, short **output, int *outputSize, int *hz, int *channels);
|
||||
|
||||
#endif // JO_INCLUDE_MP1_H
|
||||
|
||||
#ifndef JO_MP1_HEADER_FILE_ONLY
|
||||
|
||||
#if defined(_MSC_VER) && _MSC_VER >= 0x1400
|
||||
#define _CRT_SECURE_NO_WARNINGS // suppress warnings about fopen()
|
||||
#endif
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <math.h>
|
||||
#include <limits.h>
|
||||
|
||||
static const double s_jo_multTbl[64] = {
|
||||
2.000000,1.587401,1.259921,1.000000,0.793701,0.629961,0.500000,0.396850,0.314980,0.250000,0.198425,0.157490,0.125000,0.099213,0.078745,0.062500,
|
||||
0.049606,0.039373,0.031250,0.024803,0.019686,0.015625,0.012402,0.009843,0.007812,0.006201,0.004922,0.003906,0.003100,0.002461,0.001953,0.001550,
|
||||
0.001230,0.000977,0.000775,0.000615,0.000488,0.000388,0.000308,0.000244,0.000194,0.000154,0.000122,0.000097,0.000077,0.000061,0.000048,0.000038,
|
||||
0.000031,0.000024,0.000019,0.000015,0.000012,0.000010,0.000008,0.000006,0.000005,0.000004,0.000003,0.000002,0.000002,0.000002,0.000001,1e-20
|
||||
};
|
||||
|
||||
// l = i - 256;
|
||||
// s = (i & 0x40) ? 1 : -1;
|
||||
// windowTbl[(i/16)|((i%16)<<5)] = s * 20 * exp(-(l/112)*-(l/112)) * sin(l * M_PI*2 / 112) / l;
|
||||
static const double s_jo_windowTbl[512] = {
|
||||
-0.000000,-0.000443,0.003250,-0.007004,0.031082,-0.078629,0.100311,-0.572037,1.144989,0.572037,0.100311,0.078629,0.031082,0.007004,0.003250,0.000443,
|
||||
-0.000015,-0.000473,0.003326,-0.007919,0.030518,-0.084183,0.090927,-0.600220,1.144287,0.543823,0.108856,0.073059,0.031479,0.006119,0.003174,0.000397,
|
||||
-0.000015,-0.000534,0.003387,-0.008865,0.029785,-0.089706,0.080688,-0.628296,1.142212,0.515610,0.116577,0.067520,0.031738,0.005295,0.003082,0.000366,
|
||||
-0.000015,-0.000580,0.003433,-0.009842,0.028885,-0.095169,0.069595,-0.656219,1.138763,0.487473,0.123474,0.061996,0.031845,0.004486,0.002991,0.000320,
|
||||
-0.000015,-0.000626,0.003464,-0.010849,0.027802,-0.100540,0.057617,-0.683914,1.133926,0.459473,0.129578,0.056534,0.031815,0.003723,0.002899,0.000290,
|
||||
-0.000015,-0.000687,0.003479,-0.011887,0.026535,-0.105820,0.044785,-0.711319,1.127747,0.431656,0.134888,0.051132,0.031662,0.003006,0.002792,0.000259,
|
||||
-0.000015,-0.000748,0.003479,-0.012939,0.025085,-0.110947,0.031082,-0.738373,1.120224,0.404083,0.139450,0.045837,0.031387,0.002335,0.002686,0.000244,
|
||||
-0.000031,-0.000809,0.003464,-0.014023,0.023422,-0.115921,0.016510,-0.765030,1.111374,0.376801,0.143265,0.040634,0.031006,0.001694,0.002579,0.000214,
|
||||
-0.000031,-0.000885,0.003418,-0.015121,0.021576,-0.120697,0.001068,-0.791214,1.101212,0.349869,0.146362,0.035553,0.030533,0.001099,0.002457,0.000198,
|
||||
-0.000031,-0.000961,0.003372,-0.016235,0.019531,-0.125259,-0.015228,-0.816864,1.089783,0.323318,0.148773,0.030609,0.029938,0.000549,0.002350,0.000168,
|
||||
-0.000031,-0.001038,0.003281,-0.017349,0.017258,-0.129562,-0.032379,-0.841949,1.077118,0.297211,0.150497,0.025818,0.029282,0.000031,0.002243,0.000153,
|
||||
-0.000046,-0.001114,0.003174,-0.018463,0.014801,-0.133591,-0.050354,-0.866364,1.063217,0.271591,0.151596,0.021179,0.028534,-0.000443,0.002121,0.000137,
|
||||
-0.000046,-0.001205,0.003052,-0.019577,0.012115,-0.137299,-0.069168,-0.890091,1.048157,0.246506,0.152069,0.016708,0.027725,-0.000870,0.002014,0.000122,
|
||||
-0.000061,-0.001297,0.002884,-0.020691,0.009232,-0.140671,-0.088776,-0.913055,1.031937,0.221985,0.151962,0.012421,0.026840,-0.001266,0.001907,0.000107,
|
||||
-0.000061,-0.001389,0.002701,-0.021790,0.006134,-0.143677,-0.109161,-0.935196,1.014618,0.198059,0.151306,0.008316,0.025909,-0.001617,0.001785,0.000107,
|
||||
-0.000076,-0.001480,0.002487,-0.022858,0.002823,-0.146255,-0.130310,-0.956482,0.996246,0.174789,0.150116,0.004395,0.024933,-0.001938,0.001694,0.000092,
|
||||
-0.000076,-0.001587,0.002228,-0.023911,-0.000687,-0.148422,-0.152206,-0.976852,0.976852,0.152206,0.148422,0.000687,0.023911,-0.002228,0.001587,0.000076,
|
||||
-0.000092,-0.001694,0.001938,-0.024933,-0.004395,-0.150116,-0.174789,-0.996246,0.956482,0.130310,0.146255,-0.002823,0.022858,-0.002487,0.001480,0.000076,
|
||||
-0.000107,-0.001785,0.001617,-0.025909,-0.008316,-0.151306,-0.198059,-1.014618,0.935196,0.109161,0.143677,-0.006134,0.021790,-0.002701,0.001389,0.000061,
|
||||
-0.000107,-0.001907,0.001266,-0.026840,-0.012421,-0.151962,-0.221985,-1.031937,0.913055,0.088776,0.140671,-0.009232,0.020691,-0.002884,0.001297,0.000061,
|
||||
-0.000122,-0.002014,0.000870,-0.027725,-0.016708,-0.152069,-0.246506,-1.048157,0.890091,0.069168,0.137299,-0.012115,0.019577,-0.003052,0.001205,0.000046,
|
||||
-0.000137,-0.002121,0.000443,-0.028534,-0.021179,-0.151596,-0.271591,-1.063217,0.866364,0.050354,0.133591,-0.014801,0.018463,-0.003174,0.001114,0.000046,
|
||||
-0.000153,-0.002243,-0.000031,-0.029282,-0.025818,-0.150497,-0.297211,-1.077118,0.841949,0.032379,0.129562,-0.017258,0.017349,-0.003281,0.001038,0.000031,
|
||||
-0.000168,-0.002350,-0.000549,-0.029938,-0.030609,-0.148773,-0.323318,-1.089783,0.816864,0.015228,0.125259,-0.019531,0.016235,-0.003372,0.000961,0.000031,
|
||||
-0.000198,-0.002457,-0.001099,-0.030533,-0.035553,-0.146362,-0.349869,-1.101212,0.791214,-0.001068,0.120697,-0.021576,0.015121,-0.003418,0.000885,0.000031,
|
||||
-0.000214,-0.002579,-0.001694,-0.031006,-0.040634,-0.143265,-0.376801,-1.111374,0.765030,-0.016510,0.115921,-0.023422,0.014023,-0.003464,0.000809,0.000031,
|
||||
-0.000244,-0.002686,-0.002335,-0.031387,-0.045837,-0.139450,-0.404083,-1.120224,0.738373,-0.031082,0.110947,-0.025085,0.012939,-0.003479,0.000748,0.000015,
|
||||
-0.000259,-0.002792,-0.003006,-0.031662,-0.051132,-0.134888,-0.431656,-1.127747,0.711319,-0.044785,0.105820,-0.026535,0.011887,-0.003479,0.000687,0.000015,
|
||||
-0.000290,-0.002899,-0.003723,-0.031815,-0.056534,-0.129578,-0.459473,-1.133926,0.683914,-0.057617,0.100540,-0.027802,0.010849,-0.003464,0.000626,0.000015,
|
||||
-0.000320,-0.002991,-0.004486,-0.031845,-0.061996,-0.123474,-0.487473,-1.138763,0.656219,-0.069595,0.095169,-0.028885,0.009842,-0.003433,0.000580,0.000015,
|
||||
-0.000366,-0.003082,-0.005295,-0.031738,-0.067520,-0.116577,-0.515610,-1.142212,0.628296,-0.080688,0.089706,-0.029785,0.008865,-0.003387,0.000534,0.000015,
|
||||
-0.000397,-0.003174,-0.006119,-0.031479,-0.073059,-0.108856,-0.543823,-1.144287,0.600220,-0.090927,0.084183,-0.030518,0.007919,-0.003326,0.000473,0.000015,
|
||||
};
|
||||
|
||||
// filterTbl[i][j] = cos(((M_PI/64*i+M_PI/4)*(2*j+1)));
|
||||
static const double s_jo_filterTbl[64][32] = {
|
||||
{0.707107,-0.707107,-0.707107,0.707107,0.707107,-0.707107,-0.707107,0.707107,0.707107,-0.707107,-0.707107,0.707107,0.707107,-0.707107,-0.707107,0.707107,
|
||||
0.707107,-0.707107,-0.707107,0.707107,0.707107,-0.707107,-0.707107,0.707107,0.707107,-0.707107,-0.707107,0.707107,0.707107,-0.707107,-0.707107,0.707107},
|
||||
{0.671559,-0.803208,-0.514103,0.903989,0.336890,-0.970031,-0.146730,0.998795,-0.049068,-0.989177,0.242980,0.941544,-0.427555,-0.857729,0.595699,0.740951,
|
||||
-0.740951,-0.595699,0.857729,0.427555,-0.941544,-0.242980,0.989177,0.049068,-0.998795,0.146730,0.970031,-0.336890,-0.903989,0.514103,0.803208,-0.671559},
|
||||
{0.634393,-0.881921,-0.290285,0.995185,-0.098017,-0.956940,0.471397,0.773010,-0.773010,-0.471397,0.956940,0.098017,-0.995185,0.290285,0.881921,-0.634393,
|
||||
-0.634393,0.881921,0.290285,-0.995185,0.098017,0.956940,-0.471397,-0.773010,0.773010,0.471397,-0.956940,-0.098017,0.995185,-0.290285,-0.881921,0.634393},
|
||||
{0.595699,-0.941544,-0.049068,0.970031,-0.514103,-0.671559,0.903989,0.146730,-0.989177,0.427555,0.740951,-0.857729,-0.242980,0.998795,-0.336890,-0.803208,
|
||||
0.803208,0.336890,-0.998795,0.242980,0.857729,-0.740951,-0.427555,0.989177,-0.146730,-0.903989,0.671559,0.514103,-0.970031,0.049068,0.941544,-0.595699},
|
||||
{0.555570,-0.980785,0.195090,0.831470,-0.831470,-0.195090,0.980785,-0.555570,-0.555570,0.980785,-0.195090,-0.831470,0.831470,0.195090,-0.980785,0.555570,
|
||||
0.555570,-0.980785,0.195090,0.831470,-0.831470,-0.195090,0.980785,-0.555570,-0.555570,0.980785,-0.195090,-0.831470,0.831470,0.195090,-0.980785,0.555570},
|
||||
{0.514103,-0.998795,0.427555,0.595699,-0.989177,0.336890,0.671559,-0.970031,0.242980,0.740951,-0.941544,0.146730,0.803208,-0.903989,0.049068,0.857729,
|
||||
-0.857729,-0.049068,0.903989,-0.803208,-0.146730,0.941544,-0.740951,-0.242980,0.970031,-0.671559,-0.336890,0.989177,-0.595699,-0.427555,0.998795,-0.514103},
|
||||
{0.471397,-0.995185,0.634393,0.290285,-0.956940,0.773010,0.098017,-0.881921,0.881921,-0.098017,-0.773010,0.956940,-0.290285,-0.634393,0.995185,-0.471397,
|
||||
-0.471397,0.995185,-0.634393,-0.290285,0.956940,-0.773010,-0.098017,0.881921,-0.881921,0.098017,0.773010,-0.956940,0.290285,0.634393,-0.995185,0.471397},
|
||||
{0.427555,-0.970031,0.803208,-0.049068,-0.740951,0.989177,-0.514103,-0.336890,0.941544,-0.857729,0.146730,0.671559,-0.998795,0.595699,0.242980,-0.903989,
|
||||
0.903989,-0.242980,-0.595699,0.998795,-0.671559,-0.146730,0.857729,-0.941544,0.336890,0.514103,-0.989177,0.740951,0.049068,-0.803208,0.970031,-0.427555},
|
||||
{0.382683,-0.923880,0.923880,-0.382683,-0.382683,0.923880,-0.923880,0.382683,0.382683,-0.923880,0.923880,-0.382683,-0.382683,0.923880,-0.923880,0.382683,
|
||||
0.382683,-0.923880,0.923880,-0.382683,-0.382683,0.923880,-0.923880,0.382683,0.382683,-0.923880,0.923880,-0.382683,-0.382683,0.923880,-0.923880,0.382683},
|
||||
{0.336890,-0.857729,0.989177,-0.671559,0.049068,0.595699,-0.970031,0.903989,-0.427555,-0.242980,0.803208,-0.998795,0.740951,-0.146730,-0.514103,0.941544,
|
||||
-0.941544,0.514103,0.146730,-0.740951,0.998795,-0.803208,0.242980,0.427555,-0.903989,0.970031,-0.595699,-0.049068,0.671559,-0.989177,0.857729,-0.336890},
|
||||
{0.290285,-0.773010,0.995185,-0.881921,0.471397,0.098017,-0.634393,0.956940,-0.956940,0.634393,-0.098017,-0.471397,0.881921,-0.995185,0.773010,-0.290285,
|
||||
-0.290285,0.773010,-0.995185,0.881921,-0.471397,-0.098017,0.634393,-0.956940,0.956940,-0.634393,0.098017,0.471397,-0.881921,0.995185,-0.773010,0.290285},
|
||||
{0.242980,-0.671559,0.941544,-0.989177,0.803208,-0.427555,-0.049068,0.514103,-0.857729,0.998795,-0.903989,0.595699,-0.146730,-0.336890,0.740951,-0.970031,
|
||||
0.970031,-0.740951,0.336890,0.146730,-0.595699,0.903989,-0.998795,0.857729,-0.514103,0.049068,0.427555,-0.803208,0.989177,-0.941544,0.671559,-0.242980},
|
||||
{0.195090,-0.555570,0.831470,-0.980785,0.980785,-0.831470,0.555570,-0.195090,-0.195090,0.555570,-0.831470,0.980785,-0.980785,0.831470,-0.555570,0.195090,
|
||||
0.195090,-0.555570,0.831470,-0.980785,0.980785,-0.831470,0.555570,-0.195090,-0.195090,0.555570,-0.831470,0.980785,-0.980785,0.831470,-0.555570,0.195090},
|
||||
{0.146730,-0.427555,0.671559,-0.857729,0.970031,-0.998795,0.941544,-0.803208,0.595699,-0.336890,0.049068,0.242980,-0.514103,0.740951,-0.903989,0.989177,
|
||||
-0.989177,0.903989,-0.740951,0.514103,-0.242980,-0.049068,0.336890,-0.595699,0.803208,-0.941544,0.998795,-0.970031,0.857729,-0.671559,0.427555,-0.146730},
|
||||
{0.098017,-0.290285,0.471397,-0.634393,0.773010,-0.881921,0.956940,-0.995185,0.995185,-0.956940,0.881921,-0.773010,0.634393,-0.471397,0.290285,-0.098017,
|
||||
-0.098017,0.290285,-0.471397,0.634393,-0.773010,0.881921,-0.956940,0.995185,-0.995185,0.956940,-0.881921,0.773010,-0.634393,0.471397,-0.290285,0.098017},
|
||||
{0.049068,-0.146730,0.242980,-0.336890,0.427555,-0.514103,0.595699,-0.671559,0.740951,-0.803208,0.857729,-0.903989,0.941544,-0.970031,0.989177,-0.998795,
|
||||
0.998795,-0.989177,0.970031,-0.941544,0.903989,-0.857729,0.803208,-0.740951,0.671559,-0.595699,0.514103,-0.427555,0.336890,-0.242980,0.146730,-0.049068},
|
||||
{0.000000,-0.000000,0.000000,-0.000000,0.000000,-0.000000,-0.000000,-0.000000,-0.000000,-0.000000,-0.000000,-0.000000,-0.000000,-0.000000,-0.000000,-0.000000,
|
||||
0.000000,-0.000000,0.000000,-0.000000,0.000000,-0.000000,0.000000,0.000000,0.000000,-0.000000,0.000000,0.000000,0.000000,-0.000000,0.000000,0.000000},
|
||||
{-0.049068,0.146730,-0.242980,0.336890,-0.427555,0.514103,-0.595699,0.671559,-0.740951,0.803208,-0.857729,0.903989,-0.941544,0.970031,-0.989177,0.998795,
|
||||
-0.998795,0.989177,-0.970031,0.941544,-0.903989,0.857729,-0.803208,0.740951,-0.671559,0.595699,-0.514103,0.427555,-0.336890,0.242980,-0.146730,0.049068},
|
||||
{-0.098017,0.290285,-0.471397,0.634393,-0.773010,0.881921,-0.956940,0.995185,-0.995185,0.956940,-0.881921,0.773010,-0.634393,0.471397,-0.290285,0.098017,
|
||||
0.098017,-0.290285,0.471397,-0.634393,0.773010,-0.881921,0.956940,-0.995185,0.995185,-0.956940,0.881921,-0.773010,0.634393,-0.471397,0.290285,-0.098017},
|
||||
{-0.146730,0.427555,-0.671559,0.857729,-0.970031,0.998795,-0.941544,0.803208,-0.595699,0.336890,-0.049068,-0.242980,0.514103,-0.740951,0.903989,-0.989177,
|
||||
0.989177,-0.903989,0.740951,-0.514103,0.242980,0.049068,-0.336890,0.595699,-0.803208,0.941544,-0.998795,0.970031,-0.857729,0.671559,-0.427555,0.146730},
|
||||
{-0.195090,0.555570,-0.831470,0.980785,-0.980785,0.831470,-0.555570,0.195090,0.195090,-0.555570,0.831470,-0.980785,0.980785,-0.831470,0.555570,-0.195090,
|
||||
-0.195090,0.555570,-0.831470,0.980785,-0.980785,0.831470,-0.555570,0.195090,0.195090,-0.555570,0.831470,-0.980785,0.980785,-0.831470,0.555570,-0.195090},
|
||||
{-0.242980,0.671559,-0.941544,0.989177,-0.803208,0.427555,0.049068,-0.514103,0.857729,-0.998795,0.903989,-0.595699,0.146730,0.336890,-0.740951,0.970031,
|
||||
-0.970031,0.740951,-0.336890,-0.146730,0.595699,-0.903989,0.998795,-0.857729,0.514103,-0.049068,-0.427555,0.803208,-0.989177,0.941544,-0.671559,0.242980},
|
||||
{-0.290285,0.773010,-0.995185,0.881921,-0.471397,-0.098017,0.634393,-0.956940,0.956940,-0.634393,0.098017,0.471397,-0.881921,0.995185,-0.773010,0.290285,
|
||||
0.290285,-0.773010,0.995185,-0.881921,0.471397,0.098017,-0.634393,0.956940,-0.956940,0.634393,-0.098017,-0.471397,0.881921,-0.995185,0.773010,-0.290285},
|
||||
{-0.336890,0.857729,-0.989177,0.671559,-0.049068,-0.595699,0.970031,-0.903989,0.427555,0.242980,-0.803208,0.998795,-0.740951,0.146730,0.514103,-0.941544,
|
||||
0.941544,-0.514103,-0.146730,0.740951,-0.998795,0.803208,-0.242980,-0.427555,0.903989,-0.970031,0.595699,0.049068,-0.671559,0.989177,-0.857729,0.336890},
|
||||
{-0.382683,0.923880,-0.923880,0.382683,0.382683,-0.923880,0.923880,-0.382683,-0.382683,0.923880,-0.923880,0.382683,0.382683,-0.923880,0.923880,-0.382683,
|
||||
-0.382683,0.923880,-0.923880,0.382683,0.382683,-0.923880,0.923880,-0.382683,-0.382683,0.923880,-0.923880,0.382683,0.382683,-0.923880,0.923880,-0.382683},
|
||||
{-0.427555,0.970031,-0.803208,0.049068,0.740951,-0.989177,0.514103,0.336890,-0.941544,0.857729,-0.146730,-0.671559,0.998795,-0.595699,-0.242980,0.903989,
|
||||
-0.903989,0.242980,0.595699,-0.998795,0.671559,0.146730,-0.857729,0.941544,-0.336890,-0.514103,0.989177,-0.740951,-0.049068,0.803208,-0.970031,0.427555},
|
||||
{-0.471397,0.995185,-0.634393,-0.290285,0.956940,-0.773010,-0.098017,0.881921,-0.881921,0.098017,0.773010,-0.956940,0.290285,0.634393,-0.995185,0.471397,
|
||||
0.471397,-0.995185,0.634393,0.290285,-0.956940,0.773010,0.098017,-0.881921,0.881921,-0.098017,-0.773010,0.956940,-0.290285,-0.634393,0.995185,-0.471397},
|
||||
{-0.514103,0.998795,-0.427555,-0.595699,0.989177,-0.336890,-0.671559,0.970031,-0.242980,-0.740951,0.941544,-0.146730,-0.803208,0.903989,-0.049068,-0.857729,
|
||||
0.857729,0.049068,-0.903989,0.803208,0.146730,-0.941544,0.740951,0.242980,-0.970031,0.671559,0.336890,-0.989177,0.595699,0.427555,-0.998795,0.514103},
|
||||
{-0.555570,0.980785,-0.195090,-0.831470,0.831470,0.195090,-0.980785,0.555570,0.555570,-0.980785,0.195090,0.831470,-0.831470,-0.195090,0.980785,-0.555570,
|
||||
-0.555570,0.980785,-0.195090,-0.831470,0.831470,0.195090,-0.980785,0.555570,0.555570,-0.980785,0.195090,0.831470,-0.831470,-0.195090,0.980785,-0.555570},
|
||||
{-0.595699,0.941544,0.049068,-0.970031,0.514103,0.671559,-0.903989,-0.146730,0.989177,-0.427555,-0.740951,0.857729,0.242980,-0.998795,0.336890,0.803208,
|
||||
-0.803208,-0.336890,0.998795,-0.242980,-0.857729,0.740951,0.427555,-0.989177,0.146730,0.903989,-0.671559,-0.514103,0.970031,-0.049068,-0.941544,0.595699},
|
||||
{-0.634393,0.881921,0.290285,-0.995185,0.098017,0.956940,-0.471397,-0.773010,0.773010,0.471397,-0.956940,-0.098017,0.995185,-0.290285,-0.881921,0.634393,
|
||||
0.634393,-0.881921,-0.290285,0.995185,-0.098017,-0.956940,0.471397,0.773010,-0.773010,-0.471397,0.956940,0.098017,-0.995185,0.290285,0.881921,-0.634393},
|
||||
{-0.671559,0.803208,0.514103,-0.903989,-0.336890,0.970031,0.146730,-0.998795,0.049068,0.989177,-0.242980,-0.941544,0.427555,0.857729,-0.595699,-0.740951,
|
||||
0.740951,0.595699,-0.857729,-0.427555,0.941544,0.242980,-0.989177,-0.049068,0.998795,-0.146730,-0.970031,0.336890,0.903989,-0.514103,-0.803208,0.671559},
|
||||
{-0.707107,0.707107,0.707107,-0.707107,-0.707107,0.707107,0.707107,-0.707107,-0.707107,0.707107,0.707107,-0.707107,-0.707107,0.707107,0.707107,-0.707107,
|
||||
-0.707107,0.707107,0.707107,-0.707107,-0.707107,0.707107,0.707107,-0.707107,-0.707107,0.707107,0.707107,-0.707107,-0.707107,0.707107,0.707107,-0.707107},
|
||||
{-0.740951,0.595699,0.857729,-0.427555,-0.941544,0.242980,0.989177,-0.049068,-0.998795,-0.146730,0.970031,0.336890,-0.903989,-0.514103,0.803208,0.671559,
|
||||
-0.671559,-0.803208,0.514103,0.903989,-0.336890,-0.970031,0.146730,0.998795,0.049068,-0.989177,-0.242980,0.941544,0.427555,-0.857729,-0.595699,0.740951},
|
||||
{-0.773010,0.471397,0.956940,-0.098017,-0.995185,-0.290285,0.881921,0.634393,-0.634393,-0.881921,0.290285,0.995185,0.098017,-0.956940,-0.471397,0.773010,
|
||||
0.773010,-0.471397,-0.956940,0.098017,0.995185,0.290285,-0.881921,-0.634393,0.634393,0.881921,-0.290285,-0.995185,-0.098017,0.956940,0.471397,-0.773010},
|
||||
{-0.803208,0.336890,0.998795,0.242980,-0.857729,-0.740951,0.427555,0.989177,0.146730,-0.903989,-0.671559,0.514103,0.970031,0.049068,-0.941544,-0.595699,
|
||||
0.595699,0.941544,-0.049068,-0.970031,-0.514103,0.671559,0.903989,-0.146730,-0.989177,-0.427555,0.740951,0.857729,-0.242980,-0.998795,-0.336890,0.803208},
|
||||
{-0.831470,0.195090,0.980785,0.555570,-0.555570,-0.980785,-0.195090,0.831470,0.831470,-0.195090,-0.980785,-0.555570,0.555570,0.980785,0.195090,-0.831470,
|
||||
-0.831470,0.195090,0.980785,0.555570,-0.555570,-0.980785,-0.195090,0.831470,0.831470,-0.195090,-0.980785,-0.555570,0.555570,0.980785,0.195090,-0.831470},
|
||||
{-0.857729,0.049068,0.903989,0.803208,-0.146730,-0.941544,-0.740951,0.242980,0.970031,0.671559,-0.336890,-0.989177,-0.595699,0.427555,0.998795,0.514103,
|
||||
-0.514103,-0.998795,-0.427555,0.595699,0.989177,0.336890,-0.671559,-0.970031,-0.242980,0.740951,0.941544,0.146730,-0.803208,-0.903989,-0.049068,0.857729},
|
||||
{-0.881921,-0.098017,0.773010,0.956940,0.290285,-0.634393,-0.995185,-0.471397,0.471397,0.995185,0.634393,-0.290285,-0.956940,-0.773010,0.098017,0.881921,
|
||||
0.881921,0.098017,-0.773010,-0.956940,-0.290285,0.634393,0.995185,0.471397,-0.471397,-0.995185,-0.634393,0.290285,0.956940,0.773010,-0.098017,-0.881921},
|
||||
{-0.903989,-0.242980,0.595699,0.998795,0.671559,-0.146730,-0.857729,-0.941544,-0.336890,0.514103,0.989177,0.740951,-0.049068,-0.803208,-0.970031,-0.427555,
|
||||
0.427555,0.970031,0.803208,0.049068,-0.740951,-0.989177,-0.514103,0.336890,0.941544,0.857729,0.146730,-0.671559,-0.998795,-0.595699,0.242980,0.903989},
|
||||
{-0.923880,-0.382683,0.382683,0.923880,0.923880,0.382683,-0.382683,-0.923880,-0.923880,-0.382683,0.382683,0.923880,0.923880,0.382683,-0.382683,-0.923880,
|
||||
-0.923880,-0.382683,0.382683,0.923880,0.923880,0.382683,-0.382683,-0.923880,-0.923880,-0.382683,0.382683,0.923880,0.923880,0.382683,-0.382683,-0.923880},
|
||||
{-0.941544,-0.514103,0.146730,0.740951,0.998795,0.803208,0.242980,-0.427555,-0.903989,-0.970031,-0.595699,0.049068,0.671559,0.989177,0.857729,0.336890,
|
||||
-0.336890,-0.857729,-0.989177,-0.671559,-0.049068,0.595699,0.970031,0.903989,0.427555,-0.242980,-0.803208,-0.998795,-0.740951,-0.146730,0.514103,0.941544},
|
||||
{-0.956940,-0.634393,-0.098017,0.471397,0.881921,0.995185,0.773010,0.290285,-0.290285,-0.773010,-0.995185,-0.881921,-0.471397,0.098017,0.634393,0.956940,
|
||||
0.956940,0.634393,0.098017,-0.471397,-0.881921,-0.995185,-0.773010,-0.290285,0.290285,0.773010,0.995185,0.881921,0.471397,-0.098017,-0.634393,-0.956940},
|
||||
{-0.970031,-0.740951,-0.336890,0.146730,0.595699,0.903989,0.998795,0.857729,0.514103,0.049068,-0.427555,-0.803208,-0.989177,-0.941544,-0.671559,-0.242980,
|
||||
0.242980,0.671559,0.941544,0.989177,0.803208,0.427555,-0.049068,-0.514103,-0.857729,-0.998795,-0.903989,-0.595699,-0.146730,0.336890,0.740951,0.970031},
|
||||
{-0.980785,-0.831470,-0.555570,-0.195090,0.195090,0.555570,0.831470,0.980785,0.980785,0.831470,0.555570,0.195090,-0.195090,-0.555570,-0.831470,-0.980785,
|
||||
-0.980785,-0.831470,-0.555570,-0.195090,0.195090,0.555570,0.831470,0.980785,0.980785,0.831470,0.555570,0.195090,-0.195090,-0.555570,-0.831470,-0.980785},
|
||||
{-0.989177,-0.903989,-0.740951,-0.514103,-0.242980,0.049068,0.336890,0.595699,0.803208,0.941544,0.998795,0.970031,0.857729,0.671559,0.427555,0.146730,
|
||||
-0.146730,-0.427555,-0.671559,-0.857729,-0.970031,-0.998795,-0.941544,-0.803208,-0.595699,-0.336890,-0.049068,0.242980,0.514103,0.740951,0.903989,0.989177},
|
||||
{-0.995185,-0.956940,-0.881921,-0.773010,-0.634393,-0.471397,-0.290285,-0.098017,0.098017,0.290285,0.471397,0.634393,0.773010,0.881921,0.956940,0.995185,
|
||||
0.995185,0.956940,0.881921,0.773010,0.634393,0.471397,0.290285,0.098017,-0.098017,-0.290285,-0.471397,-0.634393,-0.773010,-0.881921,-0.956940,-0.995185},
|
||||
{-0.998795,-0.989177,-0.970031,-0.941544,-0.903989,-0.857729,-0.803208,-0.740951,-0.671559,-0.595699,-0.514103,-0.427555,-0.336890,-0.242980,-0.146730,-0.049068,
|
||||
0.049068,0.146730,0.242980,0.336890,0.427555,0.514103,0.595699,0.671559,0.740951,0.803208,0.857729,0.903989,0.941544,0.970031,0.989177,0.998795},
|
||||
{-1.000000,-1.000000,-1.000000,-1.000000,-1.000000,-1.000000,-1.000000,-1.000000,-1.000000,-1.000000,-1.000000,-1.000000,-1.000000,-1.000000,-1.000000,-1.000000,
|
||||
-1.000000,-1.000000,-1.000000,-1.000000,-1.000000,-1.000000,-1.000000,-1.000000,-1.000000,-1.000000,-1.000000,-1.000000,-1.000000,-1.000000,-1.000000,-1.000000},
|
||||
{-0.998795,-0.989177,-0.970031,-0.941544,-0.903989,-0.857729,-0.803208,-0.740951,-0.671559,-0.595699,-0.514103,-0.427555,-0.336890,-0.242980,-0.146730,-0.049068,
|
||||
0.049068,0.146730,0.242980,0.336890,0.427555,0.514103,0.595699,0.671559,0.740951,0.803208,0.857729,0.903989,0.941544,0.970031,0.989177,0.998795},
|
||||
{-0.995185,-0.956940,-0.881921,-0.773010,-0.634393,-0.471397,-0.290285,-0.098017,0.098017,0.290285,0.471397,0.634393,0.773010,0.881921,0.956940,0.995185,
|
||||
0.995185,0.956940,0.881921,0.773010,0.634393,0.471397,0.290285,0.098017,-0.098017,-0.290285,-0.471397,-0.634393,-0.773010,-0.881921,-0.956940,-0.995185},
|
||||
{-0.989177,-0.903989,-0.740951,-0.514103,-0.242980,0.049068,0.336890,0.595699,0.803208,0.941544,0.998795,0.970031,0.857729,0.671559,0.427555,0.146730,
|
||||
-0.146730,-0.427555,-0.671559,-0.857729,-0.970031,-0.998795,-0.941544,-0.803208,-0.595699,-0.336890,-0.049068,0.242980,0.514103,0.740951,0.903989,0.989177},
|
||||
{-0.980785,-0.831470,-0.555570,-0.195090,0.195090,0.555570,0.831470,0.980785,0.980785,0.831470,0.555570,0.195090,-0.195090,-0.555570,-0.831470,-0.980785,
|
||||
-0.980785,-0.831470,-0.555570,-0.195090,0.195090,0.555570,0.831470,0.980785,0.980785,0.831470,0.555570,0.195090,-0.195090,-0.555570,-0.831470,-0.980785},
|
||||
{-0.970031,-0.740951,-0.336890,0.146730,0.595699,0.903989,0.998795,0.857729,0.514103,0.049068,-0.427555,-0.803208,-0.989177,-0.941544,-0.671559,-0.242980,
|
||||
0.242980,0.671559,0.941544,0.989177,0.803208,0.427555,-0.049068,-0.514103,-0.857729,-0.998795,-0.903989,-0.595699,-0.146730,0.336890,0.740951,0.970031},
|
||||
{-0.956940,-0.634393,-0.098017,0.471397,0.881921,0.995185,0.773010,0.290285,-0.290285,-0.773010,-0.995185,-0.881921,-0.471397,0.098017,0.634393,0.956940,
|
||||
0.956940,0.634393,0.098017,-0.471397,-0.881921,-0.995185,-0.773010,-0.290285,0.290285,0.773010,0.995185,0.881921,0.471397,-0.098017,-0.634393,-0.956940},
|
||||
{-0.941544,-0.514103,0.146730,0.740951,0.998795,0.803208,0.242980,-0.427555,-0.903989,-0.970031,-0.595699,0.049068,0.671559,0.989177,0.857729,0.336890,
|
||||
-0.336890,-0.857729,-0.989177,-0.671559,-0.049068,0.595699,0.970031,0.903989,0.427555,-0.242980,-0.803208,-0.998795,-0.740951,-0.146730,0.514103,0.941544},
|
||||
{-0.923880,-0.382683,0.382683,0.923880,0.923880,0.382683,-0.382683,-0.923880,-0.923880,-0.382683,0.382683,0.923880,0.923880,0.382683,-0.382683,-0.923880,
|
||||
-0.923880,-0.382683,0.382683,0.923880,0.923880,0.382683,-0.382683,-0.923880,-0.923880,-0.382683,0.382683,0.923880,0.923880,0.382683,-0.382683,-0.923880},
|
||||
{-0.903989,-0.242980,0.595699,0.998795,0.671559,-0.146730,-0.857729,-0.941544,-0.336890,0.514103,0.989177,0.740951,-0.049068,-0.803208,-0.970031,-0.427555,
|
||||
0.427555,0.970031,0.803208,0.049068,-0.740951,-0.989177,-0.514103,0.336890,0.941544,0.857729,0.146730,-0.671559,-0.998795,-0.595699,0.242980,0.903989},
|
||||
{-0.881921,-0.098017,0.773010,0.956940,0.290285,-0.634393,-0.995185,-0.471397,0.471397,0.995185,0.634393,-0.290285,-0.956940,-0.773010,0.098017,0.881921,
|
||||
0.881921,0.098017,-0.773010,-0.956940,-0.290285,0.634393,0.995185,0.471397,-0.471397,-0.995185,-0.634393,0.290285,0.956940,0.773010,-0.098017,-0.881921},
|
||||
{-0.857729,0.049068,0.903989,0.803208,-0.146730,-0.941544,-0.740951,0.242980,0.970031,0.671559,-0.336890,-0.989177,-0.595699,0.427555,0.998795,0.514103,
|
||||
-0.514103,-0.998795,-0.427555,0.595699,0.989177,0.336890,-0.671559,-0.970031,-0.242980,0.740951,0.941544,0.146730,-0.803208,-0.903989,-0.049068,0.857729},
|
||||
{-0.831470,0.195090,0.980785,0.555570,-0.555570,-0.980785,-0.195090,0.831470,0.831470,-0.195090,-0.980785,-0.555570,0.555570,0.980785,0.195090,-0.831470,
|
||||
-0.831470,0.195090,0.980785,0.555570,-0.555570,-0.980785,-0.195090,0.831470,0.831470,-0.195090,-0.980785,-0.555570,0.555570,0.980785,0.195090,-0.831470},
|
||||
{-0.803208,0.336890,0.998795,0.242980,-0.857729,-0.740951,0.427555,0.989177,0.146730,-0.903989,-0.671559,0.514103,0.970031,0.049068,-0.941544,-0.595699,
|
||||
0.595699,0.941544,-0.049068,-0.970031,-0.514103,0.671559,0.903989,-0.146730,-0.989177,-0.427555,0.740951,0.857729,-0.242980,-0.998795,-0.336890,0.803208},
|
||||
{-0.773010,0.471397,0.956940,-0.098017,-0.995185,-0.290285,0.881921,0.634393,-0.634393,-0.881921,0.290285,0.995185,0.098017,-0.956940,-0.471397,0.773010,
|
||||
0.773010,-0.471397,-0.956940,0.098017,0.995185,0.290285,-0.881921,-0.634393,0.634393,0.881921,-0.290285,-0.995185,-0.098017,0.956940,0.471397,-0.773010},
|
||||
{-0.740951,0.595699,0.857729,-0.427555,-0.941544,0.242980,0.989177,-0.049068,-0.998795,-0.146730,0.970031,0.336890,-0.903989,-0.514103,0.803208,0.671559,
|
||||
-0.671559,-0.803208,0.514103,0.903989,-0.336890,-0.970031,0.146730,0.998795,0.049068,-0.989177,-0.242980,0.941544,0.427555,-0.857729,-0.595699,0.740951}
|
||||
};
|
||||
|
||||
// up to 32-bits
|
||||
static unsigned jo_readBits(const unsigned char *data, int *at, int num) {
|
||||
unsigned r = 0;
|
||||
// read partial starting bits
|
||||
int sc = (8 - (*at & 7)) & 7;
|
||||
sc = sc > num ? num : sc;
|
||||
if(sc) {
|
||||
r = (data[*at/8] >> (8 - (*at&7) - sc)) & ((1<<sc)-1);
|
||||
num -= sc;
|
||||
*at += sc;
|
||||
}
|
||||
// read full bytes
|
||||
while(num>=8) {
|
||||
r <<= 8;
|
||||
r |= data[*at/8];
|
||||
*at += 8;
|
||||
num -= 8;
|
||||
}
|
||||
// read partial ending bits
|
||||
if(num) {
|
||||
r <<= num;
|
||||
r |= (data[*at/8] >> (8 - num)) & ((1<<num)-1);
|
||||
*at += num;
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
|
||||
bool jo_read_mp1(const void *input, int inputSize, short **output_, int *outputSize_, int *hz_, int *channels_) {
|
||||
int outputSize = 0, hz, channels;
|
||||
short *output = 0;
|
||||
int outputMax = 0;
|
||||
const unsigned char *data = (const unsigned char *)input;
|
||||
|
||||
// Buffers for the lapped transform
|
||||
double buf[2][2 * 512] = { 0 };
|
||||
int bufOffset[2] = { 64,64 };
|
||||
int at = 0; // Where in the stream are we?
|
||||
while (at < inputSize * 8) {
|
||||
// Sync markers are byte aligned
|
||||
at = (at + 7)&-8;
|
||||
while (at < inputSize * 8 - 32) {
|
||||
if (data[at / 8] == 0xFF && (data[at / 8 + 1] & 0xF0) == 0xF0) {
|
||||
break;
|
||||
}
|
||||
else {
|
||||
at += 8;
|
||||
}
|
||||
}
|
||||
if (at >= inputSize * 8 - 32) {
|
||||
break;
|
||||
}
|
||||
|
||||
unsigned header = jo_readBits(data, &at, 32);
|
||||
//printf("header: %x.%x/%x: %08x\n", (at-32)/8, (at-32)&7, inputSize, header);
|
||||
|
||||
// sync = 0xFFF
|
||||
// ID = 1
|
||||
// layer = 3 (layer 1)
|
||||
if ((header & 0xFFFE0000) != 0xFFFE0000) {
|
||||
return false;
|
||||
}
|
||||
|
||||
static const int bitrateTbl[16] = { 0,32,40,48,56,64,80,96,112,128,160,192,224,256,320,-1 };
|
||||
int kbps = bitrateTbl[(header >> 12) & 15];
|
||||
if (kbps < 0) {
|
||||
return false;
|
||||
}
|
||||
static const int hzTbl[4] = { 44100, 48000, 32000, 0 };
|
||||
hz = hzTbl[(header >> 10) & 3];
|
||||
if (!hz) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// mode 0 = stereo
|
||||
// mode 1 = joint stereo
|
||||
// mode 2 = dual channel (no idea what this does TODO)
|
||||
// mode 3 = mono
|
||||
int mode = (header >> 6) & 3;
|
||||
int modeExt = (header >> 4) & 3;
|
||||
channels = mode == 3 ? 1 : 2;
|
||||
const int bound = mode == 1 ? (modeExt + 1) * 4 : 32;
|
||||
|
||||
bool errorProtection = ((header >> 16) & 1) ^ 1; //< @r-lyeh extra parens
|
||||
if (errorProtection) {
|
||||
at += 16; // skip the CRC.
|
||||
}
|
||||
|
||||
// Read bit allocations
|
||||
int bitAlloc[32][2] = { 0 };
|
||||
for (int i = 0; i < bound; ++i) {
|
||||
for (int ch = 0; ch < channels; ++ch) {
|
||||
bitAlloc[i][ch] = jo_readBits(data, &at, 4);
|
||||
}
|
||||
}
|
||||
for (int i = bound; i < 32; ++i) {
|
||||
bitAlloc[i][1] = bitAlloc[i][0] = jo_readBits(data, &at, 4);
|
||||
}
|
||||
|
||||
// Read scale indexes
|
||||
int scaleIdx[32][2];
|
||||
for (int i = 0; i < 32; ++i) {
|
||||
for (int ch = 0; ch < channels; ++ch) {
|
||||
scaleIdx[i][ch] = bitAlloc[i][ch] ? jo_readBits(data, &at, 6) : 63;
|
||||
}
|
||||
}
|
||||
|
||||
// Read & compute output samples
|
||||
short pcm[12][2][32];
|
||||
for (int s = 0; s < 12; ++s) {
|
||||
// Read normalized, quantized band samples
|
||||
int samples[32][2] = { 0 };
|
||||
for (int i = 0; i < bound; ++i) {
|
||||
for (int ch = 0; ch < channels; ++ch) {
|
||||
if (bitAlloc[i][ch]) {
|
||||
samples[i][ch] = jo_readBits(data, &at, bitAlloc[i][ch] + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (int i = bound; i < 32; ++i) {
|
||||
if (bitAlloc[i][0]) {
|
||||
samples[i][1] = samples[i][0] = jo_readBits(data, &at, bitAlloc[i][0] + 1);
|
||||
}
|
||||
}
|
||||
// Compute bands: Dequantize & Denormalize
|
||||
double bandTbl[2][32] = { 0 };
|
||||
for (int i = 0; i < 32; ++i) {
|
||||
for (int ch = 0; ch < channels; ++ch) {
|
||||
int b = bitAlloc[i][ch];
|
||||
if (b++) {
|
||||
int samp = samples[i][ch];
|
||||
double f = ((samp >> (b - 1)) & 1) ? 0 : -1;
|
||||
f += (samp & ((1 << (b - 1)) - 1)) / (double)(1 << (b - 1));
|
||||
f = (f + 1.0 / (1 << (b - 1))) * (1 << b) / ((1 << b) - 1.0);
|
||||
f *= s_jo_multTbl[scaleIdx[i][ch]];
|
||||
bandTbl[ch][i] = f;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Convert subbands to PCM
|
||||
for (int ch = 0; ch < channels; ++ch) {
|
||||
bufOffset[ch] = (bufOffset[ch] + 0x3C0) & 0x3ff;
|
||||
double *bufOffsetPtr = buf[ch] + bufOffset[ch];
|
||||
const double *f = s_jo_filterTbl[0];
|
||||
for (int i = 0; i < 64; ++i) {
|
||||
double sum = 0;
|
||||
for (int j = 0; j < 32; ++j) {
|
||||
sum += *f++ * bandTbl[ch][j];
|
||||
}
|
||||
bufOffsetPtr[i] = sum;
|
||||
}
|
||||
const double *w = s_jo_windowTbl;
|
||||
for (int i = 0; i < 32; ++i) {
|
||||
double sum = 0;
|
||||
for (int j = 0; j < 16; ++j) {
|
||||
int k = i | (j + (j + 1 & -2)) << 5;
|
||||
sum += *w++ * buf[ch][(k + bufOffset[ch]) & 0x3ff];
|
||||
}
|
||||
int ss = (int)(sum * 0x8000);
|
||||
ss = ss > SHRT_MAX ? SHRT_MAX : ss < SHRT_MIN ? SHRT_MIN : ss;
|
||||
pcm[s][ch][i] = ss;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (at > inputSize * 8) {
|
||||
printf("file corruption?\n");
|
||||
return false;
|
||||
}
|
||||
if (outputMax == 0) {
|
||||
// estimate total number of samples (may be totally wrong, but its better than nothing)
|
||||
at = (at + 7)&-8;
|
||||
outputMax = inputSize / (at / 8) * 384 * channels * sizeof(*output);
|
||||
output = (short*)REALLOC(output, outputMax);
|
||||
}
|
||||
if (outputSize * sizeof(*output) + 384 * channels * sizeof(*output) > outputMax) {
|
||||
outputMax += 384 * channels * sizeof(*output);
|
||||
output = (short*)REALLOC(output, outputMax);
|
||||
}
|
||||
for (int i = 0; i < 12; ++i) {
|
||||
for (int j = 0; j < 32; ++j) {
|
||||
for (int k = 0; k < channels; ++k) {
|
||||
output[outputSize++] = pcm[i][k][j];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
*outputSize_ = outputSize;
|
||||
*hz_ = hz;
|
||||
*channels_ = channels;
|
||||
*output_ = output;
|
||||
|
||||
return outputSize && hz && channels && output;
|
||||
}
|
||||
|
||||
#endif // JO_MP1_HEADER_FILE_ONLY
|
||||
|
||||
/* public domain Simple, Minimalistic MPEG Layer 1 decoder - http://jonolick.com
|
||||
*
|
||||
* Revision History:
|
||||
* 1.00 (2014-26-1) Initial release.
|
||||
*
|
||||
* Basic usage:
|
||||
* int hz, channels, outputSize;
|
||||
* short *output;
|
||||
* jo_read_mp1(input, inputSize, output, outputSize, hz, channels);
|
||||
* // Do something with the data here
|
||||
* free(output);
|
||||
*
|
||||
* */
|
||||
|
||||
#ifndef JO_INCLUDE_MP1_H
|
||||
#define JO_INCLUDE_MP1_H
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
extern bool jo_read_mp1(const void *input, int inputSize, short **output, int *outputSize, int *hz, int *channels);
|
||||
|
||||
#endif // JO_INCLUDE_MP1_H
|
||||
|
||||
#ifndef JO_MP1_HEADER_FILE_ONLY
|
||||
|
||||
#if defined(_MSC_VER) && _MSC_VER >= 0x1400
|
||||
#define _CRT_SECURE_NO_WARNINGS // suppress warnings about fopen()
|
||||
#endif
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <math.h>
|
||||
#include <limits.h>
|
||||
|
||||
static const double s_jo_multTbl[64] = {
|
||||
2.000000,1.587401,1.259921,1.000000,0.793701,0.629961,0.500000,0.396850,0.314980,0.250000,0.198425,0.157490,0.125000,0.099213,0.078745,0.062500,
|
||||
0.049606,0.039373,0.031250,0.024803,0.019686,0.015625,0.012402,0.009843,0.007812,0.006201,0.004922,0.003906,0.003100,0.002461,0.001953,0.001550,
|
||||
0.001230,0.000977,0.000775,0.000615,0.000488,0.000388,0.000308,0.000244,0.000194,0.000154,0.000122,0.000097,0.000077,0.000061,0.000048,0.000038,
|
||||
0.000031,0.000024,0.000019,0.000015,0.000012,0.000010,0.000008,0.000006,0.000005,0.000004,0.000003,0.000002,0.000002,0.000002,0.000001,1e-20
|
||||
};
|
||||
|
||||
// l = i - 256;
|
||||
// s = (i & 0x40) ? 1 : -1;
|
||||
// windowTbl[(i/16)|((i%16)<<5)] = s * 20 * exp(-(l/112)*-(l/112)) * sin(l * M_PI*2 / 112) / l;
|
||||
static const double s_jo_windowTbl[512] = {
|
||||
-0.000000,-0.000443,0.003250,-0.007004,0.031082,-0.078629,0.100311,-0.572037,1.144989,0.572037,0.100311,0.078629,0.031082,0.007004,0.003250,0.000443,
|
||||
-0.000015,-0.000473,0.003326,-0.007919,0.030518,-0.084183,0.090927,-0.600220,1.144287,0.543823,0.108856,0.073059,0.031479,0.006119,0.003174,0.000397,
|
||||
-0.000015,-0.000534,0.003387,-0.008865,0.029785,-0.089706,0.080688,-0.628296,1.142212,0.515610,0.116577,0.067520,0.031738,0.005295,0.003082,0.000366,
|
||||
-0.000015,-0.000580,0.003433,-0.009842,0.028885,-0.095169,0.069595,-0.656219,1.138763,0.487473,0.123474,0.061996,0.031845,0.004486,0.002991,0.000320,
|
||||
-0.000015,-0.000626,0.003464,-0.010849,0.027802,-0.100540,0.057617,-0.683914,1.133926,0.459473,0.129578,0.056534,0.031815,0.003723,0.002899,0.000290,
|
||||
-0.000015,-0.000687,0.003479,-0.011887,0.026535,-0.105820,0.044785,-0.711319,1.127747,0.431656,0.134888,0.051132,0.031662,0.003006,0.002792,0.000259,
|
||||
-0.000015,-0.000748,0.003479,-0.012939,0.025085,-0.110947,0.031082,-0.738373,1.120224,0.404083,0.139450,0.045837,0.031387,0.002335,0.002686,0.000244,
|
||||
-0.000031,-0.000809,0.003464,-0.014023,0.023422,-0.115921,0.016510,-0.765030,1.111374,0.376801,0.143265,0.040634,0.031006,0.001694,0.002579,0.000214,
|
||||
-0.000031,-0.000885,0.003418,-0.015121,0.021576,-0.120697,0.001068,-0.791214,1.101212,0.349869,0.146362,0.035553,0.030533,0.001099,0.002457,0.000198,
|
||||
-0.000031,-0.000961,0.003372,-0.016235,0.019531,-0.125259,-0.015228,-0.816864,1.089783,0.323318,0.148773,0.030609,0.029938,0.000549,0.002350,0.000168,
|
||||
-0.000031,-0.001038,0.003281,-0.017349,0.017258,-0.129562,-0.032379,-0.841949,1.077118,0.297211,0.150497,0.025818,0.029282,0.000031,0.002243,0.000153,
|
||||
-0.000046,-0.001114,0.003174,-0.018463,0.014801,-0.133591,-0.050354,-0.866364,1.063217,0.271591,0.151596,0.021179,0.028534,-0.000443,0.002121,0.000137,
|
||||
-0.000046,-0.001205,0.003052,-0.019577,0.012115,-0.137299,-0.069168,-0.890091,1.048157,0.246506,0.152069,0.016708,0.027725,-0.000870,0.002014,0.000122,
|
||||
-0.000061,-0.001297,0.002884,-0.020691,0.009232,-0.140671,-0.088776,-0.913055,1.031937,0.221985,0.151962,0.012421,0.026840,-0.001266,0.001907,0.000107,
|
||||
-0.000061,-0.001389,0.002701,-0.021790,0.006134,-0.143677,-0.109161,-0.935196,1.014618,0.198059,0.151306,0.008316,0.025909,-0.001617,0.001785,0.000107,
|
||||
-0.000076,-0.001480,0.002487,-0.022858,0.002823,-0.146255,-0.130310,-0.956482,0.996246,0.174789,0.150116,0.004395,0.024933,-0.001938,0.001694,0.000092,
|
||||
-0.000076,-0.001587,0.002228,-0.023911,-0.000687,-0.148422,-0.152206,-0.976852,0.976852,0.152206,0.148422,0.000687,0.023911,-0.002228,0.001587,0.000076,
|
||||
-0.000092,-0.001694,0.001938,-0.024933,-0.004395,-0.150116,-0.174789,-0.996246,0.956482,0.130310,0.146255,-0.002823,0.022858,-0.002487,0.001480,0.000076,
|
||||
-0.000107,-0.001785,0.001617,-0.025909,-0.008316,-0.151306,-0.198059,-1.014618,0.935196,0.109161,0.143677,-0.006134,0.021790,-0.002701,0.001389,0.000061,
|
||||
-0.000107,-0.001907,0.001266,-0.026840,-0.012421,-0.151962,-0.221985,-1.031937,0.913055,0.088776,0.140671,-0.009232,0.020691,-0.002884,0.001297,0.000061,
|
||||
-0.000122,-0.002014,0.000870,-0.027725,-0.016708,-0.152069,-0.246506,-1.048157,0.890091,0.069168,0.137299,-0.012115,0.019577,-0.003052,0.001205,0.000046,
|
||||
-0.000137,-0.002121,0.000443,-0.028534,-0.021179,-0.151596,-0.271591,-1.063217,0.866364,0.050354,0.133591,-0.014801,0.018463,-0.003174,0.001114,0.000046,
|
||||
-0.000153,-0.002243,-0.000031,-0.029282,-0.025818,-0.150497,-0.297211,-1.077118,0.841949,0.032379,0.129562,-0.017258,0.017349,-0.003281,0.001038,0.000031,
|
||||
-0.000168,-0.002350,-0.000549,-0.029938,-0.030609,-0.148773,-0.323318,-1.089783,0.816864,0.015228,0.125259,-0.019531,0.016235,-0.003372,0.000961,0.000031,
|
||||
-0.000198,-0.002457,-0.001099,-0.030533,-0.035553,-0.146362,-0.349869,-1.101212,0.791214,-0.001068,0.120697,-0.021576,0.015121,-0.003418,0.000885,0.000031,
|
||||
-0.000214,-0.002579,-0.001694,-0.031006,-0.040634,-0.143265,-0.376801,-1.111374,0.765030,-0.016510,0.115921,-0.023422,0.014023,-0.003464,0.000809,0.000031,
|
||||
-0.000244,-0.002686,-0.002335,-0.031387,-0.045837,-0.139450,-0.404083,-1.120224,0.738373,-0.031082,0.110947,-0.025085,0.012939,-0.003479,0.000748,0.000015,
|
||||
-0.000259,-0.002792,-0.003006,-0.031662,-0.051132,-0.134888,-0.431656,-1.127747,0.711319,-0.044785,0.105820,-0.026535,0.011887,-0.003479,0.000687,0.000015,
|
||||
-0.000290,-0.002899,-0.003723,-0.031815,-0.056534,-0.129578,-0.459473,-1.133926,0.683914,-0.057617,0.100540,-0.027802,0.010849,-0.003464,0.000626,0.000015,
|
||||
-0.000320,-0.002991,-0.004486,-0.031845,-0.061996,-0.123474,-0.487473,-1.138763,0.656219,-0.069595,0.095169,-0.028885,0.009842,-0.003433,0.000580,0.000015,
|
||||
-0.000366,-0.003082,-0.005295,-0.031738,-0.067520,-0.116577,-0.515610,-1.142212,0.628296,-0.080688,0.089706,-0.029785,0.008865,-0.003387,0.000534,0.000015,
|
||||
-0.000397,-0.003174,-0.006119,-0.031479,-0.073059,-0.108856,-0.543823,-1.144287,0.600220,-0.090927,0.084183,-0.030518,0.007919,-0.003326,0.000473,0.000015,
|
||||
};
|
||||
|
||||
// filterTbl[i][j] = cos(((M_PI/64*i+M_PI/4)*(2*j+1)));
|
||||
static const double s_jo_filterTbl[64][32] = {
|
||||
{0.707107,-0.707107,-0.707107,0.707107,0.707107,-0.707107,-0.707107,0.707107,0.707107,-0.707107,-0.707107,0.707107,0.707107,-0.707107,-0.707107,0.707107,
|
||||
0.707107,-0.707107,-0.707107,0.707107,0.707107,-0.707107,-0.707107,0.707107,0.707107,-0.707107,-0.707107,0.707107,0.707107,-0.707107,-0.707107,0.707107},
|
||||
{0.671559,-0.803208,-0.514103,0.903989,0.336890,-0.970031,-0.146730,0.998795,-0.049068,-0.989177,0.242980,0.941544,-0.427555,-0.857729,0.595699,0.740951,
|
||||
-0.740951,-0.595699,0.857729,0.427555,-0.941544,-0.242980,0.989177,0.049068,-0.998795,0.146730,0.970031,-0.336890,-0.903989,0.514103,0.803208,-0.671559},
|
||||
{0.634393,-0.881921,-0.290285,0.995185,-0.098017,-0.956940,0.471397,0.773010,-0.773010,-0.471397,0.956940,0.098017,-0.995185,0.290285,0.881921,-0.634393,
|
||||
-0.634393,0.881921,0.290285,-0.995185,0.098017,0.956940,-0.471397,-0.773010,0.773010,0.471397,-0.956940,-0.098017,0.995185,-0.290285,-0.881921,0.634393},
|
||||
{0.595699,-0.941544,-0.049068,0.970031,-0.514103,-0.671559,0.903989,0.146730,-0.989177,0.427555,0.740951,-0.857729,-0.242980,0.998795,-0.336890,-0.803208,
|
||||
0.803208,0.336890,-0.998795,0.242980,0.857729,-0.740951,-0.427555,0.989177,-0.146730,-0.903989,0.671559,0.514103,-0.970031,0.049068,0.941544,-0.595699},
|
||||
{0.555570,-0.980785,0.195090,0.831470,-0.831470,-0.195090,0.980785,-0.555570,-0.555570,0.980785,-0.195090,-0.831470,0.831470,0.195090,-0.980785,0.555570,
|
||||
0.555570,-0.980785,0.195090,0.831470,-0.831470,-0.195090,0.980785,-0.555570,-0.555570,0.980785,-0.195090,-0.831470,0.831470,0.195090,-0.980785,0.555570},
|
||||
{0.514103,-0.998795,0.427555,0.595699,-0.989177,0.336890,0.671559,-0.970031,0.242980,0.740951,-0.941544,0.146730,0.803208,-0.903989,0.049068,0.857729,
|
||||
-0.857729,-0.049068,0.903989,-0.803208,-0.146730,0.941544,-0.740951,-0.242980,0.970031,-0.671559,-0.336890,0.989177,-0.595699,-0.427555,0.998795,-0.514103},
|
||||
{0.471397,-0.995185,0.634393,0.290285,-0.956940,0.773010,0.098017,-0.881921,0.881921,-0.098017,-0.773010,0.956940,-0.290285,-0.634393,0.995185,-0.471397,
|
||||
-0.471397,0.995185,-0.634393,-0.290285,0.956940,-0.773010,-0.098017,0.881921,-0.881921,0.098017,0.773010,-0.956940,0.290285,0.634393,-0.995185,0.471397},
|
||||
{0.427555,-0.970031,0.803208,-0.049068,-0.740951,0.989177,-0.514103,-0.336890,0.941544,-0.857729,0.146730,0.671559,-0.998795,0.595699,0.242980,-0.903989,
|
||||
0.903989,-0.242980,-0.595699,0.998795,-0.671559,-0.146730,0.857729,-0.941544,0.336890,0.514103,-0.989177,0.740951,0.049068,-0.803208,0.970031,-0.427555},
|
||||
{0.382683,-0.923880,0.923880,-0.382683,-0.382683,0.923880,-0.923880,0.382683,0.382683,-0.923880,0.923880,-0.382683,-0.382683,0.923880,-0.923880,0.382683,
|
||||
0.382683,-0.923880,0.923880,-0.382683,-0.382683,0.923880,-0.923880,0.382683,0.382683,-0.923880,0.923880,-0.382683,-0.382683,0.923880,-0.923880,0.382683},
|
||||
{0.336890,-0.857729,0.989177,-0.671559,0.049068,0.595699,-0.970031,0.903989,-0.427555,-0.242980,0.803208,-0.998795,0.740951,-0.146730,-0.514103,0.941544,
|
||||
-0.941544,0.514103,0.146730,-0.740951,0.998795,-0.803208,0.242980,0.427555,-0.903989,0.970031,-0.595699,-0.049068,0.671559,-0.989177,0.857729,-0.336890},
|
||||
{0.290285,-0.773010,0.995185,-0.881921,0.471397,0.098017,-0.634393,0.956940,-0.956940,0.634393,-0.098017,-0.471397,0.881921,-0.995185,0.773010,-0.290285,
|
||||
-0.290285,0.773010,-0.995185,0.881921,-0.471397,-0.098017,0.634393,-0.956940,0.956940,-0.634393,0.098017,0.471397,-0.881921,0.995185,-0.773010,0.290285},
|
||||
{0.242980,-0.671559,0.941544,-0.989177,0.803208,-0.427555,-0.049068,0.514103,-0.857729,0.998795,-0.903989,0.595699,-0.146730,-0.336890,0.740951,-0.970031,
|
||||
0.970031,-0.740951,0.336890,0.146730,-0.595699,0.903989,-0.998795,0.857729,-0.514103,0.049068,0.427555,-0.803208,0.989177,-0.941544,0.671559,-0.242980},
|
||||
{0.195090,-0.555570,0.831470,-0.980785,0.980785,-0.831470,0.555570,-0.195090,-0.195090,0.555570,-0.831470,0.980785,-0.980785,0.831470,-0.555570,0.195090,
|
||||
0.195090,-0.555570,0.831470,-0.980785,0.980785,-0.831470,0.555570,-0.195090,-0.195090,0.555570,-0.831470,0.980785,-0.980785,0.831470,-0.555570,0.195090},
|
||||
{0.146730,-0.427555,0.671559,-0.857729,0.970031,-0.998795,0.941544,-0.803208,0.595699,-0.336890,0.049068,0.242980,-0.514103,0.740951,-0.903989,0.989177,
|
||||
-0.989177,0.903989,-0.740951,0.514103,-0.242980,-0.049068,0.336890,-0.595699,0.803208,-0.941544,0.998795,-0.970031,0.857729,-0.671559,0.427555,-0.146730},
|
||||
{0.098017,-0.290285,0.471397,-0.634393,0.773010,-0.881921,0.956940,-0.995185,0.995185,-0.956940,0.881921,-0.773010,0.634393,-0.471397,0.290285,-0.098017,
|
||||
-0.098017,0.290285,-0.471397,0.634393,-0.773010,0.881921,-0.956940,0.995185,-0.995185,0.956940,-0.881921,0.773010,-0.634393,0.471397,-0.290285,0.098017},
|
||||
{0.049068,-0.146730,0.242980,-0.336890,0.427555,-0.514103,0.595699,-0.671559,0.740951,-0.803208,0.857729,-0.903989,0.941544,-0.970031,0.989177,-0.998795,
|
||||
0.998795,-0.989177,0.970031,-0.941544,0.903989,-0.857729,0.803208,-0.740951,0.671559,-0.595699,0.514103,-0.427555,0.336890,-0.242980,0.146730,-0.049068},
|
||||
{0.000000,-0.000000,0.000000,-0.000000,0.000000,-0.000000,-0.000000,-0.000000,-0.000000,-0.000000,-0.000000,-0.000000,-0.000000,-0.000000,-0.000000,-0.000000,
|
||||
0.000000,-0.000000,0.000000,-0.000000,0.000000,-0.000000,0.000000,0.000000,0.000000,-0.000000,0.000000,0.000000,0.000000,-0.000000,0.000000,0.000000},
|
||||
{-0.049068,0.146730,-0.242980,0.336890,-0.427555,0.514103,-0.595699,0.671559,-0.740951,0.803208,-0.857729,0.903989,-0.941544,0.970031,-0.989177,0.998795,
|
||||
-0.998795,0.989177,-0.970031,0.941544,-0.903989,0.857729,-0.803208,0.740951,-0.671559,0.595699,-0.514103,0.427555,-0.336890,0.242980,-0.146730,0.049068},
|
||||
{-0.098017,0.290285,-0.471397,0.634393,-0.773010,0.881921,-0.956940,0.995185,-0.995185,0.956940,-0.881921,0.773010,-0.634393,0.471397,-0.290285,0.098017,
|
||||
0.098017,-0.290285,0.471397,-0.634393,0.773010,-0.881921,0.956940,-0.995185,0.995185,-0.956940,0.881921,-0.773010,0.634393,-0.471397,0.290285,-0.098017},
|
||||
{-0.146730,0.427555,-0.671559,0.857729,-0.970031,0.998795,-0.941544,0.803208,-0.595699,0.336890,-0.049068,-0.242980,0.514103,-0.740951,0.903989,-0.989177,
|
||||
0.989177,-0.903989,0.740951,-0.514103,0.242980,0.049068,-0.336890,0.595699,-0.803208,0.941544,-0.998795,0.970031,-0.857729,0.671559,-0.427555,0.146730},
|
||||
{-0.195090,0.555570,-0.831470,0.980785,-0.980785,0.831470,-0.555570,0.195090,0.195090,-0.555570,0.831470,-0.980785,0.980785,-0.831470,0.555570,-0.195090,
|
||||
-0.195090,0.555570,-0.831470,0.980785,-0.980785,0.831470,-0.555570,0.195090,0.195090,-0.555570,0.831470,-0.980785,0.980785,-0.831470,0.555570,-0.195090},
|
||||
{-0.242980,0.671559,-0.941544,0.989177,-0.803208,0.427555,0.049068,-0.514103,0.857729,-0.998795,0.903989,-0.595699,0.146730,0.336890,-0.740951,0.970031,
|
||||
-0.970031,0.740951,-0.336890,-0.146730,0.595699,-0.903989,0.998795,-0.857729,0.514103,-0.049068,-0.427555,0.803208,-0.989177,0.941544,-0.671559,0.242980},
|
||||
{-0.290285,0.773010,-0.995185,0.881921,-0.471397,-0.098017,0.634393,-0.956940,0.956940,-0.634393,0.098017,0.471397,-0.881921,0.995185,-0.773010,0.290285,
|
||||
0.290285,-0.773010,0.995185,-0.881921,0.471397,0.098017,-0.634393,0.956940,-0.956940,0.634393,-0.098017,-0.471397,0.881921,-0.995185,0.773010,-0.290285},
|
||||
{-0.336890,0.857729,-0.989177,0.671559,-0.049068,-0.595699,0.970031,-0.903989,0.427555,0.242980,-0.803208,0.998795,-0.740951,0.146730,0.514103,-0.941544,
|
||||
0.941544,-0.514103,-0.146730,0.740951,-0.998795,0.803208,-0.242980,-0.427555,0.903989,-0.970031,0.595699,0.049068,-0.671559,0.989177,-0.857729,0.336890},
|
||||
{-0.382683,0.923880,-0.923880,0.382683,0.382683,-0.923880,0.923880,-0.382683,-0.382683,0.923880,-0.923880,0.382683,0.382683,-0.923880,0.923880,-0.382683,
|
||||
-0.382683,0.923880,-0.923880,0.382683,0.382683,-0.923880,0.923880,-0.382683,-0.382683,0.923880,-0.923880,0.382683,0.382683,-0.923880,0.923880,-0.382683},
|
||||
{-0.427555,0.970031,-0.803208,0.049068,0.740951,-0.989177,0.514103,0.336890,-0.941544,0.857729,-0.146730,-0.671559,0.998795,-0.595699,-0.242980,0.903989,
|
||||
-0.903989,0.242980,0.595699,-0.998795,0.671559,0.146730,-0.857729,0.941544,-0.336890,-0.514103,0.989177,-0.740951,-0.049068,0.803208,-0.970031,0.427555},
|
||||
{-0.471397,0.995185,-0.634393,-0.290285,0.956940,-0.773010,-0.098017,0.881921,-0.881921,0.098017,0.773010,-0.956940,0.290285,0.634393,-0.995185,0.471397,
|
||||
0.471397,-0.995185,0.634393,0.290285,-0.956940,0.773010,0.098017,-0.881921,0.881921,-0.098017,-0.773010,0.956940,-0.290285,-0.634393,0.995185,-0.471397},
|
||||
{-0.514103,0.998795,-0.427555,-0.595699,0.989177,-0.336890,-0.671559,0.970031,-0.242980,-0.740951,0.941544,-0.146730,-0.803208,0.903989,-0.049068,-0.857729,
|
||||
0.857729,0.049068,-0.903989,0.803208,0.146730,-0.941544,0.740951,0.242980,-0.970031,0.671559,0.336890,-0.989177,0.595699,0.427555,-0.998795,0.514103},
|
||||
{-0.555570,0.980785,-0.195090,-0.831470,0.831470,0.195090,-0.980785,0.555570,0.555570,-0.980785,0.195090,0.831470,-0.831470,-0.195090,0.980785,-0.555570,
|
||||
-0.555570,0.980785,-0.195090,-0.831470,0.831470,0.195090,-0.980785,0.555570,0.555570,-0.980785,0.195090,0.831470,-0.831470,-0.195090,0.980785,-0.555570},
|
||||
{-0.595699,0.941544,0.049068,-0.970031,0.514103,0.671559,-0.903989,-0.146730,0.989177,-0.427555,-0.740951,0.857729,0.242980,-0.998795,0.336890,0.803208,
|
||||
-0.803208,-0.336890,0.998795,-0.242980,-0.857729,0.740951,0.427555,-0.989177,0.146730,0.903989,-0.671559,-0.514103,0.970031,-0.049068,-0.941544,0.595699},
|
||||
{-0.634393,0.881921,0.290285,-0.995185,0.098017,0.956940,-0.471397,-0.773010,0.773010,0.471397,-0.956940,-0.098017,0.995185,-0.290285,-0.881921,0.634393,
|
||||
0.634393,-0.881921,-0.290285,0.995185,-0.098017,-0.956940,0.471397,0.773010,-0.773010,-0.471397,0.956940,0.098017,-0.995185,0.290285,0.881921,-0.634393},
|
||||
{-0.671559,0.803208,0.514103,-0.903989,-0.336890,0.970031,0.146730,-0.998795,0.049068,0.989177,-0.242980,-0.941544,0.427555,0.857729,-0.595699,-0.740951,
|
||||
0.740951,0.595699,-0.857729,-0.427555,0.941544,0.242980,-0.989177,-0.049068,0.998795,-0.146730,-0.970031,0.336890,0.903989,-0.514103,-0.803208,0.671559},
|
||||
{-0.707107,0.707107,0.707107,-0.707107,-0.707107,0.707107,0.707107,-0.707107,-0.707107,0.707107,0.707107,-0.707107,-0.707107,0.707107,0.707107,-0.707107,
|
||||
-0.707107,0.707107,0.707107,-0.707107,-0.707107,0.707107,0.707107,-0.707107,-0.707107,0.707107,0.707107,-0.707107,-0.707107,0.707107,0.707107,-0.707107},
|
||||
{-0.740951,0.595699,0.857729,-0.427555,-0.941544,0.242980,0.989177,-0.049068,-0.998795,-0.146730,0.970031,0.336890,-0.903989,-0.514103,0.803208,0.671559,
|
||||
-0.671559,-0.803208,0.514103,0.903989,-0.336890,-0.970031,0.146730,0.998795,0.049068,-0.989177,-0.242980,0.941544,0.427555,-0.857729,-0.595699,0.740951},
|
||||
{-0.773010,0.471397,0.956940,-0.098017,-0.995185,-0.290285,0.881921,0.634393,-0.634393,-0.881921,0.290285,0.995185,0.098017,-0.956940,-0.471397,0.773010,
|
||||
0.773010,-0.471397,-0.956940,0.098017,0.995185,0.290285,-0.881921,-0.634393,0.634393,0.881921,-0.290285,-0.995185,-0.098017,0.956940,0.471397,-0.773010},
|
||||
{-0.803208,0.336890,0.998795,0.242980,-0.857729,-0.740951,0.427555,0.989177,0.146730,-0.903989,-0.671559,0.514103,0.970031,0.049068,-0.941544,-0.595699,
|
||||
0.595699,0.941544,-0.049068,-0.970031,-0.514103,0.671559,0.903989,-0.146730,-0.989177,-0.427555,0.740951,0.857729,-0.242980,-0.998795,-0.336890,0.803208},
|
||||
{-0.831470,0.195090,0.980785,0.555570,-0.555570,-0.980785,-0.195090,0.831470,0.831470,-0.195090,-0.980785,-0.555570,0.555570,0.980785,0.195090,-0.831470,
|
||||
-0.831470,0.195090,0.980785,0.555570,-0.555570,-0.980785,-0.195090,0.831470,0.831470,-0.195090,-0.980785,-0.555570,0.555570,0.980785,0.195090,-0.831470},
|
||||
{-0.857729,0.049068,0.903989,0.803208,-0.146730,-0.941544,-0.740951,0.242980,0.970031,0.671559,-0.336890,-0.989177,-0.595699,0.427555,0.998795,0.514103,
|
||||
-0.514103,-0.998795,-0.427555,0.595699,0.989177,0.336890,-0.671559,-0.970031,-0.242980,0.740951,0.941544,0.146730,-0.803208,-0.903989,-0.049068,0.857729},
|
||||
{-0.881921,-0.098017,0.773010,0.956940,0.290285,-0.634393,-0.995185,-0.471397,0.471397,0.995185,0.634393,-0.290285,-0.956940,-0.773010,0.098017,0.881921,
|
||||
0.881921,0.098017,-0.773010,-0.956940,-0.290285,0.634393,0.995185,0.471397,-0.471397,-0.995185,-0.634393,0.290285,0.956940,0.773010,-0.098017,-0.881921},
|
||||
{-0.903989,-0.242980,0.595699,0.998795,0.671559,-0.146730,-0.857729,-0.941544,-0.336890,0.514103,0.989177,0.740951,-0.049068,-0.803208,-0.970031,-0.427555,
|
||||
0.427555,0.970031,0.803208,0.049068,-0.740951,-0.989177,-0.514103,0.336890,0.941544,0.857729,0.146730,-0.671559,-0.998795,-0.595699,0.242980,0.903989},
|
||||
{-0.923880,-0.382683,0.382683,0.923880,0.923880,0.382683,-0.382683,-0.923880,-0.923880,-0.382683,0.382683,0.923880,0.923880,0.382683,-0.382683,-0.923880,
|
||||
-0.923880,-0.382683,0.382683,0.923880,0.923880,0.382683,-0.382683,-0.923880,-0.923880,-0.382683,0.382683,0.923880,0.923880,0.382683,-0.382683,-0.923880},
|
||||
{-0.941544,-0.514103,0.146730,0.740951,0.998795,0.803208,0.242980,-0.427555,-0.903989,-0.970031,-0.595699,0.049068,0.671559,0.989177,0.857729,0.336890,
|
||||
-0.336890,-0.857729,-0.989177,-0.671559,-0.049068,0.595699,0.970031,0.903989,0.427555,-0.242980,-0.803208,-0.998795,-0.740951,-0.146730,0.514103,0.941544},
|
||||
{-0.956940,-0.634393,-0.098017,0.471397,0.881921,0.995185,0.773010,0.290285,-0.290285,-0.773010,-0.995185,-0.881921,-0.471397,0.098017,0.634393,0.956940,
|
||||
0.956940,0.634393,0.098017,-0.471397,-0.881921,-0.995185,-0.773010,-0.290285,0.290285,0.773010,0.995185,0.881921,0.471397,-0.098017,-0.634393,-0.956940},
|
||||
{-0.970031,-0.740951,-0.336890,0.146730,0.595699,0.903989,0.998795,0.857729,0.514103,0.049068,-0.427555,-0.803208,-0.989177,-0.941544,-0.671559,-0.242980,
|
||||
0.242980,0.671559,0.941544,0.989177,0.803208,0.427555,-0.049068,-0.514103,-0.857729,-0.998795,-0.903989,-0.595699,-0.146730,0.336890,0.740951,0.970031},
|
||||
{-0.980785,-0.831470,-0.555570,-0.195090,0.195090,0.555570,0.831470,0.980785,0.980785,0.831470,0.555570,0.195090,-0.195090,-0.555570,-0.831470,-0.980785,
|
||||
-0.980785,-0.831470,-0.555570,-0.195090,0.195090,0.555570,0.831470,0.980785,0.980785,0.831470,0.555570,0.195090,-0.195090,-0.555570,-0.831470,-0.980785},
|
||||
{-0.989177,-0.903989,-0.740951,-0.514103,-0.242980,0.049068,0.336890,0.595699,0.803208,0.941544,0.998795,0.970031,0.857729,0.671559,0.427555,0.146730,
|
||||
-0.146730,-0.427555,-0.671559,-0.857729,-0.970031,-0.998795,-0.941544,-0.803208,-0.595699,-0.336890,-0.049068,0.242980,0.514103,0.740951,0.903989,0.989177},
|
||||
{-0.995185,-0.956940,-0.881921,-0.773010,-0.634393,-0.471397,-0.290285,-0.098017,0.098017,0.290285,0.471397,0.634393,0.773010,0.881921,0.956940,0.995185,
|
||||
0.995185,0.956940,0.881921,0.773010,0.634393,0.471397,0.290285,0.098017,-0.098017,-0.290285,-0.471397,-0.634393,-0.773010,-0.881921,-0.956940,-0.995185},
|
||||
{-0.998795,-0.989177,-0.970031,-0.941544,-0.903989,-0.857729,-0.803208,-0.740951,-0.671559,-0.595699,-0.514103,-0.427555,-0.336890,-0.242980,-0.146730,-0.049068,
|
||||
0.049068,0.146730,0.242980,0.336890,0.427555,0.514103,0.595699,0.671559,0.740951,0.803208,0.857729,0.903989,0.941544,0.970031,0.989177,0.998795},
|
||||
{-1.000000,-1.000000,-1.000000,-1.000000,-1.000000,-1.000000,-1.000000,-1.000000,-1.000000,-1.000000,-1.000000,-1.000000,-1.000000,-1.000000,-1.000000,-1.000000,
|
||||
-1.000000,-1.000000,-1.000000,-1.000000,-1.000000,-1.000000,-1.000000,-1.000000,-1.000000,-1.000000,-1.000000,-1.000000,-1.000000,-1.000000,-1.000000,-1.000000},
|
||||
{-0.998795,-0.989177,-0.970031,-0.941544,-0.903989,-0.857729,-0.803208,-0.740951,-0.671559,-0.595699,-0.514103,-0.427555,-0.336890,-0.242980,-0.146730,-0.049068,
|
||||
0.049068,0.146730,0.242980,0.336890,0.427555,0.514103,0.595699,0.671559,0.740951,0.803208,0.857729,0.903989,0.941544,0.970031,0.989177,0.998795},
|
||||
{-0.995185,-0.956940,-0.881921,-0.773010,-0.634393,-0.471397,-0.290285,-0.098017,0.098017,0.290285,0.471397,0.634393,0.773010,0.881921,0.956940,0.995185,
|
||||
0.995185,0.956940,0.881921,0.773010,0.634393,0.471397,0.290285,0.098017,-0.098017,-0.290285,-0.471397,-0.634393,-0.773010,-0.881921,-0.956940,-0.995185},
|
||||
{-0.989177,-0.903989,-0.740951,-0.514103,-0.242980,0.049068,0.336890,0.595699,0.803208,0.941544,0.998795,0.970031,0.857729,0.671559,0.427555,0.146730,
|
||||
-0.146730,-0.427555,-0.671559,-0.857729,-0.970031,-0.998795,-0.941544,-0.803208,-0.595699,-0.336890,-0.049068,0.242980,0.514103,0.740951,0.903989,0.989177},
|
||||
{-0.980785,-0.831470,-0.555570,-0.195090,0.195090,0.555570,0.831470,0.980785,0.980785,0.831470,0.555570,0.195090,-0.195090,-0.555570,-0.831470,-0.980785,
|
||||
-0.980785,-0.831470,-0.555570,-0.195090,0.195090,0.555570,0.831470,0.980785,0.980785,0.831470,0.555570,0.195090,-0.195090,-0.555570,-0.831470,-0.980785},
|
||||
{-0.970031,-0.740951,-0.336890,0.146730,0.595699,0.903989,0.998795,0.857729,0.514103,0.049068,-0.427555,-0.803208,-0.989177,-0.941544,-0.671559,-0.242980,
|
||||
0.242980,0.671559,0.941544,0.989177,0.803208,0.427555,-0.049068,-0.514103,-0.857729,-0.998795,-0.903989,-0.595699,-0.146730,0.336890,0.740951,0.970031},
|
||||
{-0.956940,-0.634393,-0.098017,0.471397,0.881921,0.995185,0.773010,0.290285,-0.290285,-0.773010,-0.995185,-0.881921,-0.471397,0.098017,0.634393,0.956940,
|
||||
0.956940,0.634393,0.098017,-0.471397,-0.881921,-0.995185,-0.773010,-0.290285,0.290285,0.773010,0.995185,0.881921,0.471397,-0.098017,-0.634393,-0.956940},
|
||||
{-0.941544,-0.514103,0.146730,0.740951,0.998795,0.803208,0.242980,-0.427555,-0.903989,-0.970031,-0.595699,0.049068,0.671559,0.989177,0.857729,0.336890,
|
||||
-0.336890,-0.857729,-0.989177,-0.671559,-0.049068,0.595699,0.970031,0.903989,0.427555,-0.242980,-0.803208,-0.998795,-0.740951,-0.146730,0.514103,0.941544},
|
||||
{-0.923880,-0.382683,0.382683,0.923880,0.923880,0.382683,-0.382683,-0.923880,-0.923880,-0.382683,0.382683,0.923880,0.923880,0.382683,-0.382683,-0.923880,
|
||||
-0.923880,-0.382683,0.382683,0.923880,0.923880,0.382683,-0.382683,-0.923880,-0.923880,-0.382683,0.382683,0.923880,0.923880,0.382683,-0.382683,-0.923880},
|
||||
{-0.903989,-0.242980,0.595699,0.998795,0.671559,-0.146730,-0.857729,-0.941544,-0.336890,0.514103,0.989177,0.740951,-0.049068,-0.803208,-0.970031,-0.427555,
|
||||
0.427555,0.970031,0.803208,0.049068,-0.740951,-0.989177,-0.514103,0.336890,0.941544,0.857729,0.146730,-0.671559,-0.998795,-0.595699,0.242980,0.903989},
|
||||
{-0.881921,-0.098017,0.773010,0.956940,0.290285,-0.634393,-0.995185,-0.471397,0.471397,0.995185,0.634393,-0.290285,-0.956940,-0.773010,0.098017,0.881921,
|
||||
0.881921,0.098017,-0.773010,-0.956940,-0.290285,0.634393,0.995185,0.471397,-0.471397,-0.995185,-0.634393,0.290285,0.956940,0.773010,-0.098017,-0.881921},
|
||||
{-0.857729,0.049068,0.903989,0.803208,-0.146730,-0.941544,-0.740951,0.242980,0.970031,0.671559,-0.336890,-0.989177,-0.595699,0.427555,0.998795,0.514103,
|
||||
-0.514103,-0.998795,-0.427555,0.595699,0.989177,0.336890,-0.671559,-0.970031,-0.242980,0.740951,0.941544,0.146730,-0.803208,-0.903989,-0.049068,0.857729},
|
||||
{-0.831470,0.195090,0.980785,0.555570,-0.555570,-0.980785,-0.195090,0.831470,0.831470,-0.195090,-0.980785,-0.555570,0.555570,0.980785,0.195090,-0.831470,
|
||||
-0.831470,0.195090,0.980785,0.555570,-0.555570,-0.980785,-0.195090,0.831470,0.831470,-0.195090,-0.980785,-0.555570,0.555570,0.980785,0.195090,-0.831470},
|
||||
{-0.803208,0.336890,0.998795,0.242980,-0.857729,-0.740951,0.427555,0.989177,0.146730,-0.903989,-0.671559,0.514103,0.970031,0.049068,-0.941544,-0.595699,
|
||||
0.595699,0.941544,-0.049068,-0.970031,-0.514103,0.671559,0.903989,-0.146730,-0.989177,-0.427555,0.740951,0.857729,-0.242980,-0.998795,-0.336890,0.803208},
|
||||
{-0.773010,0.471397,0.956940,-0.098017,-0.995185,-0.290285,0.881921,0.634393,-0.634393,-0.881921,0.290285,0.995185,0.098017,-0.956940,-0.471397,0.773010,
|
||||
0.773010,-0.471397,-0.956940,0.098017,0.995185,0.290285,-0.881921,-0.634393,0.634393,0.881921,-0.290285,-0.995185,-0.098017,0.956940,0.471397,-0.773010},
|
||||
{-0.740951,0.595699,0.857729,-0.427555,-0.941544,0.242980,0.989177,-0.049068,-0.998795,-0.146730,0.970031,0.336890,-0.903989,-0.514103,0.803208,0.671559,
|
||||
-0.671559,-0.803208,0.514103,0.903989,-0.336890,-0.970031,0.146730,0.998795,0.049068,-0.989177,-0.242980,0.941544,0.427555,-0.857729,-0.595699,0.740951}
|
||||
};
|
||||
|
||||
// up to 32-bits
|
||||
static unsigned jo_readBits(const unsigned char *data, int *at, int num) {
|
||||
unsigned r = 0;
|
||||
// read partial starting bits
|
||||
int sc = (8 - (*at & 7)) & 7;
|
||||
sc = sc > num ? num : sc;
|
||||
if(sc) {
|
||||
r = (data[*at/8] >> (8 - (*at&7) - sc)) & ((1<<sc)-1);
|
||||
num -= sc;
|
||||
*at += sc;
|
||||
}
|
||||
// read full bytes
|
||||
while(num>=8) {
|
||||
r <<= 8;
|
||||
r |= data[*at/8];
|
||||
*at += 8;
|
||||
num -= 8;
|
||||
}
|
||||
// read partial ending bits
|
||||
if(num) {
|
||||
r <<= num;
|
||||
r |= (data[*at/8] >> (8 - num)) & ((1<<num)-1);
|
||||
*at += num;
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
|
||||
bool jo_read_mp1(const void *input, int inputSize, short **output_, int *outputSize_, int *hz_, int *channels_) {
|
||||
int outputSize = 0, hz, channels;
|
||||
short *output = 0;
|
||||
int outputMax = 0;
|
||||
const unsigned char *data = (const unsigned char *)input;
|
||||
|
||||
// Buffers for the lapped transform
|
||||
double buf[2][2 * 512] = { 0 };
|
||||
int bufOffset[2] = { 64,64 };
|
||||
int at = 0; // Where in the stream are we?
|
||||
while (at < inputSize * 8) {
|
||||
// Sync markers are byte aligned
|
||||
at = (at + 7)&-8;
|
||||
while (at < inputSize * 8 - 32) {
|
||||
if (data[at / 8] == 0xFF && (data[at / 8 + 1] & 0xF0) == 0xF0) {
|
||||
break;
|
||||
}
|
||||
else {
|
||||
at += 8;
|
||||
}
|
||||
}
|
||||
if (at >= inputSize * 8 - 32) {
|
||||
break;
|
||||
}
|
||||
|
||||
unsigned header = jo_readBits(data, &at, 32);
|
||||
//printf("header: %x.%x/%x: %08x\n", (at-32)/8, (at-32)&7, inputSize, header);
|
||||
|
||||
// sync = 0xFFF
|
||||
// ID = 1
|
||||
// layer = 3 (layer 1)
|
||||
if ((header & 0xFFFE0000) != 0xFFFE0000) {
|
||||
return false;
|
||||
}
|
||||
|
||||
static const int bitrateTbl[16] = { 0,32,40,48,56,64,80,96,112,128,160,192,224,256,320,-1 };
|
||||
int kbps = bitrateTbl[(header >> 12) & 15];
|
||||
if (kbps < 0) {
|
||||
return false;
|
||||
}
|
||||
static const int hzTbl[4] = { 44100, 48000, 32000, 0 };
|
||||
hz = hzTbl[(header >> 10) & 3];
|
||||
if (!hz) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// mode 0 = stereo
|
||||
// mode 1 = joint stereo
|
||||
// mode 2 = dual channel (no idea what this does TODO)
|
||||
// mode 3 = mono
|
||||
int mode = (header >> 6) & 3;
|
||||
int modeExt = (header >> 4) & 3;
|
||||
channels = mode == 3 ? 1 : 2;
|
||||
const int bound = mode == 1 ? (modeExt + 1) * 4 : 32;
|
||||
|
||||
bool errorProtection = ((header >> 16) & 1) ^ 1; //< @r-lyeh extra parens
|
||||
if (errorProtection) {
|
||||
at += 16; // skip the CRC.
|
||||
}
|
||||
|
||||
// Read bit allocations
|
||||
int bitAlloc[32][2] = { 0 };
|
||||
for (int i = 0; i < bound; ++i) {
|
||||
for (int ch = 0; ch < channels; ++ch) {
|
||||
bitAlloc[i][ch] = jo_readBits(data, &at, 4);
|
||||
}
|
||||
}
|
||||
for (int i = bound; i < 32; ++i) {
|
||||
bitAlloc[i][1] = bitAlloc[i][0] = jo_readBits(data, &at, 4);
|
||||
}
|
||||
|
||||
// Read scale indexes
|
||||
int scaleIdx[32][2];
|
||||
for (int i = 0; i < 32; ++i) {
|
||||
for (int ch = 0; ch < channels; ++ch) {
|
||||
scaleIdx[i][ch] = bitAlloc[i][ch] ? jo_readBits(data, &at, 6) : 63;
|
||||
}
|
||||
}
|
||||
|
||||
// Read & compute output samples
|
||||
short pcm[12][2][32];
|
||||
for (int s = 0; s < 12; ++s) {
|
||||
// Read normalized, quantized band samples
|
||||
int samples[32][2] = { 0 };
|
||||
for (int i = 0; i < bound; ++i) {
|
||||
for (int ch = 0; ch < channels; ++ch) {
|
||||
if (bitAlloc[i][ch]) {
|
||||
samples[i][ch] = jo_readBits(data, &at, bitAlloc[i][ch] + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (int i = bound; i < 32; ++i) {
|
||||
if (bitAlloc[i][0]) {
|
||||
samples[i][1] = samples[i][0] = jo_readBits(data, &at, bitAlloc[i][0] + 1);
|
||||
}
|
||||
}
|
||||
// Compute bands: Dequantize & Denormalize
|
||||
double bandTbl[2][32] = { 0 };
|
||||
for (int i = 0; i < 32; ++i) {
|
||||
for (int ch = 0; ch < channels; ++ch) {
|
||||
int b = bitAlloc[i][ch];
|
||||
if (b++) {
|
||||
int samp = samples[i][ch];
|
||||
double f = ((samp >> (b - 1)) & 1) ? 0 : -1;
|
||||
f += (samp & ((1 << (b - 1)) - 1)) / (double)(1 << (b - 1));
|
||||
f = (f + 1.0 / (1 << (b - 1))) * (1 << b) / ((1 << b) - 1.0);
|
||||
f *= s_jo_multTbl[scaleIdx[i][ch]];
|
||||
bandTbl[ch][i] = f;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Convert subbands to PCM
|
||||
for (int ch = 0; ch < channels; ++ch) {
|
||||
bufOffset[ch] = (bufOffset[ch] + 0x3C0) & 0x3ff;
|
||||
double *bufOffsetPtr = buf[ch] + bufOffset[ch];
|
||||
const double *f = s_jo_filterTbl[0];
|
||||
for (int i = 0; i < 64; ++i) {
|
||||
double sum = 0;
|
||||
for (int j = 0; j < 32; ++j) {
|
||||
sum += *f++ * bandTbl[ch][j];
|
||||
}
|
||||
bufOffsetPtr[i] = sum;
|
||||
}
|
||||
const double *w = s_jo_windowTbl;
|
||||
for (int i = 0; i < 32; ++i) {
|
||||
double sum = 0;
|
||||
for (int j = 0; j < 16; ++j) {
|
||||
int k = i | (j + (j + 1 & -2)) << 5;
|
||||
sum += *w++ * buf[ch][(k + bufOffset[ch]) & 0x3ff];
|
||||
}
|
||||
int ss = (int)(sum * 0x8000);
|
||||
ss = ss > SHRT_MAX ? SHRT_MAX : ss < SHRT_MIN ? SHRT_MIN : ss;
|
||||
pcm[s][ch][i] = ss;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (at > inputSize * 8) {
|
||||
printf("file corruption?\n");
|
||||
return false;
|
||||
}
|
||||
if (outputMax == 0) {
|
||||
// estimate total number of samples (may be totally wrong, but its better than nothing)
|
||||
at = (at + 7)&-8;
|
||||
outputMax = inputSize / (at / 8) * 384 * channels * sizeof(*output);
|
||||
output = (short*)REALLOC(output, outputMax);
|
||||
}
|
||||
if (outputSize * sizeof(*output) + 384 * channels * sizeof(*output) > outputMax) {
|
||||
outputMax += 384 * channels * sizeof(*output);
|
||||
output = (short*)REALLOC(output, outputMax);
|
||||
}
|
||||
for (int i = 0; i < 12; ++i) {
|
||||
for (int j = 0; j < 32; ++j) {
|
||||
for (int k = 0; k < channels; ++k) {
|
||||
output[outputSize++] = pcm[i][k][j];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
*outputSize_ = outputSize;
|
||||
*hz_ = hz;
|
||||
*channels_ = channels;
|
||||
*output_ = output;
|
||||
|
||||
return outputSize && hz && channels && output;
|
||||
}
|
||||
|
||||
#endif // JO_MP1_HEADER_FILE_ONLY
|
||||
|
||||
|
|
|
@ -1,264 +1,264 @@
|
|||
/* public domain Simple, Minimalistic, No Allocations MPEG writer - http://jonolick.com
|
||||
*
|
||||
* Latest revisions:
|
||||
* 1.02c rgbx -> bgrx channel swap && vertical image flip && userdef components (@r-lyeh)
|
||||
* 1.02 (22-03-2017) Fixed AC encoding bug.
|
||||
* Fixed color space bug (thx r- lyeh!)
|
||||
* 1.01 (18-10-2016) warning fixes
|
||||
* 1.00 (25-09-2016) initial release
|
||||
*
|
||||
* Basic usage:
|
||||
* char *frame = new char[width*height*4]; // 4 component. bgrx format, where X is unused
|
||||
* FILE *fp = fopen("foo.mpg", "wb");
|
||||
* jo_write_mpeg(fp, frame, width, height, 60); // frame 0
|
||||
* jo_write_mpeg(fp, frame, width, height, 60); // frame 1
|
||||
* jo_write_mpeg(fp, frame, width, height, 60); // frame 2
|
||||
* ...
|
||||
* fclose(fp);
|
||||
*
|
||||
* Notes:
|
||||
* Only supports 24, 25, 30, 50, or 60 fps
|
||||
*
|
||||
* I don't know if decoders support changing of fps, or dimensions for each frame.
|
||||
* Movie players *should* support it as the spec allows it, but ...
|
||||
*
|
||||
* MPEG-1/2 currently has no active patents as far as I am aware.
|
||||
*
|
||||
* http://dvd.sourceforge.net/dvdinfo/mpeghdrs.html
|
||||
* http://www.cs.cornell.edu/dali/api/mpegvideo-c.html
|
||||
* */
|
||||
|
||||
#ifndef JO_INCLUDE_MPEG_H
|
||||
#define JO_INCLUDE_MPEG_H
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
// To get a header file for this, either cut and paste the header,
|
||||
// or create jo_mpeg.h, #define JO_MPEG_HEADER_FILE_ONLY, and
|
||||
// then include jo_mpeg.c from it.
|
||||
|
||||
// Returns false on failure
|
||||
extern void jo_write_mpeg(FILE *fp, const unsigned char *bgrx, int width, int height, int fps);
|
||||
|
||||
#endif // JO_INCLUDE_MPEG_H
|
||||
|
||||
#ifndef JO_MPEG_HEADER_FILE_ONLY
|
||||
|
||||
#ifndef JO_MPEG_COMPONENTS
|
||||
#define JO_MPEG_COMPONENTS 4
|
||||
#endif
|
||||
|
||||
#include <stdio.h>
|
||||
#include <math.h>
|
||||
#include <memory.h>
|
||||
|
||||
// Huffman tables
|
||||
static const unsigned char s_jo_HTDC_Y[9][2] = {{4,3}, {0,2}, {1,2}, {5,3}, {6,3}, {14,4}, {30,5}, {62,6}, {126,7}};
|
||||
static const unsigned char s_jo_HTDC_C[9][2] = {{0,2}, {1,2}, {2,2}, {6,3}, {14,4}, {30,5}, {62,6}, {126,7}, {254,8}};
|
||||
static const unsigned char s_jo_HTAC[32][40][2] = {
|
||||
{{6,3},{8,5},{10,6},{12,8},{76,9},{66,9},{20,11},{58,13},{48,13},{38,13},{32,13},{52,14},{50,14},{48,14},{46,14},{62,15},{62,15},{58,15},{56,15},{54,15},{52,15},{50,15},{48,15},{46,15},{44,15},{42,15},{40,15},{38,15},{36,15},{34,15},{32,15},{48,16},{46,16},{44,16},{42,16},{40,16},{38,16},{36,16},{34,16},{32,16},},
|
||||
{{6,4},{12,7},{74,9},{24,11},{54,13},{44,14},{42,14},{62,16},{60,16},{58,16},{56,16},{54,16},{52,16},{50,16},{38,17},{36,17},{34,17},{32,17}},
|
||||
{{10,5},{8,8},{22,11},{40,13},{40,14}},
|
||||
{{14,6},{72,9},{56,13},{38,14}},
|
||||
{{12,6},{30,11},{36,13}}, {{14,7},{18,11},{36,14}}, {{10,7},{60,13},{40,17}},
|
||||
{{8,7},{42,13}}, {{14,8},{34,13}}, {{10,8},{34,14}}, {{78,9},{32,14}}, {{70,9},{52,17}}, {{68,9},{50,17}}, {{64,9},{48,17}}, {{28,11},{46,17}}, {{26,11},{44,17}}, {{16,11},{42,17}},
|
||||
{{62,13}}, {{52,13}}, {{50,13}}, {{46,13}}, {{44,13}}, {{62,14}}, {{60,14}}, {{58,14}}, {{56,14}}, {{54,14}}, {{62,17}}, {{60,17}}, {{58,17}}, {{56,17}}, {{54,17}},
|
||||
};
|
||||
static const float s_jo_quantTbl[64] = {
|
||||
0.015625f,0.005632f,0.005035f,0.004832f,0.004808f,0.005892f,0.007964f,0.013325f,
|
||||
0.005632f,0.004061f,0.003135f,0.003193f,0.003338f,0.003955f,0.004898f,0.008828f,
|
||||
0.005035f,0.003135f,0.002816f,0.003013f,0.003299f,0.003581f,0.005199f,0.009125f,
|
||||
0.004832f,0.003484f,0.003129f,0.003348f,0.003666f,0.003979f,0.005309f,0.009632f,
|
||||
0.005682f,0.003466f,0.003543f,0.003666f,0.003906f,0.004546f,0.005774f,0.009439f,
|
||||
0.006119f,0.004248f,0.004199f,0.004228f,0.004546f,0.005062f,0.006124f,0.009942f,
|
||||
0.008883f,0.006167f,0.006096f,0.005777f,0.006078f,0.006391f,0.007621f,0.012133f,
|
||||
0.016780f,0.011263f,0.009907f,0.010139f,0.009849f,0.010297f,0.012133f,0.019785f,
|
||||
};
|
||||
static const unsigned char s_jo_ZigZag[] = { 0,1,5,6,14,15,27,28,2,4,7,13,16,26,29,42,3,8,12,17,25,30,41,43,9,11,18,24,31,40,44,53,10,19,23,32,39,45,52,54,20,22,33,38,46,51,55,60,21,34,37,47,50,56,59,61,35,36,48,49,57,58,62,63 };
|
||||
|
||||
typedef struct {
|
||||
FILE *fp;
|
||||
int buf, cnt;
|
||||
} jo_bits_t;
|
||||
|
||||
static void jo_writeBits(jo_bits_t *b, int value, int count) {
|
||||
b->cnt += count;
|
||||
b->buf |= value << (24 - b->cnt);
|
||||
while(b->cnt >= 8) {
|
||||
unsigned char c = (b->buf >> 16) & 255;
|
||||
putc(c, b->fp);
|
||||
b->buf <<= 8;
|
||||
b->cnt -= 8;
|
||||
}
|
||||
}
|
||||
|
||||
static void jo_DCT(float *d0, float *d1, float *d2, float *d3, float *d4, float *d5, float *d6, float *d7) {
|
||||
float tmp0 = *d0 + *d7;
|
||||
float tmp7 = *d0 - *d7;
|
||||
float tmp1 = *d1 + *d6;
|
||||
float tmp6 = *d1 - *d6;
|
||||
float tmp2 = *d2 + *d5;
|
||||
float tmp5 = *d2 - *d5;
|
||||
float tmp3 = *d3 + *d4;
|
||||
float tmp4 = *d3 - *d4;
|
||||
|
||||
// Even part
|
||||
float tmp10 = tmp0 + tmp3; // phase 2
|
||||
float tmp13 = tmp0 - tmp3;
|
||||
float tmp11 = tmp1 + tmp2;
|
||||
float tmp12 = tmp1 - tmp2;
|
||||
|
||||
*d0 = tmp10 + tmp11; // phase 3
|
||||
*d4 = tmp10 - tmp11;
|
||||
|
||||
float z1 = (tmp12 + tmp13) * 0.707106781f; // c4
|
||||
*d2 = tmp13 + z1; // phase 5
|
||||
*d6 = tmp13 - z1;
|
||||
|
||||
// Odd part
|
||||
tmp10 = tmp4 + tmp5; // phase 2
|
||||
tmp11 = tmp5 + tmp6;
|
||||
tmp12 = tmp6 + tmp7;
|
||||
|
||||
// The rotator is modified from fig 4-8 to avoid extra negations.
|
||||
float z5 = (tmp10 - tmp12) * 0.382683433f; // c6
|
||||
float z2 = tmp10 * 0.541196100f + z5; // c2-c6
|
||||
float z4 = tmp12 * 1.306562965f + z5; // c2+c6
|
||||
float z3 = tmp11 * 0.707106781f; // c4
|
||||
|
||||
float z11 = tmp7 + z3; // phase 5
|
||||
float z13 = tmp7 - z3;
|
||||
|
||||
*d5 = z13 + z2; // phase 6
|
||||
*d3 = z13 - z2;
|
||||
*d1 = z11 + z4;
|
||||
*d7 = z11 - z4;
|
||||
}
|
||||
|
||||
static int jo_processDU(jo_bits_t *bits, float A[64], const unsigned char htdc[9][2], int DC) {
|
||||
for(int dataOff=0; dataOff<64; dataOff+=8) {
|
||||
jo_DCT(&A[dataOff], &A[dataOff+1], &A[dataOff+2], &A[dataOff+3], &A[dataOff+4], &A[dataOff+5], &A[dataOff+6], &A[dataOff+7]);
|
||||
}
|
||||
for(int dataOff=0; dataOff<8; ++dataOff) {
|
||||
jo_DCT(&A[dataOff], &A[dataOff+8], &A[dataOff+16], &A[dataOff+24], &A[dataOff+32], &A[dataOff+40], &A[dataOff+48], &A[dataOff+56]);
|
||||
}
|
||||
int Q[64];
|
||||
for(int i=0; i<64; ++i) {
|
||||
float v = A[i]*s_jo_quantTbl[i];
|
||||
Q[s_jo_ZigZag[i]] = (int)(v < 0 ? ceilf(v - 0.5f) : floorf(v + 0.5f));
|
||||
}
|
||||
|
||||
DC = Q[0] - DC;
|
||||
int aDC = DC < 0 ? -DC : DC;
|
||||
int size = 0;
|
||||
int tempval = aDC;
|
||||
while(tempval) {
|
||||
size++;
|
||||
tempval >>= 1;
|
||||
}
|
||||
jo_writeBits(bits, htdc[size][0], htdc[size][1]);
|
||||
if(DC < 0) aDC ^= (1 << size) - 1;
|
||||
jo_writeBits(bits, aDC, size);
|
||||
|
||||
int endpos = 63;
|
||||
for(; (endpos>0)&&(Q[endpos]==0); --endpos) { /* do nothing */ }
|
||||
for(int i = 1; i <= endpos;) {
|
||||
int run = 0;
|
||||
while (Q[i]==0 && i<endpos) {
|
||||
++run;
|
||||
++i;
|
||||
}
|
||||
int AC = Q[i++];
|
||||
int aAC = AC < 0 ? -AC : AC;
|
||||
int code = 0, size = 0;
|
||||
if (run<32 && aAC<=40) {
|
||||
code = s_jo_HTAC[run][aAC-1][0];
|
||||
size = s_jo_HTAC[run][aAC-1][1];
|
||||
if (AC < 0) code += 1;
|
||||
}
|
||||
if(!size) {
|
||||
jo_writeBits(bits, 1, 6);
|
||||
jo_writeBits(bits, run, 6);
|
||||
if (AC < -127) {
|
||||
jo_writeBits(bits, 128, 8);
|
||||
} else if(AC > 127) {
|
||||
jo_writeBits(bits, 0, 8);
|
||||
}
|
||||
code = AC&255;
|
||||
size = 8;
|
||||
}
|
||||
jo_writeBits(bits, code, size);
|
||||
}
|
||||
jo_writeBits(bits, 2, 2);
|
||||
|
||||
return Q[0];
|
||||
}
|
||||
|
||||
void jo_write_mpeg(FILE *fp, const unsigned char *bgrx, int width, int height, int fps) {
|
||||
int lastDCY = 128, lastDCCR = 128, lastDCCB = 128;
|
||||
jo_bits_t bits = {fp};
|
||||
|
||||
// Sequence Header
|
||||
fwrite("\x00\x00\x01\xB3", 4, 1, fp);
|
||||
// 12 bits for width, height
|
||||
putc((width>>4)&0xFF, fp);
|
||||
putc(((width&0xF)<<4) | ((height>>8) & 0xF), fp);
|
||||
putc(height & 0xFF, fp);
|
||||
// aspect ratio, framerate
|
||||
if(fps <= 24) putc(0x12, fp);
|
||||
else if(fps <= 25) putc(0x13, fp);
|
||||
else if(fps <= 30) putc(0x15, fp);
|
||||
else if(fps <= 50) putc(0x16, fp);
|
||||
else putc(0x18, fp); // 60fps
|
||||
fwrite("\xFF\xFF\xE0\xA0", 4, 1, fp);
|
||||
|
||||
fwrite("\x00\x00\x01\xB8\x80\x08\x00\x40", 8, 1, fp); // GOP header
|
||||
fwrite("\x00\x00\x01\x00\x00\x0C\x00\x00", 8, 1, fp); // PIC header
|
||||
fwrite("\x00\x00\x01\x01", 4, 1, fp); // Slice header
|
||||
jo_writeBits(&bits, 0x10, 6);
|
||||
|
||||
for (int vblock=0; vblock<(height+15)/16; vblock++) {
|
||||
for (int hblock=0; hblock<(width+15)/16; hblock++) {
|
||||
jo_writeBits(&bits, 3, 2);
|
||||
|
||||
float Y[256], CBx[256], CRx[256];
|
||||
for (int i=0; i<256; ++i) {
|
||||
int y = vblock*16+(i/16);
|
||||
int x = hblock*16+(i&15);
|
||||
x = x >= width ? width-1 : x;
|
||||
y = y >= height ? height-1 : y;
|
||||
int _4 = JO_MPEG_COMPONENTS;
|
||||
// const unsigned char *c = bgrx + y*width*_4+x*_4; // original
|
||||
const unsigned char *c = bgrx + ((height-1)-y)*width*_4+x*_4; // flipped
|
||||
float b = c[0], g = c[1], r = c[2]; // channel swap
|
||||
Y[i] = ( 0.299f*r + 0.587f*g + 0.114f*b) * (219.f/255) + 16;
|
||||
CBx[i] = (-0.299f*r - 0.587f*g + 0.886f*b) * (224.f/255) + 128;
|
||||
CRx[i] = ( 0.701f*r - 0.587f*g - 0.114f*b) * (224.f/255) + 128;
|
||||
}
|
||||
|
||||
// Downsample Cb,Cr (420 format)
|
||||
float CB[64], CR[64];
|
||||
for (int i=0; i<64; ++i) {
|
||||
int j =(i&7)*2 + (i&56)*4;
|
||||
CB[i] = (CBx[j] + CBx[j+1] + CBx[j+16] + CBx[j+17]) * 0.25f;
|
||||
CR[i] = (CRx[j] + CRx[j+1] + CRx[j+16] + CRx[j+17]) * 0.25f;
|
||||
}
|
||||
|
||||
for (int k1=0; k1<2; ++k1) {
|
||||
for (int k2=0; k2<2; ++k2) {
|
||||
float block[64];
|
||||
for (int i=0; i<64; i+=8) {
|
||||
int j = (i&7)+(i&56)*2 + k1*8*16 + k2*8;
|
||||
memcpy(block+i, Y+j, 8*sizeof(Y[0]));
|
||||
}
|
||||
lastDCY = jo_processDU(&bits, block, s_jo_HTDC_Y, lastDCY);
|
||||
}
|
||||
}
|
||||
lastDCCB = jo_processDU(&bits, CB, s_jo_HTDC_C, lastDCCB);
|
||||
lastDCCR = jo_processDU(&bits, CR, s_jo_HTDC_C, lastDCCR);
|
||||
}
|
||||
}
|
||||
jo_writeBits(&bits, 0, 7);
|
||||
fwrite("\x00\x00\x01\xb7", 4, 1, fp); // End of Sequence
|
||||
}
|
||||
#endif
|
||||
/* public domain Simple, Minimalistic, No Allocations MPEG writer - http://jonolick.com
|
||||
*
|
||||
* Latest revisions:
|
||||
* 1.02c rgbx -> bgrx channel swap && vertical image flip && userdef components (@r-lyeh)
|
||||
* 1.02 (22-03-2017) Fixed AC encoding bug.
|
||||
* Fixed color space bug (thx r- lyeh!)
|
||||
* 1.01 (18-10-2016) warning fixes
|
||||
* 1.00 (25-09-2016) initial release
|
||||
*
|
||||
* Basic usage:
|
||||
* char *frame = new char[width*height*4]; // 4 component. bgrx format, where X is unused
|
||||
* FILE *fp = fopen("foo.mpg", "wb");
|
||||
* jo_write_mpeg(fp, frame, width, height, 60); // frame 0
|
||||
* jo_write_mpeg(fp, frame, width, height, 60); // frame 1
|
||||
* jo_write_mpeg(fp, frame, width, height, 60); // frame 2
|
||||
* ...
|
||||
* fclose(fp);
|
||||
*
|
||||
* Notes:
|
||||
* Only supports 24, 25, 30, 50, or 60 fps
|
||||
*
|
||||
* I don't know if decoders support changing of fps, or dimensions for each frame.
|
||||
* Movie players *should* support it as the spec allows it, but ...
|
||||
*
|
||||
* MPEG-1/2 currently has no active patents as far as I am aware.
|
||||
*
|
||||
* http://dvd.sourceforge.net/dvdinfo/mpeghdrs.html
|
||||
* http://www.cs.cornell.edu/dali/api/mpegvideo-c.html
|
||||
* */
|
||||
|
||||
#ifndef JO_INCLUDE_MPEG_H
|
||||
#define JO_INCLUDE_MPEG_H
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
// To get a header file for this, either cut and paste the header,
|
||||
// or create jo_mpeg.h, #define JO_MPEG_HEADER_FILE_ONLY, and
|
||||
// then include jo_mpeg.c from it.
|
||||
|
||||
// Returns false on failure
|
||||
extern void jo_write_mpeg(FILE *fp, const unsigned char *bgrx, int width, int height, int fps);
|
||||
|
||||
#endif // JO_INCLUDE_MPEG_H
|
||||
|
||||
#ifndef JO_MPEG_HEADER_FILE_ONLY
|
||||
|
||||
#ifndef JO_MPEG_COMPONENTS
|
||||
#define JO_MPEG_COMPONENTS 4
|
||||
#endif
|
||||
|
||||
#include <stdio.h>
|
||||
#include <math.h>
|
||||
#include <memory.h>
|
||||
|
||||
// Huffman tables
|
||||
static const unsigned char s_jo_HTDC_Y[9][2] = {{4,3}, {0,2}, {1,2}, {5,3}, {6,3}, {14,4}, {30,5}, {62,6}, {126,7}};
|
||||
static const unsigned char s_jo_HTDC_C[9][2] = {{0,2}, {1,2}, {2,2}, {6,3}, {14,4}, {30,5}, {62,6}, {126,7}, {254,8}};
|
||||
static const unsigned char s_jo_HTAC[32][40][2] = {
|
||||
{{6,3},{8,5},{10,6},{12,8},{76,9},{66,9},{20,11},{58,13},{48,13},{38,13},{32,13},{52,14},{50,14},{48,14},{46,14},{62,15},{62,15},{58,15},{56,15},{54,15},{52,15},{50,15},{48,15},{46,15},{44,15},{42,15},{40,15},{38,15},{36,15},{34,15},{32,15},{48,16},{46,16},{44,16},{42,16},{40,16},{38,16},{36,16},{34,16},{32,16},},
|
||||
{{6,4},{12,7},{74,9},{24,11},{54,13},{44,14},{42,14},{62,16},{60,16},{58,16},{56,16},{54,16},{52,16},{50,16},{38,17},{36,17},{34,17},{32,17}},
|
||||
{{10,5},{8,8},{22,11},{40,13},{40,14}},
|
||||
{{14,6},{72,9},{56,13},{38,14}},
|
||||
{{12,6},{30,11},{36,13}}, {{14,7},{18,11},{36,14}}, {{10,7},{60,13},{40,17}},
|
||||
{{8,7},{42,13}}, {{14,8},{34,13}}, {{10,8},{34,14}}, {{78,9},{32,14}}, {{70,9},{52,17}}, {{68,9},{50,17}}, {{64,9},{48,17}}, {{28,11},{46,17}}, {{26,11},{44,17}}, {{16,11},{42,17}},
|
||||
{{62,13}}, {{52,13}}, {{50,13}}, {{46,13}}, {{44,13}}, {{62,14}}, {{60,14}}, {{58,14}}, {{56,14}}, {{54,14}}, {{62,17}}, {{60,17}}, {{58,17}}, {{56,17}}, {{54,17}},
|
||||
};
|
||||
static const float s_jo_quantTbl[64] = {
|
||||
0.015625f,0.005632f,0.005035f,0.004832f,0.004808f,0.005892f,0.007964f,0.013325f,
|
||||
0.005632f,0.004061f,0.003135f,0.003193f,0.003338f,0.003955f,0.004898f,0.008828f,
|
||||
0.005035f,0.003135f,0.002816f,0.003013f,0.003299f,0.003581f,0.005199f,0.009125f,
|
||||
0.004832f,0.003484f,0.003129f,0.003348f,0.003666f,0.003979f,0.005309f,0.009632f,
|
||||
0.005682f,0.003466f,0.003543f,0.003666f,0.003906f,0.004546f,0.005774f,0.009439f,
|
||||
0.006119f,0.004248f,0.004199f,0.004228f,0.004546f,0.005062f,0.006124f,0.009942f,
|
||||
0.008883f,0.006167f,0.006096f,0.005777f,0.006078f,0.006391f,0.007621f,0.012133f,
|
||||
0.016780f,0.011263f,0.009907f,0.010139f,0.009849f,0.010297f,0.012133f,0.019785f,
|
||||
};
|
||||
static const unsigned char s_jo_ZigZag[] = { 0,1,5,6,14,15,27,28,2,4,7,13,16,26,29,42,3,8,12,17,25,30,41,43,9,11,18,24,31,40,44,53,10,19,23,32,39,45,52,54,20,22,33,38,46,51,55,60,21,34,37,47,50,56,59,61,35,36,48,49,57,58,62,63 };
|
||||
|
||||
typedef struct {
|
||||
FILE *fp;
|
||||
int buf, cnt;
|
||||
} jo_bits_t;
|
||||
|
||||
static void jo_writeBits(jo_bits_t *b, int value, int count) {
|
||||
b->cnt += count;
|
||||
b->buf |= value << (24 - b->cnt);
|
||||
while(b->cnt >= 8) {
|
||||
unsigned char c = (b->buf >> 16) & 255;
|
||||
putc(c, b->fp);
|
||||
b->buf <<= 8;
|
||||
b->cnt -= 8;
|
||||
}
|
||||
}
|
||||
|
||||
static void jo_DCT(float *d0, float *d1, float *d2, float *d3, float *d4, float *d5, float *d6, float *d7) {
|
||||
float tmp0 = *d0 + *d7;
|
||||
float tmp7 = *d0 - *d7;
|
||||
float tmp1 = *d1 + *d6;
|
||||
float tmp6 = *d1 - *d6;
|
||||
float tmp2 = *d2 + *d5;
|
||||
float tmp5 = *d2 - *d5;
|
||||
float tmp3 = *d3 + *d4;
|
||||
float tmp4 = *d3 - *d4;
|
||||
|
||||
// Even part
|
||||
float tmp10 = tmp0 + tmp3; // phase 2
|
||||
float tmp13 = tmp0 - tmp3;
|
||||
float tmp11 = tmp1 + tmp2;
|
||||
float tmp12 = tmp1 - tmp2;
|
||||
|
||||
*d0 = tmp10 + tmp11; // phase 3
|
||||
*d4 = tmp10 - tmp11;
|
||||
|
||||
float z1 = (tmp12 + tmp13) * 0.707106781f; // c4
|
||||
*d2 = tmp13 + z1; // phase 5
|
||||
*d6 = tmp13 - z1;
|
||||
|
||||
// Odd part
|
||||
tmp10 = tmp4 + tmp5; // phase 2
|
||||
tmp11 = tmp5 + tmp6;
|
||||
tmp12 = tmp6 + tmp7;
|
||||
|
||||
// The rotator is modified from fig 4-8 to avoid extra negations.
|
||||
float z5 = (tmp10 - tmp12) * 0.382683433f; // c6
|
||||
float z2 = tmp10 * 0.541196100f + z5; // c2-c6
|
||||
float z4 = tmp12 * 1.306562965f + z5; // c2+c6
|
||||
float z3 = tmp11 * 0.707106781f; // c4
|
||||
|
||||
float z11 = tmp7 + z3; // phase 5
|
||||
float z13 = tmp7 - z3;
|
||||
|
||||
*d5 = z13 + z2; // phase 6
|
||||
*d3 = z13 - z2;
|
||||
*d1 = z11 + z4;
|
||||
*d7 = z11 - z4;
|
||||
}
|
||||
|
||||
static int jo_processDU(jo_bits_t *bits, float A[64], const unsigned char htdc[9][2], int DC) {
|
||||
for(int dataOff=0; dataOff<64; dataOff+=8) {
|
||||
jo_DCT(&A[dataOff], &A[dataOff+1], &A[dataOff+2], &A[dataOff+3], &A[dataOff+4], &A[dataOff+5], &A[dataOff+6], &A[dataOff+7]);
|
||||
}
|
||||
for(int dataOff=0; dataOff<8; ++dataOff) {
|
||||
jo_DCT(&A[dataOff], &A[dataOff+8], &A[dataOff+16], &A[dataOff+24], &A[dataOff+32], &A[dataOff+40], &A[dataOff+48], &A[dataOff+56]);
|
||||
}
|
||||
int Q[64];
|
||||
for(int i=0; i<64; ++i) {
|
||||
float v = A[i]*s_jo_quantTbl[i];
|
||||
Q[s_jo_ZigZag[i]] = (int)(v < 0 ? ceilf(v - 0.5f) : floorf(v + 0.5f));
|
||||
}
|
||||
|
||||
DC = Q[0] - DC;
|
||||
int aDC = DC < 0 ? -DC : DC;
|
||||
int size = 0;
|
||||
int tempval = aDC;
|
||||
while(tempval) {
|
||||
size++;
|
||||
tempval >>= 1;
|
||||
}
|
||||
jo_writeBits(bits, htdc[size][0], htdc[size][1]);
|
||||
if(DC < 0) aDC ^= (1 << size) - 1;
|
||||
jo_writeBits(bits, aDC, size);
|
||||
|
||||
int endpos = 63;
|
||||
for(; (endpos>0)&&(Q[endpos]==0); --endpos) { /* do nothing */ }
|
||||
for(int i = 1; i <= endpos;) {
|
||||
int run = 0;
|
||||
while (Q[i]==0 && i<endpos) {
|
||||
++run;
|
||||
++i;
|
||||
}
|
||||
int AC = Q[i++];
|
||||
int aAC = AC < 0 ? -AC : AC;
|
||||
int code = 0, size = 0;
|
||||
if (run<32 && aAC<=40) {
|
||||
code = s_jo_HTAC[run][aAC-1][0];
|
||||
size = s_jo_HTAC[run][aAC-1][1];
|
||||
if (AC < 0) code += 1;
|
||||
}
|
||||
if(!size) {
|
||||
jo_writeBits(bits, 1, 6);
|
||||
jo_writeBits(bits, run, 6);
|
||||
if (AC < -127) {
|
||||
jo_writeBits(bits, 128, 8);
|
||||
} else if(AC > 127) {
|
||||
jo_writeBits(bits, 0, 8);
|
||||
}
|
||||
code = AC&255;
|
||||
size = 8;
|
||||
}
|
||||
jo_writeBits(bits, code, size);
|
||||
}
|
||||
jo_writeBits(bits, 2, 2);
|
||||
|
||||
return Q[0];
|
||||
}
|
||||
|
||||
void jo_write_mpeg(FILE *fp, const unsigned char *bgrx, int width, int height, int fps) {
|
||||
int lastDCY = 128, lastDCCR = 128, lastDCCB = 128;
|
||||
jo_bits_t bits = {fp};
|
||||
|
||||
// Sequence Header
|
||||
fwrite("\x00\x00\x01\xB3", 4, 1, fp);
|
||||
// 12 bits for width, height
|
||||
putc((width>>4)&0xFF, fp);
|
||||
putc(((width&0xF)<<4) | ((height>>8) & 0xF), fp);
|
||||
putc(height & 0xFF, fp);
|
||||
// aspect ratio, framerate
|
||||
if(fps <= 24) putc(0x12, fp);
|
||||
else if(fps <= 25) putc(0x13, fp);
|
||||
else if(fps <= 30) putc(0x15, fp);
|
||||
else if(fps <= 50) putc(0x16, fp);
|
||||
else putc(0x18, fp); // 60fps
|
||||
fwrite("\xFF\xFF\xE0\xA0", 4, 1, fp);
|
||||
|
||||
fwrite("\x00\x00\x01\xB8\x80\x08\x00\x40", 8, 1, fp); // GOP header
|
||||
fwrite("\x00\x00\x01\x00\x00\x0C\x00\x00", 8, 1, fp); // PIC header
|
||||
fwrite("\x00\x00\x01\x01", 4, 1, fp); // Slice header
|
||||
jo_writeBits(&bits, 0x10, 6);
|
||||
|
||||
for (int vblock=0; vblock<(height+15)/16; vblock++) {
|
||||
for (int hblock=0; hblock<(width+15)/16; hblock++) {
|
||||
jo_writeBits(&bits, 3, 2);
|
||||
|
||||
float Y[256], CBx[256], CRx[256];
|
||||
for (int i=0; i<256; ++i) {
|
||||
int y = vblock*16+(i/16);
|
||||
int x = hblock*16+(i&15);
|
||||
x = x >= width ? width-1 : x;
|
||||
y = y >= height ? height-1 : y;
|
||||
int _4 = JO_MPEG_COMPONENTS;
|
||||
// const unsigned char *c = bgrx + y*width*_4+x*_4; // original
|
||||
const unsigned char *c = bgrx + ((height-1)-y)*width*_4+x*_4; // flipped
|
||||
float b = c[0], g = c[1], r = c[2]; // channel swap
|
||||
Y[i] = ( 0.299f*r + 0.587f*g + 0.114f*b) * (219.f/255) + 16;
|
||||
CBx[i] = (-0.299f*r - 0.587f*g + 0.886f*b) * (224.f/255) + 128;
|
||||
CRx[i] = ( 0.701f*r - 0.587f*g - 0.114f*b) * (224.f/255) + 128;
|
||||
}
|
||||
|
||||
// Downsample Cb,Cr (420 format)
|
||||
float CB[64], CR[64];
|
||||
for (int i=0; i<64; ++i) {
|
||||
int j =(i&7)*2 + (i&56)*4;
|
||||
CB[i] = (CBx[j] + CBx[j+1] + CBx[j+16] + CBx[j+17]) * 0.25f;
|
||||
CR[i] = (CRx[j] + CRx[j+1] + CRx[j+16] + CRx[j+17]) * 0.25f;
|
||||
}
|
||||
|
||||
for (int k1=0; k1<2; ++k1) {
|
||||
for (int k2=0; k2<2; ++k2) {
|
||||
float block[64];
|
||||
for (int i=0; i<64; i+=8) {
|
||||
int j = (i&7)+(i&56)*2 + k1*8*16 + k2*8;
|
||||
memcpy(block+i, Y+j, 8*sizeof(Y[0]));
|
||||
}
|
||||
lastDCY = jo_processDU(&bits, block, s_jo_HTDC_Y, lastDCY);
|
||||
}
|
||||
}
|
||||
lastDCCB = jo_processDU(&bits, CB, s_jo_HTDC_C, lastDCCB);
|
||||
lastDCCR = jo_processDU(&bits, CR, s_jo_HTDC_C, lastDCCR);
|
||||
}
|
||||
}
|
||||
jo_writeBits(&bits, 0, 7);
|
||||
fwrite("\x00\x00\x01\xb7", 4, 1, fp); // End of Sequence
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -1,440 +1,440 @@
|
|||
// JSON5 + SJSON parser module
|
||||
//
|
||||
// License:
|
||||
// This software is dual-licensed to the public domain and under the following
|
||||
// license: you are granted a perpetual, irrevocable license to copy, modify,
|
||||
// publish, and distribute this file as you see fit.
|
||||
// No warranty is implied, use at your own risk.
|
||||
//
|
||||
// Credits:
|
||||
// r-lyeh (fork)
|
||||
// Dominik Madarasz (@zaklaus) (original code)
|
||||
|
||||
#ifndef JSON5_H
|
||||
#define JSON5_H
|
||||
|
||||
#ifndef JSON5_ASSERT
|
||||
#define JSON5_ASSERT do { printf("JSON5: Error L%d while parsing '%c' in '%.16s'\n", __LINE__, p[0], p); assert(0); } while(0)
|
||||
#endif
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
|
||||
typedef enum json5_type {
|
||||
JSON5_UNDEFINED, // 0
|
||||
JSON5_NULL, // 1
|
||||
JSON5_BOOL, // 2
|
||||
JSON5_OBJECT, // 3
|
||||
JSON5_STRING, // 4
|
||||
JSON5_ARRAY, // 5
|
||||
JSON5_INTEGER, // 6
|
||||
JSON5_REAL, // 7
|
||||
} json5_type;
|
||||
|
||||
typedef struct json5 {
|
||||
char* name;
|
||||
#ifdef NDEBUG
|
||||
unsigned type : 3;
|
||||
#else
|
||||
json5_type type;
|
||||
#endif
|
||||
unsigned count : 29;
|
||||
union {
|
||||
struct json5* array;
|
||||
struct json5* nodes;
|
||||
int64_t integer;
|
||||
double real;
|
||||
char* string;
|
||||
int boolean;
|
||||
};
|
||||
} json5;
|
||||
|
||||
char* json5_parse(json5 *root, char *source, int flags);
|
||||
void json5_write(FILE *fp, const json5 *root);
|
||||
void json5_free(json5 *root);
|
||||
|
||||
#endif // JSON5_H
|
||||
|
||||
// json5 ----------------------------------------------------------------------
|
||||
|
||||
#ifdef JSON5_C
|
||||
//#pragma once
|
||||
#include <assert.h>
|
||||
#include <ctype.h>
|
||||
#include <math.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
char *json5__trim(char *p) {
|
||||
while (*p) {
|
||||
/**/ if( isspace(*p) ) ++p;
|
||||
else if( p[0] == '/' && p[1] == '*' ) { // skip C comment
|
||||
for( p += 2; *p && !(p[0] == '*' && p[1] == '/'); ++p) {}
|
||||
if( *p ) p += 2;
|
||||
}
|
||||
else if( p[0] == '/' && p[1] == '/' ) { // skip C++ comment
|
||||
for( p += 2; *p && p[0] != '\n'; ++p) {}
|
||||
if( *p ) ++p;
|
||||
}
|
||||
else break;
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
char *json5__parse_value(json5 *obj, char *p, char **err_code);
|
||||
|
||||
char *json5__parse_string(json5 *obj, char *p, char **err_code) {
|
||||
assert(obj && p);
|
||||
|
||||
if( *p == '"' || *p == '\'' || *p == '`' ) {
|
||||
obj->type = JSON5_STRING;
|
||||
obj->string = p + 1;
|
||||
|
||||
char eos_char = *p, *b = obj->string, *e = b;
|
||||
while (*e) {
|
||||
/**/ if( *e == '\\' && (e[1] == eos_char) ) ++e;
|
||||
else if( *e == '\\' && (e[1] == '\r' || e[1] == '\n') ) *e = ' ';
|
||||
else if( *e == eos_char ) break;
|
||||
++e;
|
||||
}
|
||||
|
||||
*e = '\0';
|
||||
return p = e + 1;
|
||||
}
|
||||
|
||||
//JSON5_ASSERT; *err_code = "json5_error_invalid_value";
|
||||
return NULL;
|
||||
}
|
||||
|
||||
char *json5__parse_object(json5 *obj, char *p, char **err_code) {
|
||||
assert(obj && p);
|
||||
|
||||
if( 1 /* *p == '{' */ ) { /* <-- for SJSON */
|
||||
int skip = *p == '{'; /* <-- for SJSON */
|
||||
|
||||
obj->type = JSON5_OBJECT;
|
||||
obj->nodes = 0;
|
||||
obj->count = 0;
|
||||
|
||||
while (*p) {
|
||||
json5 node = { 0 };
|
||||
|
||||
do { p = json5__trim(p + skip); skip = 1; } while( *p == ',' );
|
||||
|
||||
if( *p == '}' ) {
|
||||
++p;
|
||||
break;
|
||||
}
|
||||
// @todo: is_unicode() (s[0] == '\\' && isxdigit(s[1]) && isxdigit(s[2]) && isxdigit(s[3]) && isxdigit(s[4]))) {
|
||||
else if( isalnum(*p) || *p == '_' || *p == '$' || *p == '.' ) { // also || is_unicode(p)
|
||||
node.name = p;
|
||||
|
||||
do {
|
||||
++p;
|
||||
} while (*p && (isalnum(*p) || *p == '_' || *p == '$' || *p == '.') ); // also || is_unicode(p)
|
||||
|
||||
char *e = p;
|
||||
p = json5__trim(p);
|
||||
*e = '\0';
|
||||
}
|
||||
else { //if( *p == '"' || *p == '\'' || *p == '`' ) {
|
||||
char *ps = json5__parse_string(&node, p, err_code);
|
||||
if( !ps ) {
|
||||
return NULL;
|
||||
}
|
||||
p = ps;
|
||||
node.name = node.string;
|
||||
p = json5__trim(p);
|
||||
}
|
||||
|
||||
// @todo: https://www.ecma-international.org/ecma-262/5.1/#sec-7.6
|
||||
if( !(node.name && node.name[0]) ) { // !json5__validate_name(node.name) ) {
|
||||
JSON5_ASSERT; *err_code = "json5_error_invalid_name";
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if( !p || (*p && (*p != ':' && *p != '=' /* <-- for SJSON */)) ) {
|
||||
JSON5_ASSERT; *err_code = "json5_error_invalid_name";
|
||||
return NULL;
|
||||
}
|
||||
p = json5__trim(p + 1);
|
||||
p = json5__parse_value(&node, p, err_code);
|
||||
|
||||
if( *err_code[0] ) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if( node.type != JSON5_UNDEFINED ) {
|
||||
array_push(obj->nodes, node);
|
||||
++obj->count;
|
||||
}
|
||||
|
||||
if( *p == '}') { ++p; break; }
|
||||
}
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
JSON5_ASSERT; *err_code = "json5_error_invalid_value";
|
||||
return NULL;
|
||||
}
|
||||
|
||||
char *json5__parse_value(json5 *obj, char *p, char **err_code) {
|
||||
assert(obj && p);
|
||||
|
||||
p = json5__trim(p);
|
||||
|
||||
char *is_string = json5__parse_string(obj, p, err_code);
|
||||
|
||||
if( is_string ) {
|
||||
p = is_string;
|
||||
if( *err_code[0] ) {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
else if( *p == '{' ) {
|
||||
p = json5__parse_object( obj, p, err_code );
|
||||
if( *err_code[0] ) {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
else if( *p == '[' ) {
|
||||
obj->type = JSON5_ARRAY;
|
||||
obj->array = 0;
|
||||
obj->count = 0;
|
||||
|
||||
while (*p) {
|
||||
json5 elem = { 0 };
|
||||
|
||||
do { p = json5__trim(p + 1); } while( *p == ',' );
|
||||
if( *p == ']') { ++p; break; }
|
||||
|
||||
p = json5__parse_value(&elem, p, err_code);
|
||||
|
||||
if( *err_code[0] ) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if( elem.type != JSON5_UNDEFINED ) {
|
||||
array_push(obj->array, elem);
|
||||
++obj->count;
|
||||
}
|
||||
if (*p == ']') { ++p; break; }
|
||||
}
|
||||
}
|
||||
else if( isalpha(*p) || (*p == '-' && !isdigit(p[1])) ) {
|
||||
const char *labels[] = { "null", "on","true", "off","false", "nan","NaN", "-nan","-NaN", "inf","Infinity", "-inf","-Infinity", 0 };
|
||||
const int lenghts[] = { 4, 2,4, 3,5, 3,3, 4,4, 3,8, 4,9 };
|
||||
for( int i = 0; labels[i]; ++i ) {
|
||||
if( !strncmp(p, labels[i], lenghts[i] ) ) {
|
||||
p += lenghts[i];
|
||||
#ifdef _MSC_VER // somehow, NaN is apparently signed in MSC
|
||||
/**/ if( i >= 5 ) obj->type = JSON5_REAL, obj->real = i >= 11 ? -INFINITY : i >= 9 ? INFINITY : i >= 7 ? NAN :-NAN;
|
||||
#else
|
||||
/**/ if( i >= 5 ) obj->type = JSON5_REAL, obj->real = i >= 11 ? -INFINITY : i >= 9 ? INFINITY : i >= 7 ? -NAN : NAN;
|
||||
#endif
|
||||
else if( i >= 1 ) obj->type = JSON5_BOOL, obj->boolean = i <= 2;
|
||||
else obj->type = JSON5_NULL;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if( obj->type == JSON5_UNDEFINED ) {
|
||||
JSON5_ASSERT; *err_code = "json5_error_invalid_value";
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
else if( isdigit(*p) || *p == '+' || *p == '-' || *p == '.' ) {
|
||||
char buffer[32] = {0}, *buf = buffer, is_hex = 0, is_dbl = 0;
|
||||
while( *p && strchr("+-.xX0123456789aAbBcCdDeEfF", *p)) {
|
||||
is_hex |= (*p | 32) == 'x';
|
||||
is_dbl |= *p == '.';
|
||||
*buf++ = *p++;
|
||||
}
|
||||
obj->type = is_dbl ? JSON5_REAL : JSON5_INTEGER;
|
||||
long long unsigned int llu;
|
||||
long long int lli;
|
||||
/**/ if( is_dbl ) sscanf( buffer, "%lf", &obj->real );
|
||||
else if( is_hex ) sscanf( buffer, "%llx", &llu ), obj->integer = llu; // SCNx64 -> inttypes.h
|
||||
else sscanf( buffer, "%lld", &lli ), obj->integer = lli; // SCNd64 -> inttypes.h
|
||||
}
|
||||
else {
|
||||
return NULL;
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
char *json5_parse(json5 *root, char *p, int flags) {
|
||||
char *err_code = "";
|
||||
*root = (json5) {0};
|
||||
|
||||
if( p && p[0] ) {
|
||||
p = json5__trim(p);
|
||||
if( *p == '[' ) { /* <-- for SJSON */
|
||||
json5__parse_value(root, p, &err_code);
|
||||
} else {
|
||||
json5__parse_object(root, p, &err_code); /* <-- for SJSON */
|
||||
}
|
||||
} else {
|
||||
root->type = JSON5_OBJECT;
|
||||
}
|
||||
|
||||
return err_code[0] ? err_code : 0;
|
||||
}
|
||||
|
||||
void json5_free(json5 *root) {
|
||||
if( root->type == JSON5_ARRAY && root->array ) {
|
||||
for( int i = 0, cnt = array_count(root->array); i < cnt; ++i ) {
|
||||
json5_free(root->array + i);
|
||||
}
|
||||
array_free(root->array);
|
||||
}
|
||||
|
||||
if( root->type == JSON5_OBJECT && root->nodes ) {
|
||||
for( int i = 0, cnt = array_count(root->nodes); i < cnt; ++i ) {
|
||||
json5_free(root->nodes + i);
|
||||
}
|
||||
array_free(root->nodes);
|
||||
}
|
||||
|
||||
*root = (json5) {0}; // needed?
|
||||
}
|
||||
|
||||
void json5_write(FILE *fp, const json5 *o) {
|
||||
static __thread int indent = 0;
|
||||
int tabs = 1; // 0,1,2,4,8
|
||||
if( o->name ) {
|
||||
fprintf(fp, "%*.s\"%s\"%s", indent * tabs, "", o->name, tabs ? ": " : ":");
|
||||
}
|
||||
/**/ if( o->type == JSON5_NULL ) fprintf(fp, "%s", "null");
|
||||
else if( o->type == JSON5_BOOL ) fprintf(fp, "%s", o->boolean ? "true" : "false");
|
||||
else if( o->type == JSON5_INTEGER ) fprintf(fp, "%lld", (long long int)o->integer);
|
||||
else if( o->type == JSON5_REAL ) {
|
||||
/**/ if( isnan(o->real) ) fprintf(fp, "%s", signbit(o->real) ? "-nan" : "nan" );
|
||||
else if( isinf(o->real) ) fprintf(fp, "%s", signbit(o->real) ? "-inf" : "inf" );
|
||||
else fprintf(fp, "%1.8e", o->real); // %1.8e from google:"randomascii 100 digits" ; %.4llf for compactness
|
||||
}
|
||||
#if 0
|
||||
else if( o->type == JSON5_STRING ) { // write (escaped) string
|
||||
char chars[] = "\\\"\n\r\b\f\v", remap[] = "\\\"nrbfv", esc[256];
|
||||
for( int i = 0; chars[i]; ++i ) esc[ chars[i] ] = remap[i];
|
||||
|
||||
const char *b = o->string, *e = strpbrk(b, chars), *sep = "\"";
|
||||
while( e ) {
|
||||
fprintf(fp, "%s%.*s\\%c", sep, (int)(e - b), b, esc[(unsigned char)*e] );
|
||||
e = strpbrk( b = e + 1, chars);
|
||||
sep = "";
|
||||
}
|
||||
fprintf(fp, "%s%s\"", sep, b);
|
||||
}
|
||||
#else
|
||||
else if( o->type == JSON5_STRING ) { // write string
|
||||
fprintf(fp, "\"%s\"", o->string);
|
||||
}
|
||||
#endif
|
||||
else if( o->type == JSON5_ARRAY ) {
|
||||
const char *sep = "";
|
||||
fprintf(fp, "%s", tabs ? "[ " : "[");
|
||||
for( int i = 0, cnt = o->count; i < cnt; ++i ) {
|
||||
fprintf(fp, "%s", sep); sep = tabs ? ", " : ",";
|
||||
json5_write(fp, o->array + i);
|
||||
}
|
||||
fprintf(fp, "%s", tabs ? " ]" : "]");
|
||||
}
|
||||
else if( o->type == JSON5_OBJECT ) {
|
||||
const char *sep = "";
|
||||
fprintf(fp, "%*.s{%s", 0 * (++indent) * tabs, "", tabs ? "\n":"");
|
||||
for( int i = 0, cnt = o->count; i < cnt; ++i ) {
|
||||
fprintf(fp, "%s", sep); sep = tabs ? ",\n" : ",";
|
||||
json5_write(fp, o->nodes + i);
|
||||
}
|
||||
fprintf(fp, "%s%*.s}", tabs ? "\n":"", (--indent) * tabs, "");
|
||||
} else {
|
||||
char p[16] = {0};
|
||||
JSON5_ASSERT; /* "json5_error_invalid_value"; */
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef JSON5_BENCH
|
||||
#include <time.h>
|
||||
int main() {
|
||||
// https://www.reddit.com/r/datasets/comments/1uyd0t/200000_jeopardy_questions_in_a_json_file/
|
||||
char *content = 0;
|
||||
for( FILE *fp = fopen("jeopardy.json", "rb"); fp; fclose(fp), fp = 0 ) {
|
||||
fseek(fp, 0L, SEEK_END);
|
||||
size_t pos = ftell(fp);
|
||||
fseek(fp, 0L, SEEK_SET);
|
||||
content = (char*)malloc( pos + 1 );
|
||||
fread(content, 1, pos, fp);
|
||||
content[pos] = 0;
|
||||
}
|
||||
|
||||
if( content ) {
|
||||
clock_t start = clock();
|
||||
json5 root = {0};
|
||||
char *error = json5_parse(&root, content, 0);
|
||||
clock_t end = clock();
|
||||
double delta = ( end - start ) / (double)CLOCKS_PER_SEC;
|
||||
|
||||
if( !error ) {
|
||||
printf("Parsing time: %.3fms\n", delta*1000);
|
||||
printf("Total nodes: %d\n", array_count(root.array));
|
||||
printf("Category: %s, air date: %s\nQuestion: %s\n", root.array[0].nodes[0].string,
|
||||
root.array[0].nodes[1].string,
|
||||
root.array[0].nodes[2].string);
|
||||
} else {
|
||||
printf("Error: %s\n", error);
|
||||
}
|
||||
|
||||
json5_free(&root);
|
||||
free(content);
|
||||
}
|
||||
}
|
||||
#define main main__
|
||||
#endif
|
||||
|
||||
#ifdef JSON5_DEMO
|
||||
int main() {
|
||||
char source5[] =
|
||||
" // comments\n" /* json5 sample */
|
||||
" unquoted: 'and you can quote me on that',\n"
|
||||
" singleQuotes: 'I can use \"double quotes\" here',\n"
|
||||
" lineBreaks : \"Look, Mom! \\\n"
|
||||
"No \\n's!\",\n"
|
||||
" hexadecimal: 0x100,\n"
|
||||
" leadingDecimalPoint: .8675309, andTrailing: 8675309.,\n"
|
||||
" positiveSign: +1,\n"
|
||||
" trailingComma: 'in objects', andIn: ['arrays', ],\n"
|
||||
" \"backwardsCompatible\": \"with JSON\",\n"
|
||||
""
|
||||
" ip = \"127.0.0.1\"\n" /* sjson sample */
|
||||
" port = 8888\n"
|
||||
""
|
||||
" /* comment //nested comment*/\n" /* tests */
|
||||
" // comment /*nested comment*/\n"
|
||||
" nil: null,"
|
||||
" \"+lšctžýáíé=:\": true,,,,"
|
||||
" huge: 2.2239333e5, "
|
||||
" array: [+1,2,-3,4,5], "
|
||||
" hello: 'world /*comment in string*/ //again', "
|
||||
" abc: 42.67, def: false, "
|
||||
" children : { a: 1, b: 2, },"
|
||||
" invalids : [ nan, NaN, -nan, -NaN, inf, Infinity, -inf, -Infinity ],"
|
||||
""
|
||||
" multiline: `this is\n"
|
||||
"a multiline string\n"
|
||||
"yeah`"
|
||||
"}\n";
|
||||
|
||||
json5 root = { 0 };
|
||||
char *error = json5_parse(&root, source5, 0);
|
||||
if( error ) {
|
||||
printf("Error: %s\n", error);
|
||||
} else {
|
||||
json5_write(stdout, &root);
|
||||
}
|
||||
json5_free(&root);
|
||||
}
|
||||
#define main main__
|
||||
#endif
|
||||
|
||||
#endif // JSON5_C
|
||||
// JSON5 + SJSON parser module
|
||||
//
|
||||
// License:
|
||||
// This software is dual-licensed to the public domain and under the following
|
||||
// license: you are granted a perpetual, irrevocable license to copy, modify,
|
||||
// publish, and distribute this file as you see fit.
|
||||
// No warranty is implied, use at your own risk.
|
||||
//
|
||||
// Credits:
|
||||
// r-lyeh (fork)
|
||||
// Dominik Madarasz (@zaklaus) (original code)
|
||||
|
||||
#ifndef JSON5_H
|
||||
#define JSON5_H
|
||||
|
||||
#ifndef JSON5_ASSERT
|
||||
#define JSON5_ASSERT do { printf("JSON5: Error L%d while parsing '%c' in '%.16s'\n", __LINE__, p[0], p); assert(0); } while(0)
|
||||
#endif
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
|
||||
typedef enum json5_type {
|
||||
JSON5_UNDEFINED, // 0
|
||||
JSON5_NULL, // 1
|
||||
JSON5_BOOL, // 2
|
||||
JSON5_OBJECT, // 3
|
||||
JSON5_STRING, // 4
|
||||
JSON5_ARRAY, // 5
|
||||
JSON5_INTEGER, // 6
|
||||
JSON5_REAL, // 7
|
||||
} json5_type;
|
||||
|
||||
typedef struct json5 {
|
||||
char* name;
|
||||
#ifdef NDEBUG
|
||||
unsigned type : 3;
|
||||
#else
|
||||
json5_type type;
|
||||
#endif
|
||||
unsigned count : 29;
|
||||
union {
|
||||
struct json5* array;
|
||||
struct json5* nodes;
|
||||
int64_t integer;
|
||||
double real;
|
||||
char* string;
|
||||
int boolean;
|
||||
};
|
||||
} json5;
|
||||
|
||||
char* json5_parse(json5 *root, char *source, int flags);
|
||||
void json5_write(FILE *fp, const json5 *root);
|
||||
void json5_free(json5 *root);
|
||||
|
||||
#endif // JSON5_H
|
||||
|
||||
// json5 ----------------------------------------------------------------------
|
||||
|
||||
#ifdef JSON5_C
|
||||
//#pragma once
|
||||
#include <assert.h>
|
||||
#include <ctype.h>
|
||||
#include <math.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
char *json5__trim(char *p) {
|
||||
while (*p) {
|
||||
/**/ if( isspace(*p) ) ++p;
|
||||
else if( p[0] == '/' && p[1] == '*' ) { // skip C comment
|
||||
for( p += 2; *p && !(p[0] == '*' && p[1] == '/'); ++p) {}
|
||||
if( *p ) p += 2;
|
||||
}
|
||||
else if( p[0] == '/' && p[1] == '/' ) { // skip C++ comment
|
||||
for( p += 2; *p && p[0] != '\n'; ++p) {}
|
||||
if( *p ) ++p;
|
||||
}
|
||||
else break;
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
char *json5__parse_value(json5 *obj, char *p, char **err_code);
|
||||
|
||||
char *json5__parse_string(json5 *obj, char *p, char **err_code) {
|
||||
assert(obj && p);
|
||||
|
||||
if( *p == '"' || *p == '\'' || *p == '`' ) {
|
||||
obj->type = JSON5_STRING;
|
||||
obj->string = p + 1;
|
||||
|
||||
char eos_char = *p, *b = obj->string, *e = b;
|
||||
while (*e) {
|
||||
/**/ if( *e == '\\' && (e[1] == eos_char) ) ++e;
|
||||
else if( *e == '\\' && (e[1] == '\r' || e[1] == '\n') ) *e = ' ';
|
||||
else if( *e == eos_char ) break;
|
||||
++e;
|
||||
}
|
||||
|
||||
*e = '\0';
|
||||
return p = e + 1;
|
||||
}
|
||||
|
||||
//JSON5_ASSERT; *err_code = "json5_error_invalid_value";
|
||||
return NULL;
|
||||
}
|
||||
|
||||
char *json5__parse_object(json5 *obj, char *p, char **err_code) {
|
||||
assert(obj && p);
|
||||
|
||||
if( 1 /* *p == '{' */ ) { /* <-- for SJSON */
|
||||
int skip = *p == '{'; /* <-- for SJSON */
|
||||
|
||||
obj->type = JSON5_OBJECT;
|
||||
obj->nodes = 0;
|
||||
obj->count = 0;
|
||||
|
||||
while (*p) {
|
||||
json5 node = { 0 };
|
||||
|
||||
do { p = json5__trim(p + skip); skip = 1; } while( *p == ',' );
|
||||
|
||||
if( *p == '}' ) {
|
||||
++p;
|
||||
break;
|
||||
}
|
||||
// @todo: is_unicode() (s[0] == '\\' && isxdigit(s[1]) && isxdigit(s[2]) && isxdigit(s[3]) && isxdigit(s[4]))) {
|
||||
else if( isalnum(*p) || *p == '_' || *p == '$' || *p == '.' ) { // also || is_unicode(p)
|
||||
node.name = p;
|
||||
|
||||
do {
|
||||
++p;
|
||||
} while (*p && (isalnum(*p) || *p == '_' || *p == '$' || *p == '.') ); // also || is_unicode(p)
|
||||
|
||||
char *e = p;
|
||||
p = json5__trim(p);
|
||||
*e = '\0';
|
||||
}
|
||||
else { //if( *p == '"' || *p == '\'' || *p == '`' ) {
|
||||
char *ps = json5__parse_string(&node, p, err_code);
|
||||
if( !ps ) {
|
||||
return NULL;
|
||||
}
|
||||
p = ps;
|
||||
node.name = node.string;
|
||||
p = json5__trim(p);
|
||||
}
|
||||
|
||||
// @todo: https://www.ecma-international.org/ecma-262/5.1/#sec-7.6
|
||||
if( !(node.name && node.name[0]) ) { // !json5__validate_name(node.name) ) {
|
||||
JSON5_ASSERT; *err_code = "json5_error_invalid_name";
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if( !p || (*p && (*p != ':' && *p != '=' /* <-- for SJSON */)) ) {
|
||||
JSON5_ASSERT; *err_code = "json5_error_invalid_name";
|
||||
return NULL;
|
||||
}
|
||||
p = json5__trim(p + 1);
|
||||
p = json5__parse_value(&node, p, err_code);
|
||||
|
||||
if( *err_code[0] ) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if( node.type != JSON5_UNDEFINED ) {
|
||||
array_push(obj->nodes, node);
|
||||
++obj->count;
|
||||
}
|
||||
|
||||
if( *p == '}') { ++p; break; }
|
||||
}
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
JSON5_ASSERT; *err_code = "json5_error_invalid_value";
|
||||
return NULL;
|
||||
}
|
||||
|
||||
char *json5__parse_value(json5 *obj, char *p, char **err_code) {
|
||||
assert(obj && p);
|
||||
|
||||
p = json5__trim(p);
|
||||
|
||||
char *is_string = json5__parse_string(obj, p, err_code);
|
||||
|
||||
if( is_string ) {
|
||||
p = is_string;
|
||||
if( *err_code[0] ) {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
else if( *p == '{' ) {
|
||||
p = json5__parse_object( obj, p, err_code );
|
||||
if( *err_code[0] ) {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
else if( *p == '[' ) {
|
||||
obj->type = JSON5_ARRAY;
|
||||
obj->array = 0;
|
||||
obj->count = 0;
|
||||
|
||||
while (*p) {
|
||||
json5 elem = { 0 };
|
||||
|
||||
do { p = json5__trim(p + 1); } while( *p == ',' );
|
||||
if( *p == ']') { ++p; break; }
|
||||
|
||||
p = json5__parse_value(&elem, p, err_code);
|
||||
|
||||
if( *err_code[0] ) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if( elem.type != JSON5_UNDEFINED ) {
|
||||
array_push(obj->array, elem);
|
||||
++obj->count;
|
||||
}
|
||||
if (*p == ']') { ++p; break; }
|
||||
}
|
||||
}
|
||||
else if( isalpha(*p) || (*p == '-' && !isdigit(p[1])) ) {
|
||||
const char *labels[] = { "null", "on","true", "off","false", "nan","NaN", "-nan","-NaN", "inf","Infinity", "-inf","-Infinity", 0 };
|
||||
const int lenghts[] = { 4, 2,4, 3,5, 3,3, 4,4, 3,8, 4,9 };
|
||||
for( int i = 0; labels[i]; ++i ) {
|
||||
if( !strncmp(p, labels[i], lenghts[i] ) ) {
|
||||
p += lenghts[i];
|
||||
#ifdef _MSC_VER // somehow, NaN is apparently signed in MSC
|
||||
/**/ if( i >= 5 ) obj->type = JSON5_REAL, obj->real = i >= 11 ? -INFINITY : i >= 9 ? INFINITY : i >= 7 ? NAN :-NAN;
|
||||
#else
|
||||
/**/ if( i >= 5 ) obj->type = JSON5_REAL, obj->real = i >= 11 ? -INFINITY : i >= 9 ? INFINITY : i >= 7 ? -NAN : NAN;
|
||||
#endif
|
||||
else if( i >= 1 ) obj->type = JSON5_BOOL, obj->boolean = i <= 2;
|
||||
else obj->type = JSON5_NULL;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if( obj->type == JSON5_UNDEFINED ) {
|
||||
JSON5_ASSERT; *err_code = "json5_error_invalid_value";
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
else if( isdigit(*p) || *p == '+' || *p == '-' || *p == '.' ) {
|
||||
char buffer[32] = {0}, *buf = buffer, is_hex = 0, is_dbl = 0;
|
||||
while( *p && strchr("+-.xX0123456789aAbBcCdDeEfF", *p)) {
|
||||
is_hex |= (*p | 32) == 'x';
|
||||
is_dbl |= *p == '.';
|
||||
*buf++ = *p++;
|
||||
}
|
||||
obj->type = is_dbl ? JSON5_REAL : JSON5_INTEGER;
|
||||
long long unsigned int llu;
|
||||
long long int lli;
|
||||
/**/ if( is_dbl ) sscanf( buffer, "%lf", &obj->real );
|
||||
else if( is_hex ) sscanf( buffer, "%llx", &llu ), obj->integer = llu; // SCNx64 -> inttypes.h
|
||||
else sscanf( buffer, "%lld", &lli ), obj->integer = lli; // SCNd64 -> inttypes.h
|
||||
}
|
||||
else {
|
||||
return NULL;
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
char *json5_parse(json5 *root, char *p, int flags) {
|
||||
char *err_code = "";
|
||||
*root = (json5) {0};
|
||||
|
||||
if( p && p[0] ) {
|
||||
p = json5__trim(p);
|
||||
if( *p == '[' ) { /* <-- for SJSON */
|
||||
json5__parse_value(root, p, &err_code);
|
||||
} else {
|
||||
json5__parse_object(root, p, &err_code); /* <-- for SJSON */
|
||||
}
|
||||
} else {
|
||||
root->type = JSON5_OBJECT;
|
||||
}
|
||||
|
||||
return err_code[0] ? err_code : 0;
|
||||
}
|
||||
|
||||
void json5_free(json5 *root) {
|
||||
if( root->type == JSON5_ARRAY && root->array ) {
|
||||
for( int i = 0, cnt = array_count(root->array); i < cnt; ++i ) {
|
||||
json5_free(root->array + i);
|
||||
}
|
||||
array_free(root->array);
|
||||
}
|
||||
|
||||
if( root->type == JSON5_OBJECT && root->nodes ) {
|
||||
for( int i = 0, cnt = array_count(root->nodes); i < cnt; ++i ) {
|
||||
json5_free(root->nodes + i);
|
||||
}
|
||||
array_free(root->nodes);
|
||||
}
|
||||
|
||||
*root = (json5) {0}; // needed?
|
||||
}
|
||||
|
||||
void json5_write(FILE *fp, const json5 *o) {
|
||||
static __thread int indent = 0;
|
||||
int tabs = 1; // 0,1,2,4,8
|
||||
if( o->name ) {
|
||||
fprintf(fp, "%*.s\"%s\"%s", indent * tabs, "", o->name, tabs ? ": " : ":");
|
||||
}
|
||||
/**/ if( o->type == JSON5_NULL ) fprintf(fp, "%s", "null");
|
||||
else if( o->type == JSON5_BOOL ) fprintf(fp, "%s", o->boolean ? "true" : "false");
|
||||
else if( o->type == JSON5_INTEGER ) fprintf(fp, "%lld", (long long int)o->integer);
|
||||
else if( o->type == JSON5_REAL ) {
|
||||
/**/ if( isnan(o->real) ) fprintf(fp, "%s", signbit(o->real) ? "-nan" : "nan" );
|
||||
else if( isinf(o->real) ) fprintf(fp, "%s", signbit(o->real) ? "-inf" : "inf" );
|
||||
else fprintf(fp, "%1.8e", o->real); // %1.8e from google:"randomascii 100 digits" ; %.4llf for compactness
|
||||
}
|
||||
#if 0
|
||||
else if( o->type == JSON5_STRING ) { // write (escaped) string
|
||||
char chars[] = "\\\"\n\r\b\f\v", remap[] = "\\\"nrbfv", esc[256];
|
||||
for( int i = 0; chars[i]; ++i ) esc[ chars[i] ] = remap[i];
|
||||
|
||||
const char *b = o->string, *e = strpbrk(b, chars), *sep = "\"";
|
||||
while( e ) {
|
||||
fprintf(fp, "%s%.*s\\%c", sep, (int)(e - b), b, esc[(unsigned char)*e] );
|
||||
e = strpbrk( b = e + 1, chars);
|
||||
sep = "";
|
||||
}
|
||||
fprintf(fp, "%s%s\"", sep, b);
|
||||
}
|
||||
#else
|
||||
else if( o->type == JSON5_STRING ) { // write string
|
||||
fprintf(fp, "\"%s\"", o->string);
|
||||
}
|
||||
#endif
|
||||
else if( o->type == JSON5_ARRAY ) {
|
||||
const char *sep = "";
|
||||
fprintf(fp, "%s", tabs ? "[ " : "[");
|
||||
for( int i = 0, cnt = o->count; i < cnt; ++i ) {
|
||||
fprintf(fp, "%s", sep); sep = tabs ? ", " : ",";
|
||||
json5_write(fp, o->array + i);
|
||||
}
|
||||
fprintf(fp, "%s", tabs ? " ]" : "]");
|
||||
}
|
||||
else if( o->type == JSON5_OBJECT ) {
|
||||
const char *sep = "";
|
||||
fprintf(fp, "%*.s{%s", 0 * (++indent) * tabs, "", tabs ? "\n":"");
|
||||
for( int i = 0, cnt = o->count; i < cnt; ++i ) {
|
||||
fprintf(fp, "%s", sep); sep = tabs ? ",\n" : ",";
|
||||
json5_write(fp, o->nodes + i);
|
||||
}
|
||||
fprintf(fp, "%s%*.s}", tabs ? "\n":"", (--indent) * tabs, "");
|
||||
} else {
|
||||
char p[16] = {0};
|
||||
JSON5_ASSERT; /* "json5_error_invalid_value"; */
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef JSON5_BENCH
|
||||
#include <time.h>
|
||||
int main() {
|
||||
// https://www.reddit.com/r/datasets/comments/1uyd0t/200000_jeopardy_questions_in_a_json_file/
|
||||
char *content = 0;
|
||||
for( FILE *fp = fopen("jeopardy.json", "rb"); fp; fclose(fp), fp = 0 ) {
|
||||
fseek(fp, 0L, SEEK_END);
|
||||
size_t pos = ftell(fp);
|
||||
fseek(fp, 0L, SEEK_SET);
|
||||
content = (char*)malloc( pos + 1 );
|
||||
fread(content, 1, pos, fp);
|
||||
content[pos] = 0;
|
||||
}
|
||||
|
||||
if( content ) {
|
||||
clock_t start = clock();
|
||||
json5 root = {0};
|
||||
char *error = json5_parse(&root, content, 0);
|
||||
clock_t end = clock();
|
||||
double delta = ( end - start ) / (double)CLOCKS_PER_SEC;
|
||||
|
||||
if( !error ) {
|
||||
printf("Parsing time: %.3fms\n", delta*1000);
|
||||
printf("Total nodes: %d\n", array_count(root.array));
|
||||
printf("Category: %s, air date: %s\nQuestion: %s\n", root.array[0].nodes[0].string,
|
||||
root.array[0].nodes[1].string,
|
||||
root.array[0].nodes[2].string);
|
||||
} else {
|
||||
printf("Error: %s\n", error);
|
||||
}
|
||||
|
||||
json5_free(&root);
|
||||
free(content);
|
||||
}
|
||||
}
|
||||
#define main main__
|
||||
#endif
|
||||
|
||||
#ifdef JSON5_DEMO
|
||||
int main() {
|
||||
char source5[] =
|
||||
" // comments\n" /* json5 sample */
|
||||
" unquoted: 'and you can quote me on that',\n"
|
||||
" singleQuotes: 'I can use \"double quotes\" here',\n"
|
||||
" lineBreaks : \"Look, Mom! \\\n"
|
||||
"No \\n's!\",\n"
|
||||
" hexadecimal: 0x100,\n"
|
||||
" leadingDecimalPoint: .8675309, andTrailing: 8675309.,\n"
|
||||
" positiveSign: +1,\n"
|
||||
" trailingComma: 'in objects', andIn: ['arrays', ],\n"
|
||||
" \"backwardsCompatible\": \"with JSON\",\n"
|
||||
""
|
||||
" ip = \"127.0.0.1\"\n" /* sjson sample */
|
||||
" port = 8888\n"
|
||||
""
|
||||
" /* comment //nested comment*/\n" /* tests */
|
||||
" // comment /*nested comment*/\n"
|
||||
" nil: null,"
|
||||
" \"+lšctžýáíé=:\": true,,,,"
|
||||
" huge: 2.2239333e5, "
|
||||
" array: [+1,2,-3,4,5], "
|
||||
" hello: 'world /*comment in string*/ //again', "
|
||||
" abc: 42.67, def: false, "
|
||||
" children : { a: 1, b: 2, },"
|
||||
" invalids : [ nan, NaN, -nan, -NaN, inf, Infinity, -inf, -Infinity ],"
|
||||
""
|
||||
" multiline: `this is\n"
|
||||
"a multiline string\n"
|
||||
"yeah`"
|
||||
"}\n";
|
||||
|
||||
json5 root = { 0 };
|
||||
char *error = json5_parse(&root, source5, 0);
|
||||
if( error ) {
|
||||
printf("Error: %s\n", error);
|
||||
} else {
|
||||
json5_write(stdout, &root);
|
||||
}
|
||||
json5_free(&root);
|
||||
}
|
||||
#define main main__
|
||||
#endif
|
||||
|
||||
#endif // JSON5_C
|
||||
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -1,275 +1,275 @@
|
|||
// lite editor, platform details
|
||||
// - rlyeh, public domain
|
||||
|
||||
#define LT_DATAPATH "/lite"
|
||||
|
||||
#define lt_assert(x) ASSERT(x)
|
||||
|
||||
#define lt_realpath(p, q) file_pathabs(p)
|
||||
#define lt_realpath_free(p)
|
||||
|
||||
#define lt_malloc(n) MALLOC(n)
|
||||
#define lt_calloc(n,m) CALLOC(n,m)
|
||||
#define lt_free(p) FREE(p)
|
||||
#define lt_memcpy(d,s,c) memcpy(d,s,c)
|
||||
#define lt_memset(p,ch,c) memset(p,ch,c)
|
||||
|
||||
#define lt_time_ms() time_ms()
|
||||
#define lt_sleep_ms(ms) sleep_ms(ms)
|
||||
|
||||
#define lt_getclipboard(w) window_clipboard()
|
||||
#define lt_setclipboard(w,s) window_setclipboard(s)
|
||||
|
||||
#define lt_window() window_handle()
|
||||
#define lt_setwindowmode(m) window_fullscreen(m == 2), (m < 2 && (window_maximize(m),1)) // 0:normal,1:maximized,2:fullscreen
|
||||
#define lt_setwindowtitle(t) //window_title(t)
|
||||
#define lt_haswindowfocus() window_has_focus()
|
||||
#define lt_setcursor(shape) window_cursor_shape(lt_events & (1<<31) ? CURSOR_SW_AUTO : shape+1) // 0:arrow,1:ibeam,2:sizeh,3:sizev,4:hand
|
||||
|
||||
#define lt_prompt(msg,title) ifndef(win32, 0, (MessageBoxA(0, msg, title, MB_YESNO | MB_ICONWARNING) == IDYES))
|
||||
|
||||
unsigned lt_events = ~0u;
|
||||
int lt_mx = 0, lt_my = 0, lt_wx = 0, lt_wy = 0, lt_ww = 0, lt_wh = 0;
|
||||
|
||||
typedef struct lt_surface {
|
||||
unsigned w, h;
|
||||
void *pixels;
|
||||
texture_t t;
|
||||
} lt_surface;
|
||||
|
||||
typedef struct lt_rect {
|
||||
int x, y, width, height;
|
||||
} lt_rect;
|
||||
|
||||
lt_surface *lt_getsurface(void *window) {
|
||||
static lt_surface s = {0};
|
||||
return &s;
|
||||
}
|
||||
void lt_updatesurfacerects(lt_surface *s, lt_rect* rects, unsigned count) {
|
||||
if(0)
|
||||
for( int i = 0; i < count; ++i ) {
|
||||
memset( (unsigned*)s->pixels + (rects[i].x + rects[i].y * s->w), 0xFF, rects[i].width*4 );
|
||||
memset( (unsigned*)s->pixels + (rects[i].x + (rects[i].y + (rects[i].height-1)) * s->w), 0xFF, rects[i].width*4 );
|
||||
for( int y = 1; y < (rects[i].height-1); ++y ) {
|
||||
((unsigned*)s->pixels)[ rects[i].x + y * s->w ] =
|
||||
((unsigned*)s->pixels)[ rects[i].x + (rects[i].width-1) + y * s->w ] = 0xFFFFFFFF;
|
||||
}
|
||||
}
|
||||
|
||||
// update contents
|
||||
texture_update(&s->t, s->w, s->h, 4, s->pixels, TEXTURE_LINEAR|TEXTURE_BGRA);
|
||||
}
|
||||
|
||||
void ren_set_clip_rect(struct lt_rect rect);
|
||||
void rencache_invalidate(void);
|
||||
int lt_resizesurface(lt_surface *s, int ww, int wh) {
|
||||
s->w = ww, s->h = wh;
|
||||
if( s->t.id == 0 || s->w != s->t.w || s->h != s->t.h ) {
|
||||
// invalidate tiles
|
||||
ren_set_clip_rect( (lt_rect) { 0, 0, s->w, s->h } );
|
||||
rencache_invalidate();
|
||||
|
||||
// texture clear
|
||||
if( !s->t.id ) s->t = texture_create(1, 1, 4, " ", TEXTURE_LINEAR|TEXTURE_RGBA|TEXTURE_BYTE );
|
||||
s->pixels = REALLOC(s->pixels, s->w * s->h * 4);
|
||||
memset(s->pixels, 0, s->w * s->h * 4);
|
||||
|
||||
// texture update
|
||||
lt_updatesurfacerects(s,0,0);
|
||||
return 1; // resized
|
||||
}
|
||||
return 0; // unchanged
|
||||
}
|
||||
|
||||
void *lt_load_file(const char *filename, int *size) {
|
||||
int datalen; char *data = file_load(filename, &datalen);
|
||||
if( !data || !datalen ) {
|
||||
filename = (const char *)file_normalize(filename);
|
||||
if( strbegi(filename, app_path()) ) filename += strlen(app_path());
|
||||
data = vfs_load(filename, &datalen);
|
||||
}
|
||||
if (size) *size = 0;
|
||||
if (!data) { return NULL; }
|
||||
if (size) *size = datalen;
|
||||
// return permanent buffers here, as file_load() and vfs_load() do return temporaries
|
||||
data = memcpy(MALLOC(datalen+1), data, datalen);
|
||||
data[datalen] = 0;
|
||||
return data;
|
||||
}
|
||||
|
||||
const char* lt_button_name(int button) {
|
||||
if(button == GLFW_MOUSE_BUTTON_LEFT) return "left";
|
||||
if(button == GLFW_MOUSE_BUTTON_RIGHT) return "right";
|
||||
if(button == GLFW_MOUSE_BUTTON_MIDDLE) return "middle";
|
||||
return "?";
|
||||
}
|
||||
|
||||
char* lt_key_name(char *dst, int key, int vk, int mods) {
|
||||
// @todo: "altgr" -> left ctrl + right alt
|
||||
|
||||
if( key == GLFW_KEY_UP ) return "up";
|
||||
if( key == GLFW_KEY_DOWN ) return "down";
|
||||
if( key == GLFW_KEY_LEFT ) return "left";
|
||||
if( key == GLFW_KEY_RIGHT ) return "right";
|
||||
if( key == GLFW_KEY_LEFT_ALT ) return "left alt";
|
||||
if( key == GLFW_KEY_RIGHT_ALT ) return "right alt";
|
||||
if( key == GLFW_KEY_LEFT_SHIFT ) return "left shift";
|
||||
if( key == GLFW_KEY_RIGHT_SHIFT ) return "right shift";
|
||||
if( key == GLFW_KEY_LEFT_CONTROL ) return "left ctrl";
|
||||
if( key == GLFW_KEY_RIGHT_CONTROL ) return "right ctrl";
|
||||
if( key == GLFW_KEY_LEFT_SUPER ) return "left windows";
|
||||
if( key == GLFW_KEY_RIGHT_SUPER ) return "left windows";
|
||||
if( key == GLFW_KEY_MENU ) return "menu";
|
||||
|
||||
if( key == GLFW_KEY_ESCAPE ) return "escape";
|
||||
if( key == GLFW_KEY_BACKSPACE ) return "backspace";
|
||||
if( key == GLFW_KEY_ENTER ) return "return";
|
||||
if( key == GLFW_KEY_KP_ENTER ) return "keypad enter";
|
||||
if( key == GLFW_KEY_TAB ) return "tab";
|
||||
if( key == GLFW_KEY_CAPS_LOCK ) return "capslock";
|
||||
|
||||
if( key == GLFW_KEY_HOME ) return "home";
|
||||
if( key == GLFW_KEY_END ) return "end";
|
||||
if( key == GLFW_KEY_INSERT ) return "insert";
|
||||
if( key == GLFW_KEY_DELETE ) return "delete";
|
||||
if( key == GLFW_KEY_PAGE_UP ) return "pageup";
|
||||
if( key == GLFW_KEY_PAGE_DOWN ) return "pagedown";
|
||||
|
||||
if( key == GLFW_KEY_F1 ) return "f1";
|
||||
if( key == GLFW_KEY_F2 ) return "f2";
|
||||
if( key == GLFW_KEY_F3 ) return "f3";
|
||||
if( key == GLFW_KEY_F4 ) return "f4";
|
||||
if( key == GLFW_KEY_F5 ) return "f5";
|
||||
if( key == GLFW_KEY_F6 ) return "f6";
|
||||
if( key == GLFW_KEY_F7 ) return "f7";
|
||||
if( key == GLFW_KEY_F8 ) return "f8";
|
||||
if( key == GLFW_KEY_F9 ) return "f9";
|
||||
if( key == GLFW_KEY_F10 ) return "f10";
|
||||
if( key == GLFW_KEY_F11 ) return "f11";
|
||||
if( key == GLFW_KEY_F12 ) return "f12";
|
||||
|
||||
const char *name = glfwGetKeyName(key, vk);
|
||||
strcpy(dst, name ? name : "");
|
||||
char *p = dst;
|
||||
while (*p) {
|
||||
*p = tolower(*p);
|
||||
p++;
|
||||
}
|
||||
return dst;
|
||||
}
|
||||
|
||||
void lt_globpath(struct lua_State*L, const char *path) {
|
||||
unsigned j = 0;
|
||||
|
||||
if(!strend(path, "/")) path = (const char *)va("%s/", path);
|
||||
for( dir *d = dir_open(path, ""); d; dir_close(d), d = 0 ) {
|
||||
for( unsigned i = 0, end = dir_count(d); i < end; ++i ) {
|
||||
char *name = dir_name(d,i);
|
||||
char *last = name + strlen(name) - 1;
|
||||
if( *last == '/' ) *last = '\0';
|
||||
name = file_name(name);
|
||||
lua_pushstring(L, name);
|
||||
lua_rawseti(L, -2, ++j);
|
||||
}
|
||||
}
|
||||
|
||||
for( const char *section = strstri(path, LT_DATAPATH); section && section[sizeof(LT_DATAPATH)-1] == '/'; section = 0) {
|
||||
array(char*) list = vfs_list("**");
|
||||
for( unsigned i = 0, end = array_count(list); i < end; ++i ) {
|
||||
char *name = list[i];
|
||||
if( !strstri(name, section+1) ) continue;
|
||||
lua_pushstring(L, file_name(name));
|
||||
lua_rawseti(L, -2, ++j);
|
||||
}
|
||||
if( array_count(list) ) return;
|
||||
}
|
||||
}
|
||||
|
||||
int lt_emit_event(lua_State *L, const char *event_name, const char *event_fmt, ...) {
|
||||
int count = 0;
|
||||
lua_pushstring(L, event_name);
|
||||
if( event_fmt ) {
|
||||
va_list va;
|
||||
va_start(va, event_fmt);
|
||||
for( ; event_fmt[count]; ++count ) {
|
||||
/**/ if( event_fmt[count] == 'd' ) { int d = va_arg(va, int); lua_pushnumber(L, d); }
|
||||
else if( event_fmt[count] == 'f' ) { double f = va_arg(va, double); lua_pushnumber(L, f); }
|
||||
else if( event_fmt[count] == 's' ) { const char *s = va_arg(va, const char *); lua_pushstring(L, s); }
|
||||
}
|
||||
va_end(va);
|
||||
}
|
||||
return 1+count;
|
||||
}
|
||||
|
||||
int printi(int i) {
|
||||
// printf("clicks: %d\n", i);
|
||||
return i;
|
||||
}
|
||||
|
||||
static const char* codepoint_to_utf8_(unsigned c);
|
||||
int lt_poll_event(lua_State *L) { // init.lua > core.step() wakes on mousemoved || inputtext
|
||||
int rc = 0;
|
||||
char buf[16];
|
||||
static int prevx = 0, prevy = 0;
|
||||
|
||||
static unsigned clicks_time = 0, clicks = 0;
|
||||
if( (lt_time_ms() - clicks_time) > 400 ) clicks = 0;
|
||||
|
||||
//
|
||||
|
||||
for( GLEQevent e; gleqNextEvent(&e); gleqFreeEvent(&e) )
|
||||
if( lt_events & e.type )
|
||||
switch (e.type) {
|
||||
default:
|
||||
break; case GLEQ_WINDOW_CLOSED: // it used to be ok. depends on window_swap() flow
|
||||
rc += lt_emit_event(L, "quit", NULL);
|
||||
return rc;
|
||||
|
||||
break; case GLEQ_WINDOW_MOVED:
|
||||
lt_wx = e.pos.x;
|
||||
lt_wy = e.pos.y;
|
||||
|
||||
break; case GLEQ_WINDOW_RESIZED:
|
||||
rc += lt_emit_event(L, "resized", "dd", lt_ww = e.size.width, lt_wh = e.size.height);
|
||||
lt_resizesurface(lt_getsurface(lt_window()), lt_ww, lt_wh);
|
||||
|
||||
break; case GLEQ_WINDOW_REFRESH:
|
||||
rc += lt_emit_event(L, "exposed", NULL);
|
||||
rencache_invalidate();
|
||||
|
||||
break; case GLEQ_FILE_DROPPED:
|
||||
rc += lt_emit_event(L, "filedropped", "sdd", e.file.paths[0], lt_mx, lt_my);
|
||||
|
||||
break; case GLEQ_KEY_PRESSED:
|
||||
case GLEQ_KEY_REPEATED:
|
||||
rc += lt_emit_event(L, "keypressed", "s", lt_key_name(buf, e.keyboard.key, e.keyboard.scancode, e.keyboard.mods));
|
||||
goto bottom;
|
||||
|
||||
break; case GLEQ_KEY_RELEASED:
|
||||
rc += lt_emit_event(L, "keyreleased", "s", lt_key_name(buf, e.keyboard.key, e.keyboard.scancode, e.keyboard.mods));
|
||||
goto bottom;
|
||||
|
||||
break; case GLEQ_CODEPOINT_INPUT:
|
||||
rc += lt_emit_event(L, "textinput", "s", codepoint_to_utf8_(e.codepoint));
|
||||
|
||||
break; case GLEQ_BUTTON_PRESSED:
|
||||
rc += lt_emit_event(L, "mousepressed", "sddd", lt_button_name(e.mouse.button), lt_mx, lt_my, printi(1 + clicks));
|
||||
|
||||
break; case GLEQ_BUTTON_RELEASED:
|
||||
clicks += e.mouse.button == GLFW_MOUSE_BUTTON_1;
|
||||
clicks_time = lt_time_ms();
|
||||
rc += lt_emit_event(L, "mousereleased", "sdd", lt_button_name(e.mouse.button), lt_mx, lt_my);
|
||||
|
||||
break; case GLEQ_CURSOR_MOVED:
|
||||
lt_mx = e.pos.x - lt_wx, lt_my = e.pos.y - lt_wy;
|
||||
rc += lt_emit_event(L, "mousemoved", "dddd", lt_mx, lt_my, lt_mx - prevx, lt_my - prevy);
|
||||
prevx = lt_mx, prevy = lt_my;
|
||||
|
||||
break; case GLEQ_SCROLLED:
|
||||
rc += lt_emit_event(L, "mousewheel", "f", e.scroll.y);
|
||||
}
|
||||
|
||||
bottom:;
|
||||
|
||||
return rc;
|
||||
}
|
||||
// lite editor, platform details
|
||||
// - rlyeh, public domain
|
||||
|
||||
#define LT_DATAPATH "/lite"
|
||||
|
||||
#define lt_assert(x) ASSERT(x)
|
||||
|
||||
#define lt_realpath(p, q) file_pathabs(p)
|
||||
#define lt_realpath_free(p)
|
||||
|
||||
#define lt_malloc(n) MALLOC(n)
|
||||
#define lt_calloc(n,m) CALLOC(n,m)
|
||||
#define lt_free(p) FREE(p)
|
||||
#define lt_memcpy(d,s,c) memcpy(d,s,c)
|
||||
#define lt_memset(p,ch,c) memset(p,ch,c)
|
||||
|
||||
#define lt_time_ms() time_ms()
|
||||
#define lt_sleep_ms(ms) sleep_ms(ms)
|
||||
|
||||
#define lt_getclipboard(w) window_clipboard()
|
||||
#define lt_setclipboard(w,s) window_setclipboard(s)
|
||||
|
||||
#define lt_window() window_handle()
|
||||
#define lt_setwindowmode(m) window_fullscreen(m == 2), (m < 2 && (window_maximize(m),1)) // 0:normal,1:maximized,2:fullscreen
|
||||
#define lt_setwindowtitle(t) //window_title(t)
|
||||
#define lt_haswindowfocus() window_has_focus()
|
||||
#define lt_setcursor(shape) window_cursor_shape(lt_events & (1<<31) ? CURSOR_SW_AUTO : shape+1) // 0:arrow,1:ibeam,2:sizeh,3:sizev,4:hand
|
||||
|
||||
#define lt_prompt(msg,title) ifndef(win32, 0, (MessageBoxA(0, msg, title, MB_YESNO | MB_ICONWARNING) == IDYES))
|
||||
|
||||
unsigned lt_events = ~0u;
|
||||
int lt_mx = 0, lt_my = 0, lt_wx = 0, lt_wy = 0, lt_ww = 0, lt_wh = 0;
|
||||
|
||||
typedef struct lt_surface {
|
||||
unsigned w, h;
|
||||
void *pixels;
|
||||
texture_t t;
|
||||
} lt_surface;
|
||||
|
||||
typedef struct lt_rect {
|
||||
int x, y, width, height;
|
||||
} lt_rect;
|
||||
|
||||
lt_surface *lt_getsurface(void *window) {
|
||||
static lt_surface s = {0};
|
||||
return &s;
|
||||
}
|
||||
void lt_updatesurfacerects(lt_surface *s, lt_rect* rects, unsigned count) {
|
||||
if(0)
|
||||
for( int i = 0; i < count; ++i ) {
|
||||
memset( (unsigned*)s->pixels + (rects[i].x + rects[i].y * s->w), 0xFF, rects[i].width*4 );
|
||||
memset( (unsigned*)s->pixels + (rects[i].x + (rects[i].y + (rects[i].height-1)) * s->w), 0xFF, rects[i].width*4 );
|
||||
for( int y = 1; y < (rects[i].height-1); ++y ) {
|
||||
((unsigned*)s->pixels)[ rects[i].x + y * s->w ] =
|
||||
((unsigned*)s->pixels)[ rects[i].x + (rects[i].width-1) + y * s->w ] = 0xFFFFFFFF;
|
||||
}
|
||||
}
|
||||
|
||||
// update contents
|
||||
texture_update(&s->t, s->w, s->h, 4, s->pixels, TEXTURE_LINEAR|TEXTURE_BGRA);
|
||||
}
|
||||
|
||||
void ren_set_clip_rect(struct lt_rect rect);
|
||||
void rencache_invalidate(void);
|
||||
int lt_resizesurface(lt_surface *s, int ww, int wh) {
|
||||
s->w = ww, s->h = wh;
|
||||
if( s->t.id == 0 || s->w != s->t.w || s->h != s->t.h ) {
|
||||
// invalidate tiles
|
||||
ren_set_clip_rect( (lt_rect) { 0, 0, s->w, s->h } );
|
||||
rencache_invalidate();
|
||||
|
||||
// texture clear
|
||||
if( !s->t.id ) s->t = texture_create(1, 1, 4, " ", TEXTURE_LINEAR|TEXTURE_RGBA|TEXTURE_BYTE );
|
||||
s->pixels = REALLOC(s->pixels, s->w * s->h * 4);
|
||||
memset(s->pixels, 0, s->w * s->h * 4);
|
||||
|
||||
// texture update
|
||||
lt_updatesurfacerects(s,0,0);
|
||||
return 1; // resized
|
||||
}
|
||||
return 0; // unchanged
|
||||
}
|
||||
|
||||
void *lt_load_file(const char *filename, int *size) {
|
||||
int datalen; char *data = file_load(filename, &datalen);
|
||||
if( !data || !datalen ) {
|
||||
filename = (const char *)file_normalize(filename);
|
||||
if( strbegi(filename, app_path()) ) filename += strlen(app_path());
|
||||
data = vfs_load(filename, &datalen);
|
||||
}
|
||||
if (size) *size = 0;
|
||||
if (!data) { return NULL; }
|
||||
if (size) *size = datalen;
|
||||
// return permanent buffers here, as file_load() and vfs_load() do return temporaries
|
||||
data = memcpy(MALLOC(datalen+1), data, datalen);
|
||||
data[datalen] = 0;
|
||||
return data;
|
||||
}
|
||||
|
||||
const char* lt_button_name(int button) {
|
||||
if(button == GLFW_MOUSE_BUTTON_LEFT) return "left";
|
||||
if(button == GLFW_MOUSE_BUTTON_RIGHT) return "right";
|
||||
if(button == GLFW_MOUSE_BUTTON_MIDDLE) return "middle";
|
||||
return "?";
|
||||
}
|
||||
|
||||
char* lt_key_name(char *dst, int key, int vk, int mods) {
|
||||
// @todo: "altgr" -> left ctrl + right alt
|
||||
|
||||
if( key == GLFW_KEY_UP ) return "up";
|
||||
if( key == GLFW_KEY_DOWN ) return "down";
|
||||
if( key == GLFW_KEY_LEFT ) return "left";
|
||||
if( key == GLFW_KEY_RIGHT ) return "right";
|
||||
if( key == GLFW_KEY_LEFT_ALT ) return "left alt";
|
||||
if( key == GLFW_KEY_RIGHT_ALT ) return "right alt";
|
||||
if( key == GLFW_KEY_LEFT_SHIFT ) return "left shift";
|
||||
if( key == GLFW_KEY_RIGHT_SHIFT ) return "right shift";
|
||||
if( key == GLFW_KEY_LEFT_CONTROL ) return "left ctrl";
|
||||
if( key == GLFW_KEY_RIGHT_CONTROL ) return "right ctrl";
|
||||
if( key == GLFW_KEY_LEFT_SUPER ) return "left windows";
|
||||
if( key == GLFW_KEY_RIGHT_SUPER ) return "left windows";
|
||||
if( key == GLFW_KEY_MENU ) return "menu";
|
||||
|
||||
if( key == GLFW_KEY_ESCAPE ) return "escape";
|
||||
if( key == GLFW_KEY_BACKSPACE ) return "backspace";
|
||||
if( key == GLFW_KEY_ENTER ) return "return";
|
||||
if( key == GLFW_KEY_KP_ENTER ) return "keypad enter";
|
||||
if( key == GLFW_KEY_TAB ) return "tab";
|
||||
if( key == GLFW_KEY_CAPS_LOCK ) return "capslock";
|
||||
|
||||
if( key == GLFW_KEY_HOME ) return "home";
|
||||
if( key == GLFW_KEY_END ) return "end";
|
||||
if( key == GLFW_KEY_INSERT ) return "insert";
|
||||
if( key == GLFW_KEY_DELETE ) return "delete";
|
||||
if( key == GLFW_KEY_PAGE_UP ) return "pageup";
|
||||
if( key == GLFW_KEY_PAGE_DOWN ) return "pagedown";
|
||||
|
||||
if( key == GLFW_KEY_F1 ) return "f1";
|
||||
if( key == GLFW_KEY_F2 ) return "f2";
|
||||
if( key == GLFW_KEY_F3 ) return "f3";
|
||||
if( key == GLFW_KEY_F4 ) return "f4";
|
||||
if( key == GLFW_KEY_F5 ) return "f5";
|
||||
if( key == GLFW_KEY_F6 ) return "f6";
|
||||
if( key == GLFW_KEY_F7 ) return "f7";
|
||||
if( key == GLFW_KEY_F8 ) return "f8";
|
||||
if( key == GLFW_KEY_F9 ) return "f9";
|
||||
if( key == GLFW_KEY_F10 ) return "f10";
|
||||
if( key == GLFW_KEY_F11 ) return "f11";
|
||||
if( key == GLFW_KEY_F12 ) return "f12";
|
||||
|
||||
const char *name = glfwGetKeyName(key, vk);
|
||||
strcpy(dst, name ? name : "");
|
||||
char *p = dst;
|
||||
while (*p) {
|
||||
*p = tolower(*p);
|
||||
p++;
|
||||
}
|
||||
return dst;
|
||||
}
|
||||
|
||||
void lt_globpath(struct lua_State*L, const char *path) {
|
||||
unsigned j = 0;
|
||||
|
||||
if(!strend(path, "/")) path = (const char *)va("%s/", path);
|
||||
for( dir *d = dir_open(path, ""); d; dir_close(d), d = 0 ) {
|
||||
for( unsigned i = 0, end = dir_count(d); i < end; ++i ) {
|
||||
char *name = dir_name(d,i);
|
||||
char *last = name + strlen(name) - 1;
|
||||
if( *last == '/' ) *last = '\0';
|
||||
name = file_name(name);
|
||||
lua_pushstring(L, name);
|
||||
lua_rawseti(L, -2, ++j);
|
||||
}
|
||||
}
|
||||
|
||||
for( const char *section = strstri(path, LT_DATAPATH); section && section[sizeof(LT_DATAPATH)-1] == '/'; section = 0) {
|
||||
array(char*) list = vfs_list("**");
|
||||
for( unsigned i = 0, end = array_count(list); i < end; ++i ) {
|
||||
char *name = list[i];
|
||||
if( !strstri(name, section+1) ) continue;
|
||||
lua_pushstring(L, file_name(name));
|
||||
lua_rawseti(L, -2, ++j);
|
||||
}
|
||||
if( array_count(list) ) return;
|
||||
}
|
||||
}
|
||||
|
||||
int lt_emit_event(lua_State *L, const char *event_name, const char *event_fmt, ...) {
|
||||
int count = 0;
|
||||
lua_pushstring(L, event_name);
|
||||
if( event_fmt ) {
|
||||
va_list va;
|
||||
va_start(va, event_fmt);
|
||||
for( ; event_fmt[count]; ++count ) {
|
||||
/**/ if( event_fmt[count] == 'd' ) { int d = va_arg(va, int); lua_pushnumber(L, d); }
|
||||
else if( event_fmt[count] == 'f' ) { double f = va_arg(va, double); lua_pushnumber(L, f); }
|
||||
else if( event_fmt[count] == 's' ) { const char *s = va_arg(va, const char *); lua_pushstring(L, s); }
|
||||
}
|
||||
va_end(va);
|
||||
}
|
||||
return 1+count;
|
||||
}
|
||||
|
||||
int printi(int i) {
|
||||
// printf("clicks: %d\n", i);
|
||||
return i;
|
||||
}
|
||||
|
||||
static const char* codepoint_to_utf8_(unsigned c);
|
||||
int lt_poll_event(lua_State *L) { // init.lua > core.step() wakes on mousemoved || inputtext
|
||||
int rc = 0;
|
||||
char buf[16];
|
||||
static int prevx = 0, prevy = 0;
|
||||
|
||||
static unsigned clicks_time = 0, clicks = 0;
|
||||
if( (lt_time_ms() - clicks_time) > 400 ) clicks = 0;
|
||||
|
||||
//
|
||||
|
||||
for( GLEQevent e; gleqNextEvent(&e); gleqFreeEvent(&e) )
|
||||
if( lt_events & e.type )
|
||||
switch (e.type) {
|
||||
default:
|
||||
break; case GLEQ_WINDOW_CLOSED: // it used to be ok. depends on window_swap() flow
|
||||
rc += lt_emit_event(L, "quit", NULL);
|
||||
return rc;
|
||||
|
||||
break; case GLEQ_WINDOW_MOVED:
|
||||
lt_wx = e.pos.x;
|
||||
lt_wy = e.pos.y;
|
||||
|
||||
break; case GLEQ_WINDOW_RESIZED:
|
||||
rc += lt_emit_event(L, "resized", "dd", lt_ww = e.size.width, lt_wh = e.size.height);
|
||||
lt_resizesurface(lt_getsurface(lt_window()), lt_ww, lt_wh);
|
||||
|
||||
break; case GLEQ_WINDOW_REFRESH:
|
||||
rc += lt_emit_event(L, "exposed", NULL);
|
||||
rencache_invalidate();
|
||||
|
||||
break; case GLEQ_FILE_DROPPED:
|
||||
rc += lt_emit_event(L, "filedropped", "sdd", e.file.paths[0], lt_mx, lt_my);
|
||||
|
||||
break; case GLEQ_KEY_PRESSED:
|
||||
case GLEQ_KEY_REPEATED:
|
||||
rc += lt_emit_event(L, "keypressed", "s", lt_key_name(buf, e.keyboard.key, e.keyboard.scancode, e.keyboard.mods));
|
||||
goto bottom;
|
||||
|
||||
break; case GLEQ_KEY_RELEASED:
|
||||
rc += lt_emit_event(L, "keyreleased", "s", lt_key_name(buf, e.keyboard.key, e.keyboard.scancode, e.keyboard.mods));
|
||||
goto bottom;
|
||||
|
||||
break; case GLEQ_CODEPOINT_INPUT:
|
||||
rc += lt_emit_event(L, "textinput", "s", codepoint_to_utf8_(e.codepoint));
|
||||
|
||||
break; case GLEQ_BUTTON_PRESSED:
|
||||
rc += lt_emit_event(L, "mousepressed", "sddd", lt_button_name(e.mouse.button), lt_mx, lt_my, printi(1 + clicks));
|
||||
|
||||
break; case GLEQ_BUTTON_RELEASED:
|
||||
clicks += e.mouse.button == GLFW_MOUSE_BUTTON_1;
|
||||
clicks_time = lt_time_ms();
|
||||
rc += lt_emit_event(L, "mousereleased", "sdd", lt_button_name(e.mouse.button), lt_mx, lt_my);
|
||||
|
||||
break; case GLEQ_CURSOR_MOVED:
|
||||
lt_mx = e.pos.x - lt_wx, lt_my = e.pos.y - lt_wy;
|
||||
rc += lt_emit_event(L, "mousemoved", "dddd", lt_mx, lt_my, lt_mx - prevx, lt_my - prevy);
|
||||
prevx = lt_mx, prevy = lt_my;
|
||||
|
||||
break; case GLEQ_SCROLLED:
|
||||
rc += lt_emit_event(L, "mousewheel", "f", e.scroll.y);
|
||||
}
|
||||
|
||||
bottom:;
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
|
|
@ -1,424 +1,424 @@
|
|||
/*
|
||||
* GLEQ - A basic event queue for GLFW 3
|
||||
* Copyright © Camilla Löwy <elmindreda@glfw.org>
|
||||
*
|
||||
* This software is provided 'as-is', without any express or implied
|
||||
* warranty. In no event will the authors be held liable for any damages
|
||||
* arising from the use of this software.
|
||||
*
|
||||
* Permission is granted to anyone to use this software for any purpose,
|
||||
* including commercial applications, and to alter it and redistribute it
|
||||
* freely, subject to the following restrictions:
|
||||
*
|
||||
* 1. The origin of this software must not be misrepresented; you must not
|
||||
* claim that you wrote the original software. If you use this software
|
||||
* in a product, an acknowledgment in the product documentation would
|
||||
* be appreciated but is not required.
|
||||
*
|
||||
* 2. Altered source versions must be plainly marked as such, and must not
|
||||
* be misrepresented as being the original software.
|
||||
*
|
||||
* 3. This notice may not be removed or altered from any source
|
||||
* distribution.
|
||||
*/
|
||||
|
||||
#ifndef GLEQ_HEADER_FILE
|
||||
#define GLEQ_HEADER_FILE
|
||||
|
||||
// #include <GLFW/glfw3.h>
|
||||
|
||||
#ifdef GLEQ_STATIC
|
||||
#define GLEQDEF static
|
||||
#else
|
||||
#define GLEQDEF extern
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef enum
|
||||
{
|
||||
GLEQ_NONE = 0,
|
||||
GLEQ_WINDOW_MOVED = 1<<1,
|
||||
GLEQ_WINDOW_RESIZED = 1<<2,
|
||||
GLEQ_WINDOW_CLOSED = 1<<3,
|
||||
GLEQ_WINDOW_REFRESH = 1<<4,
|
||||
GLEQ_WINDOW_FOCUSED = 1<<5,
|
||||
GLEQ_WINDOW_DEFOCUSED = 1<<6,
|
||||
GLEQ_WINDOW_ICONIFIED = 1<<7,
|
||||
GLEQ_WINDOW_UNICONIFIED = 1<<8,
|
||||
GLEQ_FRAMEBUFFER_RESIZED = 1<<9,
|
||||
GLEQ_BUTTON_PRESSED = 1<<10,
|
||||
GLEQ_BUTTON_RELEASED = 1<<11,
|
||||
GLEQ_CURSOR_MOVED = 1<<12,
|
||||
GLEQ_CURSOR_ENTERED = 1<<13,
|
||||
GLEQ_CURSOR_LEFT = 1<<14,
|
||||
GLEQ_SCROLLED = 1<<15,
|
||||
GLEQ_KEY_PRESSED = 1<<16,
|
||||
GLEQ_KEY_REPEATED = 1<<17,
|
||||
GLEQ_KEY_RELEASED = 1<<18,
|
||||
GLEQ_CODEPOINT_INPUT = 1<<19,
|
||||
GLEQ_MONITOR_CONNECTED = 1<<20,
|
||||
GLEQ_MONITOR_DISCONNECTED = 1<<21,
|
||||
#if GLFW_VERSION_MINOR >= 1
|
||||
GLEQ_FILE_DROPPED = 1<<22,
|
||||
#endif
|
||||
#if GLFW_VERSION_MINOR >= 2
|
||||
GLEQ_JOYSTICK_CONNECTED = 1<<23,
|
||||
GLEQ_JOYSTICK_DISCONNECTED = 1<<24,
|
||||
#endif
|
||||
#if GLFW_VERSION_MINOR >= 3
|
||||
GLEQ_WINDOW_MAXIMIZED = 1<<25,
|
||||
GLEQ_WINDOW_UNMAXIMIZED = 1<<26,
|
||||
GLEQ_WINDOW_SCALE_CHANGED = 1<<27,
|
||||
#endif
|
||||
} GLEQtype;
|
||||
|
||||
typedef struct GLEQevent
|
||||
{
|
||||
unsigned/*GLEQtype*/ type;
|
||||
union {
|
||||
GLFWwindow* window;
|
||||
GLFWmonitor* monitor;
|
||||
int joystick;
|
||||
};
|
||||
union {
|
||||
struct {
|
||||
int x;
|
||||
int y;
|
||||
} pos;
|
||||
struct {
|
||||
int width;
|
||||
int height;
|
||||
} size;
|
||||
struct {
|
||||
double x;
|
||||
double y;
|
||||
} scroll;
|
||||
struct {
|
||||
int key;
|
||||
int scancode;
|
||||
int mods;
|
||||
} keyboard;
|
||||
struct {
|
||||
int button;
|
||||
int mods;
|
||||
} mouse;
|
||||
unsigned int codepoint;
|
||||
#if GLFW_VERSION_MINOR >= 1
|
||||
struct {
|
||||
char** paths;
|
||||
int count;
|
||||
} file;
|
||||
#endif
|
||||
#if GLFW_VERSION_MINOR >= 3
|
||||
struct {
|
||||
float x;
|
||||
float y;
|
||||
} scale;
|
||||
#endif
|
||||
};
|
||||
} GLEQevent;
|
||||
|
||||
GLEQDEF void gleqInit(void);
|
||||
GLEQDEF void gleqTrackWindow(GLFWwindow* window);
|
||||
|
||||
GLEQDEF int gleqNextEvent(GLEQevent* event);
|
||||
GLEQDEF void gleqFreeEvent(GLEQevent* event);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef GLEQ_IMPLEMENTATION
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
|
||||
#ifndef GLEQ_CAPACITY
|
||||
#define GLEQ_CAPACITY 1024
|
||||
#endif
|
||||
|
||||
static struct
|
||||
{
|
||||
GLEQevent events[GLEQ_CAPACITY];
|
||||
size_t head;
|
||||
size_t tail;
|
||||
} gleq_queue = { {0}, 0, 0 };
|
||||
|
||||
static char* gleq_strdup(const char* string)
|
||||
{
|
||||
const size_t size = strlen(string) + 1;
|
||||
char* result = (char*) malloc(size);
|
||||
memcpy(result, string, size);
|
||||
return result;
|
||||
}
|
||||
|
||||
static GLEQevent* gleq_new_event(void)
|
||||
{
|
||||
GLEQevent* event = gleq_queue.events + gleq_queue.head;
|
||||
gleq_queue.head = (gleq_queue.head + 1) % GLEQ_CAPACITY;
|
||||
assert(gleq_queue.head != gleq_queue.tail);
|
||||
memset(event, 0, sizeof(GLEQevent));
|
||||
return event;
|
||||
}
|
||||
|
||||
static void gleq_window_pos_callback(GLFWwindow* window, int x, int y)
|
||||
{
|
||||
GLEQevent* event = gleq_new_event();
|
||||
event->type = GLEQ_WINDOW_MOVED;
|
||||
event->window = window;
|
||||
event->pos.x = x;
|
||||
event->pos.y = y;
|
||||
}
|
||||
|
||||
static void gleq_window_size_callback(GLFWwindow* window, int width, int height)
|
||||
{
|
||||
GLEQevent* event = gleq_new_event();
|
||||
event->type = GLEQ_WINDOW_RESIZED;
|
||||
event->window = window;
|
||||
event->size.width = width;
|
||||
event->size.height = height;
|
||||
}
|
||||
|
||||
static void gleq_window_close_callback(GLFWwindow* window)
|
||||
{
|
||||
GLEQevent* event = gleq_new_event();
|
||||
event->type = GLEQ_WINDOW_CLOSED;
|
||||
event->window = window;
|
||||
}
|
||||
|
||||
static void gleq_window_refresh_callback(GLFWwindow* window)
|
||||
{
|
||||
GLEQevent* event = gleq_new_event();
|
||||
event->type = GLEQ_WINDOW_REFRESH;
|
||||
event->window = window;
|
||||
}
|
||||
|
||||
static void gleq_window_focus_callback(GLFWwindow* window, int focused)
|
||||
{
|
||||
GLEQevent* event = gleq_new_event();
|
||||
event->window = window;
|
||||
|
||||
if (focused)
|
||||
event->type = GLEQ_WINDOW_FOCUSED;
|
||||
else
|
||||
event->type = GLEQ_WINDOW_DEFOCUSED;
|
||||
}
|
||||
|
||||
static void gleq_window_iconify_callback(GLFWwindow* window, int iconified)
|
||||
{
|
||||
GLEQevent* event = gleq_new_event();
|
||||
event->window = window;
|
||||
|
||||
if (iconified)
|
||||
event->type = GLEQ_WINDOW_ICONIFIED;
|
||||
else
|
||||
event->type = GLEQ_WINDOW_UNICONIFIED;
|
||||
}
|
||||
|
||||
static void gleq_framebuffer_size_callback(GLFWwindow* window, int width, int height)
|
||||
{
|
||||
GLEQevent* event = gleq_new_event();
|
||||
event->type = GLEQ_FRAMEBUFFER_RESIZED;
|
||||
event->window = window;
|
||||
event->size.width = width;
|
||||
event->size.height = height;
|
||||
}
|
||||
|
||||
static void gleq_mouse_button_callback(GLFWwindow* window, int button, int action, int mods)
|
||||
{
|
||||
GLEQevent* event = gleq_new_event();
|
||||
event->window = window;
|
||||
event->mouse.button = button;
|
||||
event->mouse.mods = mods;
|
||||
|
||||
if (action == GLFW_PRESS)
|
||||
event->type = GLEQ_BUTTON_PRESSED;
|
||||
else if (action == GLFW_RELEASE)
|
||||
event->type = GLEQ_BUTTON_RELEASED;
|
||||
}
|
||||
|
||||
static void gleq_cursor_pos_callback(GLFWwindow* window, double x, double y)
|
||||
{
|
||||
GLEQevent* event = gleq_new_event();
|
||||
event->type = GLEQ_CURSOR_MOVED;
|
||||
event->window = window;
|
||||
event->pos.x = (int) x;
|
||||
event->pos.y = (int) y;
|
||||
}
|
||||
|
||||
static void gleq_cursor_enter_callback(GLFWwindow* window, int entered)
|
||||
{
|
||||
GLEQevent* event = gleq_new_event();
|
||||
event->window = window;
|
||||
|
||||
if (entered)
|
||||
event->type = GLEQ_CURSOR_ENTERED;
|
||||
else
|
||||
event->type = GLEQ_CURSOR_LEFT;
|
||||
}
|
||||
|
||||
static void gleq_scroll_callback(GLFWwindow* window, double x, double y)
|
||||
{
|
||||
GLEQevent* event = gleq_new_event();
|
||||
event->type = GLEQ_SCROLLED;
|
||||
event->window = window;
|
||||
event->scroll.x = x;
|
||||
event->scroll.y = y;
|
||||
}
|
||||
|
||||
static void gleq_key_callback(GLFWwindow* window, int key, int scancode, int action, int mods)
|
||||
{
|
||||
GLEQevent* event = gleq_new_event();
|
||||
event->window = window;
|
||||
event->keyboard.key = key;
|
||||
event->keyboard.scancode = scancode;
|
||||
event->keyboard.mods = mods;
|
||||
|
||||
if (action == GLFW_PRESS)
|
||||
event->type = GLEQ_KEY_PRESSED;
|
||||
else if (action == GLFW_RELEASE)
|
||||
event->type = GLEQ_KEY_RELEASED;
|
||||
else if (action == GLFW_REPEAT)
|
||||
event->type = GLEQ_KEY_REPEATED;
|
||||
}
|
||||
|
||||
static void (*gleq_char_callback_prev)(GLFWwindow* window, unsigned int codepoint) = 0;
|
||||
static void gleq_char_callback(GLFWwindow* window, unsigned int codepoint)
|
||||
{
|
||||
if( gleq_char_callback_prev )
|
||||
gleq_char_callback_prev(window, codepoint);
|
||||
|
||||
GLEQevent* event = gleq_new_event();
|
||||
event->type = GLEQ_CODEPOINT_INPUT;
|
||||
event->window = window;
|
||||
event->codepoint = codepoint;
|
||||
}
|
||||
|
||||
static void gleq_monitor_callback(GLFWmonitor* monitor, int action)
|
||||
{
|
||||
GLEQevent* event = gleq_new_event();
|
||||
event->monitor = monitor;
|
||||
|
||||
if (action == GLFW_CONNECTED)
|
||||
event->type = GLEQ_MONITOR_CONNECTED;
|
||||
else if (action == GLFW_DISCONNECTED)
|
||||
event->type = GLEQ_MONITOR_DISCONNECTED;
|
||||
}
|
||||
|
||||
#if GLFW_VERSION_MINOR >= 1
|
||||
static void gleq_file_drop_callback(GLFWwindow* window, int count, const char** paths)
|
||||
{
|
||||
GLEQevent* event = gleq_new_event();
|
||||
event->type = GLEQ_FILE_DROPPED;
|
||||
event->window = window;
|
||||
event->file.paths = (char**) malloc(count * sizeof(char*));
|
||||
event->file.count = count;
|
||||
|
||||
while (count--)
|
||||
event->file.paths[count] = gleq_strdup(paths[count]);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if GLFW_VERSION_MINOR >= 2
|
||||
static void gleq_joystick_callback(int jid, int action)
|
||||
{
|
||||
GLEQevent* event = gleq_new_event();
|
||||
event->joystick = jid;
|
||||
|
||||
if (action == GLFW_CONNECTED)
|
||||
event->type = GLEQ_JOYSTICK_CONNECTED;
|
||||
else if (action == GLFW_DISCONNECTED)
|
||||
event->type = GLEQ_JOYSTICK_DISCONNECTED;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if GLFW_VERSION_MINOR >= 3
|
||||
static void gleq_window_maximize_callback(GLFWwindow* window, int maximized)
|
||||
{
|
||||
GLEQevent* event = gleq_new_event();
|
||||
event->window = window;
|
||||
|
||||
if (maximized)
|
||||
event->type = GLEQ_WINDOW_MAXIMIZED;
|
||||
else
|
||||
event->type = GLEQ_WINDOW_UNMAXIMIZED;
|
||||
}
|
||||
|
||||
static void gleq_window_content_scale_callback(GLFWwindow* window, float xscale, float yscale)
|
||||
{
|
||||
GLEQevent* event = gleq_new_event();
|
||||
event->window = window;
|
||||
event->type = GLEQ_WINDOW_SCALE_CHANGED;
|
||||
event->scale.x = xscale;
|
||||
event->scale.y = yscale;
|
||||
}
|
||||
#endif
|
||||
|
||||
GLEQDEF void gleqInit(void)
|
||||
{
|
||||
glfwSetMonitorCallback(gleq_monitor_callback);
|
||||
#if GLFW_VERSION_MINOR >= 2
|
||||
glfwSetJoystickCallback(gleq_joystick_callback);
|
||||
#endif
|
||||
}
|
||||
|
||||
GLEQDEF void gleqTrackWindow(GLFWwindow* window)
|
||||
{
|
||||
glfwSetWindowPosCallback(window, gleq_window_pos_callback);
|
||||
glfwSetWindowSizeCallback(window, gleq_window_size_callback);
|
||||
glfwSetWindowCloseCallback(window, gleq_window_close_callback);
|
||||
glfwSetWindowRefreshCallback(window, gleq_window_refresh_callback);
|
||||
glfwSetWindowFocusCallback(window, gleq_window_focus_callback);
|
||||
glfwSetWindowIconifyCallback(window, gleq_window_iconify_callback);
|
||||
glfwSetFramebufferSizeCallback(window, gleq_framebuffer_size_callback);
|
||||
glfwSetMouseButtonCallback(window, gleq_mouse_button_callback);
|
||||
glfwSetCursorPosCallback(window, gleq_cursor_pos_callback);
|
||||
glfwSetCursorEnterCallback(window, gleq_cursor_enter_callback);
|
||||
glfwSetScrollCallback(window, gleq_scroll_callback);
|
||||
glfwSetKeyCallback(window, gleq_key_callback);
|
||||
gleq_char_callback_prev = //< @r-lyeh
|
||||
glfwSetCharCallback(window, gleq_char_callback);
|
||||
#if GLFW_VERSION_MINOR >= 1
|
||||
glfwSetDropCallback(window, gleq_file_drop_callback);
|
||||
#endif
|
||||
#if GLFW_VERSION_MINOR >= 3
|
||||
glfwSetWindowMaximizeCallback(window, gleq_window_maximize_callback);
|
||||
glfwSetWindowContentScaleCallback(window, gleq_window_content_scale_callback);
|
||||
#endif
|
||||
}
|
||||
|
||||
GLEQDEF int gleqNextEvent(GLEQevent* event)
|
||||
{
|
||||
memset(event, 0, sizeof(GLEQevent));
|
||||
|
||||
if (gleq_queue.head != gleq_queue.tail)
|
||||
{
|
||||
*event = gleq_queue.events[gleq_queue.tail];
|
||||
gleq_queue.tail = (gleq_queue.tail + 1) % GLEQ_CAPACITY;
|
||||
}
|
||||
|
||||
return event->type != GLEQ_NONE;
|
||||
}
|
||||
|
||||
GLEQDEF void gleqFreeEvent(GLEQevent* event)
|
||||
{
|
||||
#if GLFW_VERSION_MINOR >= 1
|
||||
if (event->type == GLEQ_FILE_DROPPED)
|
||||
{
|
||||
while (event->file.count--)
|
||||
free(event->file.paths[event->file.count]);
|
||||
|
||||
free(event->file.paths);
|
||||
}
|
||||
#endif
|
||||
|
||||
memset(event, 0, sizeof(GLEQevent));
|
||||
}
|
||||
|
||||
#endif /* GLEQ_IMPLEMENTATION */
|
||||
|
||||
#endif /* GLEQ_HEADER_FILE */
|
||||
/*
|
||||
* GLEQ - A basic event queue for GLFW 3
|
||||
* Copyright © Camilla Löwy <elmindreda@glfw.org>
|
||||
*
|
||||
* This software is provided 'as-is', without any express or implied
|
||||
* warranty. In no event will the authors be held liable for any damages
|
||||
* arising from the use of this software.
|
||||
*
|
||||
* Permission is granted to anyone to use this software for any purpose,
|
||||
* including commercial applications, and to alter it and redistribute it
|
||||
* freely, subject to the following restrictions:
|
||||
*
|
||||
* 1. The origin of this software must not be misrepresented; you must not
|
||||
* claim that you wrote the original software. If you use this software
|
||||
* in a product, an acknowledgment in the product documentation would
|
||||
* be appreciated but is not required.
|
||||
*
|
||||
* 2. Altered source versions must be plainly marked as such, and must not
|
||||
* be misrepresented as being the original software.
|
||||
*
|
||||
* 3. This notice may not be removed or altered from any source
|
||||
* distribution.
|
||||
*/
|
||||
|
||||
#ifndef GLEQ_HEADER_FILE
|
||||
#define GLEQ_HEADER_FILE
|
||||
|
||||
// #include <GLFW/glfw3.h>
|
||||
|
||||
#ifdef GLEQ_STATIC
|
||||
#define GLEQDEF static
|
||||
#else
|
||||
#define GLEQDEF extern
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef enum
|
||||
{
|
||||
GLEQ_NONE = 0,
|
||||
GLEQ_WINDOW_MOVED = 1<<1,
|
||||
GLEQ_WINDOW_RESIZED = 1<<2,
|
||||
GLEQ_WINDOW_CLOSED = 1<<3,
|
||||
GLEQ_WINDOW_REFRESH = 1<<4,
|
||||
GLEQ_WINDOW_FOCUSED = 1<<5,
|
||||
GLEQ_WINDOW_DEFOCUSED = 1<<6,
|
||||
GLEQ_WINDOW_ICONIFIED = 1<<7,
|
||||
GLEQ_WINDOW_UNICONIFIED = 1<<8,
|
||||
GLEQ_FRAMEBUFFER_RESIZED = 1<<9,
|
||||
GLEQ_BUTTON_PRESSED = 1<<10,
|
||||
GLEQ_BUTTON_RELEASED = 1<<11,
|
||||
GLEQ_CURSOR_MOVED = 1<<12,
|
||||
GLEQ_CURSOR_ENTERED = 1<<13,
|
||||
GLEQ_CURSOR_LEFT = 1<<14,
|
||||
GLEQ_SCROLLED = 1<<15,
|
||||
GLEQ_KEY_PRESSED = 1<<16,
|
||||
GLEQ_KEY_REPEATED = 1<<17,
|
||||
GLEQ_KEY_RELEASED = 1<<18,
|
||||
GLEQ_CODEPOINT_INPUT = 1<<19,
|
||||
GLEQ_MONITOR_CONNECTED = 1<<20,
|
||||
GLEQ_MONITOR_DISCONNECTED = 1<<21,
|
||||
#if GLFW_VERSION_MINOR >= 1
|
||||
GLEQ_FILE_DROPPED = 1<<22,
|
||||
#endif
|
||||
#if GLFW_VERSION_MINOR >= 2
|
||||
GLEQ_JOYSTICK_CONNECTED = 1<<23,
|
||||
GLEQ_JOYSTICK_DISCONNECTED = 1<<24,
|
||||
#endif
|
||||
#if GLFW_VERSION_MINOR >= 3
|
||||
GLEQ_WINDOW_MAXIMIZED = 1<<25,
|
||||
GLEQ_WINDOW_UNMAXIMIZED = 1<<26,
|
||||
GLEQ_WINDOW_SCALE_CHANGED = 1<<27,
|
||||
#endif
|
||||
} GLEQtype;
|
||||
|
||||
typedef struct GLEQevent
|
||||
{
|
||||
unsigned/*GLEQtype*/ type;
|
||||
union {
|
||||
GLFWwindow* window;
|
||||
GLFWmonitor* monitor;
|
||||
int joystick;
|
||||
};
|
||||
union {
|
||||
struct {
|
||||
int x;
|
||||
int y;
|
||||
} pos;
|
||||
struct {
|
||||
int width;
|
||||
int height;
|
||||
} size;
|
||||
struct {
|
||||
double x;
|
||||
double y;
|
||||
} scroll;
|
||||
struct {
|
||||
int key;
|
||||
int scancode;
|
||||
int mods;
|
||||
} keyboard;
|
||||
struct {
|
||||
int button;
|
||||
int mods;
|
||||
} mouse;
|
||||
unsigned int codepoint;
|
||||
#if GLFW_VERSION_MINOR >= 1
|
||||
struct {
|
||||
char** paths;
|
||||
int count;
|
||||
} file;
|
||||
#endif
|
||||
#if GLFW_VERSION_MINOR >= 3
|
||||
struct {
|
||||
float x;
|
||||
float y;
|
||||
} scale;
|
||||
#endif
|
||||
};
|
||||
} GLEQevent;
|
||||
|
||||
GLEQDEF void gleqInit(void);
|
||||
GLEQDEF void gleqTrackWindow(GLFWwindow* window);
|
||||
|
||||
GLEQDEF int gleqNextEvent(GLEQevent* event);
|
||||
GLEQDEF void gleqFreeEvent(GLEQevent* event);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef GLEQ_IMPLEMENTATION
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
|
||||
#ifndef GLEQ_CAPACITY
|
||||
#define GLEQ_CAPACITY 1024
|
||||
#endif
|
||||
|
||||
static struct
|
||||
{
|
||||
GLEQevent events[GLEQ_CAPACITY];
|
||||
size_t head;
|
||||
size_t tail;
|
||||
} gleq_queue = { {0}, 0, 0 };
|
||||
|
||||
static char* gleq_strdup(const char* string)
|
||||
{
|
||||
const size_t size = strlen(string) + 1;
|
||||
char* result = (char*) malloc(size);
|
||||
memcpy(result, string, size);
|
||||
return result;
|
||||
}
|
||||
|
||||
static GLEQevent* gleq_new_event(void)
|
||||
{
|
||||
GLEQevent* event = gleq_queue.events + gleq_queue.head;
|
||||
gleq_queue.head = (gleq_queue.head + 1) % GLEQ_CAPACITY;
|
||||
assert(gleq_queue.head != gleq_queue.tail);
|
||||
memset(event, 0, sizeof(GLEQevent));
|
||||
return event;
|
||||
}
|
||||
|
||||
static void gleq_window_pos_callback(GLFWwindow* window, int x, int y)
|
||||
{
|
||||
GLEQevent* event = gleq_new_event();
|
||||
event->type = GLEQ_WINDOW_MOVED;
|
||||
event->window = window;
|
||||
event->pos.x = x;
|
||||
event->pos.y = y;
|
||||
}
|
||||
|
||||
static void gleq_window_size_callback(GLFWwindow* window, int width, int height)
|
||||
{
|
||||
GLEQevent* event = gleq_new_event();
|
||||
event->type = GLEQ_WINDOW_RESIZED;
|
||||
event->window = window;
|
||||
event->size.width = width;
|
||||
event->size.height = height;
|
||||
}
|
||||
|
||||
static void gleq_window_close_callback(GLFWwindow* window)
|
||||
{
|
||||
GLEQevent* event = gleq_new_event();
|
||||
event->type = GLEQ_WINDOW_CLOSED;
|
||||
event->window = window;
|
||||
}
|
||||
|
||||
static void gleq_window_refresh_callback(GLFWwindow* window)
|
||||
{
|
||||
GLEQevent* event = gleq_new_event();
|
||||
event->type = GLEQ_WINDOW_REFRESH;
|
||||
event->window = window;
|
||||
}
|
||||
|
||||
static void gleq_window_focus_callback(GLFWwindow* window, int focused)
|
||||
{
|
||||
GLEQevent* event = gleq_new_event();
|
||||
event->window = window;
|
||||
|
||||
if (focused)
|
||||
event->type = GLEQ_WINDOW_FOCUSED;
|
||||
else
|
||||
event->type = GLEQ_WINDOW_DEFOCUSED;
|
||||
}
|
||||
|
||||
static void gleq_window_iconify_callback(GLFWwindow* window, int iconified)
|
||||
{
|
||||
GLEQevent* event = gleq_new_event();
|
||||
event->window = window;
|
||||
|
||||
if (iconified)
|
||||
event->type = GLEQ_WINDOW_ICONIFIED;
|
||||
else
|
||||
event->type = GLEQ_WINDOW_UNICONIFIED;
|
||||
}
|
||||
|
||||
static void gleq_framebuffer_size_callback(GLFWwindow* window, int width, int height)
|
||||
{
|
||||
GLEQevent* event = gleq_new_event();
|
||||
event->type = GLEQ_FRAMEBUFFER_RESIZED;
|
||||
event->window = window;
|
||||
event->size.width = width;
|
||||
event->size.height = height;
|
||||
}
|
||||
|
||||
static void gleq_mouse_button_callback(GLFWwindow* window, int button, int action, int mods)
|
||||
{
|
||||
GLEQevent* event = gleq_new_event();
|
||||
event->window = window;
|
||||
event->mouse.button = button;
|
||||
event->mouse.mods = mods;
|
||||
|
||||
if (action == GLFW_PRESS)
|
||||
event->type = GLEQ_BUTTON_PRESSED;
|
||||
else if (action == GLFW_RELEASE)
|
||||
event->type = GLEQ_BUTTON_RELEASED;
|
||||
}
|
||||
|
||||
static void gleq_cursor_pos_callback(GLFWwindow* window, double x, double y)
|
||||
{
|
||||
GLEQevent* event = gleq_new_event();
|
||||
event->type = GLEQ_CURSOR_MOVED;
|
||||
event->window = window;
|
||||
event->pos.x = (int) x;
|
||||
event->pos.y = (int) y;
|
||||
}
|
||||
|
||||
static void gleq_cursor_enter_callback(GLFWwindow* window, int entered)
|
||||
{
|
||||
GLEQevent* event = gleq_new_event();
|
||||
event->window = window;
|
||||
|
||||
if (entered)
|
||||
event->type = GLEQ_CURSOR_ENTERED;
|
||||
else
|
||||
event->type = GLEQ_CURSOR_LEFT;
|
||||
}
|
||||
|
||||
static void gleq_scroll_callback(GLFWwindow* window, double x, double y)
|
||||
{
|
||||
GLEQevent* event = gleq_new_event();
|
||||
event->type = GLEQ_SCROLLED;
|
||||
event->window = window;
|
||||
event->scroll.x = x;
|
||||
event->scroll.y = y;
|
||||
}
|
||||
|
||||
static void gleq_key_callback(GLFWwindow* window, int key, int scancode, int action, int mods)
|
||||
{
|
||||
GLEQevent* event = gleq_new_event();
|
||||
event->window = window;
|
||||
event->keyboard.key = key;
|
||||
event->keyboard.scancode = scancode;
|
||||
event->keyboard.mods = mods;
|
||||
|
||||
if (action == GLFW_PRESS)
|
||||
event->type = GLEQ_KEY_PRESSED;
|
||||
else if (action == GLFW_RELEASE)
|
||||
event->type = GLEQ_KEY_RELEASED;
|
||||
else if (action == GLFW_REPEAT)
|
||||
event->type = GLEQ_KEY_REPEATED;
|
||||
}
|
||||
|
||||
static void (*gleq_char_callback_prev)(GLFWwindow* window, unsigned int codepoint) = 0;
|
||||
static void gleq_char_callback(GLFWwindow* window, unsigned int codepoint)
|
||||
{
|
||||
if( gleq_char_callback_prev )
|
||||
gleq_char_callback_prev(window, codepoint);
|
||||
|
||||
GLEQevent* event = gleq_new_event();
|
||||
event->type = GLEQ_CODEPOINT_INPUT;
|
||||
event->window = window;
|
||||
event->codepoint = codepoint;
|
||||
}
|
||||
|
||||
static void gleq_monitor_callback(GLFWmonitor* monitor, int action)
|
||||
{
|
||||
GLEQevent* event = gleq_new_event();
|
||||
event->monitor = monitor;
|
||||
|
||||
if (action == GLFW_CONNECTED)
|
||||
event->type = GLEQ_MONITOR_CONNECTED;
|
||||
else if (action == GLFW_DISCONNECTED)
|
||||
event->type = GLEQ_MONITOR_DISCONNECTED;
|
||||
}
|
||||
|
||||
#if GLFW_VERSION_MINOR >= 1
|
||||
static void gleq_file_drop_callback(GLFWwindow* window, int count, const char** paths)
|
||||
{
|
||||
GLEQevent* event = gleq_new_event();
|
||||
event->type = GLEQ_FILE_DROPPED;
|
||||
event->window = window;
|
||||
event->file.paths = (char**) malloc(count * sizeof(char*));
|
||||
event->file.count = count;
|
||||
|
||||
while (count--)
|
||||
event->file.paths[count] = gleq_strdup(paths[count]);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if GLFW_VERSION_MINOR >= 2
|
||||
static void gleq_joystick_callback(int jid, int action)
|
||||
{
|
||||
GLEQevent* event = gleq_new_event();
|
||||
event->joystick = jid;
|
||||
|
||||
if (action == GLFW_CONNECTED)
|
||||
event->type = GLEQ_JOYSTICK_CONNECTED;
|
||||
else if (action == GLFW_DISCONNECTED)
|
||||
event->type = GLEQ_JOYSTICK_DISCONNECTED;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if GLFW_VERSION_MINOR >= 3
|
||||
static void gleq_window_maximize_callback(GLFWwindow* window, int maximized)
|
||||
{
|
||||
GLEQevent* event = gleq_new_event();
|
||||
event->window = window;
|
||||
|
||||
if (maximized)
|
||||
event->type = GLEQ_WINDOW_MAXIMIZED;
|
||||
else
|
||||
event->type = GLEQ_WINDOW_UNMAXIMIZED;
|
||||
}
|
||||
|
||||
static void gleq_window_content_scale_callback(GLFWwindow* window, float xscale, float yscale)
|
||||
{
|
||||
GLEQevent* event = gleq_new_event();
|
||||
event->window = window;
|
||||
event->type = GLEQ_WINDOW_SCALE_CHANGED;
|
||||
event->scale.x = xscale;
|
||||
event->scale.y = yscale;
|
||||
}
|
||||
#endif
|
||||
|
||||
GLEQDEF void gleqInit(void)
|
||||
{
|
||||
glfwSetMonitorCallback(gleq_monitor_callback);
|
||||
#if GLFW_VERSION_MINOR >= 2
|
||||
glfwSetJoystickCallback(gleq_joystick_callback);
|
||||
#endif
|
||||
}
|
||||
|
||||
GLEQDEF void gleqTrackWindow(GLFWwindow* window)
|
||||
{
|
||||
glfwSetWindowPosCallback(window, gleq_window_pos_callback);
|
||||
glfwSetWindowSizeCallback(window, gleq_window_size_callback);
|
||||
glfwSetWindowCloseCallback(window, gleq_window_close_callback);
|
||||
glfwSetWindowRefreshCallback(window, gleq_window_refresh_callback);
|
||||
glfwSetWindowFocusCallback(window, gleq_window_focus_callback);
|
||||
glfwSetWindowIconifyCallback(window, gleq_window_iconify_callback);
|
||||
glfwSetFramebufferSizeCallback(window, gleq_framebuffer_size_callback);
|
||||
glfwSetMouseButtonCallback(window, gleq_mouse_button_callback);
|
||||
glfwSetCursorPosCallback(window, gleq_cursor_pos_callback);
|
||||
glfwSetCursorEnterCallback(window, gleq_cursor_enter_callback);
|
||||
glfwSetScrollCallback(window, gleq_scroll_callback);
|
||||
glfwSetKeyCallback(window, gleq_key_callback);
|
||||
gleq_char_callback_prev = //< @r-lyeh
|
||||
glfwSetCharCallback(window, gleq_char_callback);
|
||||
#if GLFW_VERSION_MINOR >= 1
|
||||
glfwSetDropCallback(window, gleq_file_drop_callback);
|
||||
#endif
|
||||
#if GLFW_VERSION_MINOR >= 3
|
||||
glfwSetWindowMaximizeCallback(window, gleq_window_maximize_callback);
|
||||
glfwSetWindowContentScaleCallback(window, gleq_window_content_scale_callback);
|
||||
#endif
|
||||
}
|
||||
|
||||
GLEQDEF int gleqNextEvent(GLEQevent* event)
|
||||
{
|
||||
memset(event, 0, sizeof(GLEQevent));
|
||||
|
||||
if (gleq_queue.head != gleq_queue.tail)
|
||||
{
|
||||
*event = gleq_queue.events[gleq_queue.tail];
|
||||
gleq_queue.tail = (gleq_queue.tail + 1) % GLEQ_CAPACITY;
|
||||
}
|
||||
|
||||
return event->type != GLEQ_NONE;
|
||||
}
|
||||
|
||||
GLEQDEF void gleqFreeEvent(GLEQevent* event)
|
||||
{
|
||||
#if GLFW_VERSION_MINOR >= 1
|
||||
if (event->type == GLEQ_FILE_DROPPED)
|
||||
{
|
||||
while (event->file.count--)
|
||||
free(event->file.paths[event->file.count]);
|
||||
|
||||
free(event->file.paths);
|
||||
}
|
||||
#endif
|
||||
|
||||
memset(event, 0, sizeof(GLEQevent));
|
||||
}
|
||||
|
||||
#endif /* GLEQ_IMPLEMENTATION */
|
||||
|
||||
#endif /* GLEQ_HEADER_FILE */
|
||||
|
|
58480
engine/split/3rd_lua.h
58480
engine/split/3rd_lua.h
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
185074
engine/split/3rd_miniaudio.h
185074
engine/split/3rd_miniaudio.h
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -1,483 +1,483 @@
|
|||
// file browser for nuklear, based on https://github.com/vurtun/nuklear/blob/master/example/file_browser.c (public domain)
|
||||
// - rlyeh, public domain
|
||||
//
|
||||
// changelog:
|
||||
// - ported to V4K api
|
||||
// - namespaced symbols
|
||||
// - diverse win32 fixes
|
||||
// - adaptive cols/rows
|
||||
// - removed nk_begin()/nk_end() pairs
|
||||
// - dangling nk_group_begin/end() pairs
|
||||
// - simplified file<->media_group concept
|
||||
// - minor cosmetics
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <direct.h> // _getcwd()
|
||||
#else
|
||||
#include <unistd.h> // getcwd()
|
||||
#include <pwd.h> // getpwuid()
|
||||
#define _popen popen
|
||||
#define _pclose pclose
|
||||
#endif
|
||||
|
||||
const char** old_file_list(const char *cwd, const char *masks) {
|
||||
ASSERT(strend(cwd, "/"), "Error: dirs like '%s' must end with slash", cwd);
|
||||
|
||||
static __thread array(char*) list = 0;
|
||||
const char *arg0 = cwd; // app_path();
|
||||
int larg0 = strlen(arg0);
|
||||
|
||||
for( int i = 0; i < array_count(list); ++i ) {
|
||||
FREE(list[i]);
|
||||
}
|
||||
array_resize(list, 0);//array_free(list);
|
||||
|
||||
for each_substring(masks,";",it) {
|
||||
int recurse = !!strstr(it, "**");
|
||||
#if is(win32)
|
||||
char *glob = va("dir %s/b/o:n \"%s\\%s\" 2> NUL", recurse ? "/s":"", cwd, it);
|
||||
#else // linux, osx
|
||||
char *glob = va("find %s %s -name \"%s\" | sort", cwd, !recurse ? "-maxdepth 1":"-type f", it);
|
||||
#endif
|
||||
for( FILE *in = _popen(glob, "r"); in; _pclose(in), in = 0) {
|
||||
char buf[1024], *line = buf;
|
||||
while( fgets(buf, sizeof(buf), in) ) {
|
||||
// clean up
|
||||
if( strstr(line, arg0) ) line = buf + larg0;
|
||||
if( !memcmp(line, "./", 2) ) line += 2;
|
||||
int len = strlen(line); while( len > 0 && line[len-1] < 32 ) line[--len] = 0;
|
||||
if( line[0] == '\0' ) continue;
|
||||
// do not insert system folders/files
|
||||
for(int i = 0; i < len; ++i ) if(line[i] == '\\') line[i] = '/';
|
||||
if( line[0] == '.' ) if( !strcmp(line,".git") || !strcmp(line,".vs") || !strcmp(line,".") || !strcmp(line,"..") ) continue;
|
||||
if( strstr(line, "/.") ) continue;
|
||||
// insert copy
|
||||
#if is(win32)
|
||||
char *copy = STRDUP(line); // full path already provided
|
||||
#else
|
||||
// while(line[0] == '/') ++line;
|
||||
char *copy = STRDUP(va("%s%s", cwd, line)); // need to prepend path
|
||||
#endif
|
||||
array_push(list, copy);
|
||||
}
|
||||
}
|
||||
}
|
||||
array_push(list, 0); // terminator
|
||||
return (const char**)list;
|
||||
}
|
||||
|
||||
#if 1
|
||||
#define BROWSER_PRINTF(...) do {} while(0)
|
||||
#else
|
||||
#define BROWSER_PRINTF printf
|
||||
#endif
|
||||
|
||||
enum browser_groups {
|
||||
BROWSER_FOLDER,
|
||||
BROWSER_HOME,
|
||||
BROWSER_DESKTOP,
|
||||
BROWSER_COMPUTER,
|
||||
BROWSER_PROJECT,
|
||||
BROWSER_MAXFOLDERS,
|
||||
|
||||
BROWSER_MAXTYPES = 64,
|
||||
};
|
||||
|
||||
struct browser_media_group {
|
||||
unsigned icon;
|
||||
const char *extensions;
|
||||
};
|
||||
|
||||
struct browser_media {
|
||||
int font;
|
||||
int icon_sheet;
|
||||
struct nk_image custom_folders[BROWSER_MAXFOLDERS];
|
||||
struct nk_image custom_files[BROWSER_MAXTYPES];
|
||||
struct browser_media_group group[BROWSER_MAXTYPES];
|
||||
} media = {0};
|
||||
|
||||
void browser_config_dir(struct nk_image icon, unsigned counter) {
|
||||
if( counter < BROWSER_MAXFOLDERS ) {
|
||||
media.custom_folders[ counter ] = icon;
|
||||
}
|
||||
}
|
||||
|
||||
void browser_config_type(struct nk_image icon, const char *extensions) {
|
||||
static int counter = 0;
|
||||
if( counter < BROWSER_MAXTYPES ) {
|
||||
media.custom_files[ counter ] = icon;
|
||||
media.group[ counter ].icon = counter;
|
||||
media.group[ counter ].extensions = extensions;
|
||||
++counter;
|
||||
}
|
||||
}
|
||||
|
||||
#define BROWSER_MAX_PATH 512
|
||||
struct browser {
|
||||
/* path */
|
||||
char file[BROWSER_MAX_PATH]; // selection
|
||||
char directory[BROWSER_MAX_PATH]; // current cwd while browsing
|
||||
|
||||
char home[BROWSER_MAX_PATH];
|
||||
char desktop[BROWSER_MAX_PATH];
|
||||
char computer[BROWSER_MAX_PATH];
|
||||
char project[BROWSER_MAX_PATH]; // cwd when first invoked
|
||||
|
||||
/* directory content */
|
||||
array(char*) files;
|
||||
array(char*) directories;
|
||||
size_t file_count;
|
||||
size_t dir_count;
|
||||
|
||||
/* filtered directory content */
|
||||
array(char*) ffiles;
|
||||
array(char*) fdirectories;
|
||||
|
||||
/* view mode */
|
||||
bool listing;
|
||||
float zooming;
|
||||
};
|
||||
|
||||
static struct nk_image* media_icon_for_file(const char *file) {
|
||||
/* extract extension .xxx from file */
|
||||
char *ext = strrchr(file, '.');
|
||||
if( ext && strlen(ext) < 16 ) {
|
||||
char ext_dot[16+1];
|
||||
snprintf(ext_dot, 16, "%s.", ext);
|
||||
/* check for all file definition of all groups for fitting extension. skip first group (default) */
|
||||
for (int i = 1; i < BROWSER_MAXTYPES && media.group[i].extensions; ++i) {
|
||||
if( strstri(media.group[i].extensions, ext_dot) ) {
|
||||
return &media.custom_files[ media.group[i].icon ];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// return first (default) group
|
||||
return &media.custom_files[0];
|
||||
}
|
||||
|
||||
|
||||
static void browser_reload_directory_content(struct browser *browser, const char *path) {
|
||||
if(path[0] == '\0') path = va("./");
|
||||
if(!strend(path, "/")) path = va("%s/", path);
|
||||
|
||||
for(int i = 0; i < array_count(browser->files); ++i) FREE(browser->files[i]);
|
||||
for(int i = 0; i < array_count(browser->directories); ++i) FREE(browser->directories[i]);
|
||||
|
||||
array_resize(browser->files, 0);
|
||||
array_resize(browser->directories, 0);
|
||||
|
||||
BROWSER_PRINTF("searching at %s\n", path);
|
||||
|
||||
const char** list = old_file_list(path, "*");
|
||||
for( int i = 0; list[i]; ++i ) {
|
||||
|
||||
char *absolute = file_pathabs(ifndef(win32, list[i], va("%s/%s", path, list[i]))); // ../dir/./file.ext -> c:/prj/dir/file.ext
|
||||
BROWSER_PRINTF("%s->%s %d->", list[i], absolute, file_directory(absolute) );
|
||||
|
||||
if( file_directory(absolute) ) {
|
||||
// remove last '/' if present. ok to overwrite absolute var, file_*() API returns writeable strings.
|
||||
char *dir = absolute; if( dir[ strlen(dir) - 1 ] == '/' ) dir[ strlen(dir) - 1 ] = '\0';
|
||||
|
||||
dir = file_name(dir); // /home/rlyeh/prj/v4k/art -> art
|
||||
BROWSER_PRINTF("%s\n", dir);
|
||||
|
||||
if( dir[0] != '.' ) // skip special files, folders and internal files like .git or .art.zip
|
||||
array_push(browser->directories, STRDUP(dir));
|
||||
} else {
|
||||
const char *fname = file_name(absolute);
|
||||
|
||||
BROWSER_PRINTF("%s\n", fname);
|
||||
|
||||
if( fname[0] != '.' ) // skip special files, folders and internal files like .git or .art.zip
|
||||
array_push(browser->files, STRDUP(fname));
|
||||
}
|
||||
}
|
||||
|
||||
browser->file_count = array_count(browser->files);
|
||||
browser->dir_count = array_count(browser->directories);
|
||||
}
|
||||
|
||||
static void browser_chdir_and_reload_directory_content(struct browser *browser, const char *path) {
|
||||
if( path != browser->directory ) strncpy(browser->directory, path, BROWSER_MAX_PATH);
|
||||
browser_reload_directory_content(browser, path);
|
||||
}
|
||||
|
||||
static void browser_init(struct browser *browser) {
|
||||
memset(browser, 0, sizeof(*browser));
|
||||
{
|
||||
/* load files and sub-directory list */
|
||||
const char *home = getenv("HOME");
|
||||
#ifdef _WIN32
|
||||
if (!home) home = getenv("USERPROFILE");
|
||||
#else
|
||||
if (!home) home = getpwuid(getuid())->pw_dir;
|
||||
#endif
|
||||
snprintf(browser->home, BROWSER_MAX_PATH, "%s/", home);
|
||||
snprintf(browser->desktop, BROWSER_MAX_PATH, "%s/Desktop/", home);
|
||||
snprintf(browser->computer, BROWSER_MAX_PATH, "%s", ifdef(win32, va("%.*s", 3, getenv("windir")), "/"));
|
||||
{
|
||||
ifdef(win32, _getcwd, getcwd)(browser->project, sizeof(browser->project) - 1); // -1 == room for '/'
|
||||
strcat(browser->project, "/");
|
||||
}
|
||||
|
||||
BROWSER_PRINTF("%s\n", browser->home);
|
||||
BROWSER_PRINTF("%s\n", browser->desktop);
|
||||
BROWSER_PRINTF("%s\n", browser->computer);
|
||||
BROWSER_PRINTF("%s\n", browser->project);
|
||||
|
||||
browser_chdir_and_reload_directory_content(browser, browser->project);
|
||||
}
|
||||
}
|
||||
|
||||
static void browser_free(struct browser *browser) {
|
||||
for(int i = 0; i < array_count(browser->files); ++i) FREE(browser->files[i]);
|
||||
for(int i = 0; i < array_count(browser->directories); ++i) FREE(browser->directories[i]);
|
||||
array_free(browser->files);
|
||||
array_free(browser->directories);
|
||||
array_free(browser->ffiles);
|
||||
array_free(browser->fdirectories);
|
||||
|
||||
memset(browser, 0, sizeof(*browser));
|
||||
}
|
||||
|
||||
static int browser_run(struct nk_context *ctx, struct browser *browser, int windowed, struct nk_rect total_space) {
|
||||
int clicked = 0;
|
||||
|
||||
static float ratio[] = {0.25f, NK_UNDEFINED};
|
||||
|
||||
float spacing_x = ctx->style.window.spacing.x;
|
||||
|
||||
/* output path directory selector in the menubar */
|
||||
ctx->style.window.spacing.x = 0;
|
||||
if( windowed ) nk_menubar_begin(ctx);
|
||||
{
|
||||
char *d = browser->directory;
|
||||
#ifdef _WIN32
|
||||
char *begin = d;
|
||||
#else
|
||||
char *begin = d + 1;
|
||||
#endif
|
||||
nk_layout_row_template_begin(ctx, 25);
|
||||
nk_layout_row_template_push_variable(ctx, 40);
|
||||
nk_layout_row_template_push_variable(ctx, 40);
|
||||
nk_layout_row_template_push_variable(ctx, 40);
|
||||
nk_layout_row_template_end(ctx);
|
||||
|
||||
if (nk_button_label(ctx, !browser->listing ? ICON_MD_LIST : ICON_MD_GRID_VIEW)) {
|
||||
browser->listing ^= 1;
|
||||
}
|
||||
|
||||
while (*d++) {
|
||||
if (*d == '/') {
|
||||
*d = '\0';
|
||||
if (nk_button_label(ctx, va("%s" ICON_MD_ARROW_RIGHT, file_name(begin)))) {
|
||||
*d++ = '/'; *d = '\0';
|
||||
browser_chdir_and_reload_directory_content(browser, browser->directory);
|
||||
break;
|
||||
}
|
||||
*d = '/';
|
||||
begin = d + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
if( windowed ) nk_menubar_end(ctx);
|
||||
ctx->style.window.spacing.x = spacing_x;
|
||||
|
||||
if(nk_window_has_focus(ctx)) {
|
||||
browser->zooming = clampf( browser->zooming + (input(KEY_LCTRL) || input(KEY_RCTRL)) * input_diff(MOUSE_W) * 0.1, 1, 3);
|
||||
}
|
||||
|
||||
bool compact = 0, tiny = browser->listing; // compact, no left panel. tiny, no large icons
|
||||
size_t cols = total_space.w / (100 * browser->zooming);
|
||||
int icon_height = (67 * browser->zooming) * (tiny ? 0.33 : 1.); // icon height (96) + button padding (??). originally: 135
|
||||
/**/ if( tiny ) cols = (int)cols+1.5, cols /= 2, compact = total_space.w < 500; // cols <= 2;
|
||||
else cols = (int)cols+1, compact = total_space.w < 500; // cols <= 5;
|
||||
if( cols < 1 ) cols=1;
|
||||
|
||||
/* window layout */
|
||||
nk_layout_row(ctx, NK_DYNAMIC, total_space.h, compact ? 1 : 2, compact ? ratio+1 : ratio);
|
||||
if( !compact )
|
||||
if( nk_group_begin(ctx, "Special", NK_WINDOW_NO_SCROLLBAR) ) {
|
||||
nk_layout_row_dynamic(ctx, 40, 1);
|
||||
|
||||
if (nk_button_image_label(ctx,media.custom_folders[BROWSER_HOME],"Home",NK_TEXT_RIGHT))
|
||||
browser_chdir_and_reload_directory_content(browser, browser->home);
|
||||
|
||||
if (nk_button_image_label(ctx,media.custom_folders[BROWSER_DESKTOP],"Desktop",NK_TEXT_RIGHT))
|
||||
browser_chdir_and_reload_directory_content(browser, browser->desktop);
|
||||
|
||||
if (nk_button_image_label(ctx,media.custom_folders[BROWSER_COMPUTER],"Computer",NK_TEXT_RIGHT))
|
||||
browser_chdir_and_reload_directory_content(browser, browser->computer);
|
||||
|
||||
if (nk_button_image_label(ctx,media.custom_folders[BROWSER_PROJECT],"Project",NK_TEXT_RIGHT))
|
||||
browser_chdir_and_reload_directory_content(browser, browser->project);
|
||||
|
||||
nk_group_end(ctx);
|
||||
}
|
||||
|
||||
/* output directory content window */
|
||||
if(nk_group_begin(ctx, "Content", windowed ? NK_WINDOW_NO_SCROLLBAR : 0)) {
|
||||
|
||||
array(char*) *directories = &browser->directories;
|
||||
array(char*) *files = &browser->files;
|
||||
|
||||
if( ui_filter && ui_filter[0] ) {
|
||||
array_resize(browser->fdirectories, 0);
|
||||
array_resize(browser->ffiles, 0);
|
||||
|
||||
for each_array(browser->directories,char*,k)
|
||||
if( strstri(k, ui_filter) )
|
||||
array_push(browser->fdirectories, k);
|
||||
|
||||
for each_array(browser->files,char*,k)
|
||||
if( strstri(k, ui_filter) )
|
||||
array_push(browser->ffiles, k);
|
||||
|
||||
directories = &browser->fdirectories;
|
||||
files = &browser->ffiles;
|
||||
}
|
||||
|
||||
int dir_count = array_count(*directories);
|
||||
int file_count = array_count(*files);
|
||||
|
||||
int index = -1;
|
||||
size_t i = 0, j = 0, k = 0;
|
||||
size_t rows = 0;
|
||||
size_t count = dir_count + file_count;
|
||||
|
||||
rows = count / cols;
|
||||
for (i = 0; i <= rows; i += 1) {
|
||||
if(!tiny)
|
||||
{size_t n = j + cols;
|
||||
nk_layout_row_dynamic(ctx, icon_height, (int)cols);
|
||||
for (; j < count && j < n; ++j) {
|
||||
size_t t = j-dir_count;
|
||||
|
||||
/* draw one row of icons */
|
||||
if (j < dir_count) {
|
||||
/* draw and execute directory buttons */
|
||||
if (nk_button_image(ctx,media.custom_folders[BROWSER_FOLDER]))
|
||||
index = (int)j;
|
||||
} else {
|
||||
/* draw and execute files buttons */
|
||||
struct nk_image *icon;
|
||||
size_t fileIndex = ((size_t)j - dir_count);
|
||||
icon = media_icon_for_file((*files)[fileIndex]);
|
||||
if (nk_button_image(ctx, *icon)) {
|
||||
snprintf(browser->file, BROWSER_MAX_PATH, "%s%s", browser->directory, browser->files[fileIndex]);
|
||||
clicked = 1;
|
||||
}
|
||||
}
|
||||
}}
|
||||
if(!tiny)
|
||||
{size_t n = k + cols;
|
||||
nk_layout_row_dynamic(ctx, 20, (int)cols);
|
||||
for (; k < count && k < n; k++) {
|
||||
size_t t = k-dir_count;
|
||||
|
||||
/* draw one row of labels */
|
||||
if (k < dir_count) {
|
||||
nk_label(ctx, (*directories)[k], NK_TEXT_CENTERED);
|
||||
} else {
|
||||
nk_label(ctx, (*files)[t], NK_TEXT_CENTERED);
|
||||
}
|
||||
}}
|
||||
if(tiny)
|
||||
{size_t n = j + cols;
|
||||
nk_layout_row_dynamic(ctx, icon_height, (int)cols);
|
||||
for (; j < count && j < n; ++j) {
|
||||
size_t t = j-dir_count;
|
||||
|
||||
/* draw one row of icons */
|
||||
if (j < dir_count) {
|
||||
/* draw and execute directory buttons */
|
||||
if (nk_button_image_label(ctx,media.custom_folders[BROWSER_FOLDER], (*directories)[j], NK_TEXT_RIGHT))
|
||||
index = (int)j;
|
||||
} else {
|
||||
/* draw and execute files buttons */
|
||||
struct nk_image *icon;
|
||||
size_t fileIndex = ((size_t)j - dir_count);
|
||||
icon = media_icon_for_file((*files)[fileIndex]);
|
||||
if (nk_button_image_label(ctx, *icon, (*files)[t],NK_TEXT_RIGHT)) {
|
||||
snprintf(browser->file, BROWSER_MAX_PATH, "%s%s", browser->directory, browser->files[fileIndex]);
|
||||
clicked = 1;
|
||||
}
|
||||
}
|
||||
#if 0
|
||||
bool has_focus = nk_window_has_focus(ctx); // @fixme: move out of loop
|
||||
bool has_popups = ui_popups(); // @fixme: move out of loop
|
||||
if( !has_popups && has_focus ) {
|
||||
struct nk_rect bounds = nk_widget_bounds(ctx);
|
||||
if (nk_input_is_mouse_hovering_rect(&ctx->input, bounds) ) {
|
||||
|
||||
char *name = j < dir_count ? (*directories)[j] : (*files)[j-dir_count];
|
||||
|
||||
char fullpath[PATH_MAX];
|
||||
snprintf(fullpath, PATH_MAX, "%s%s", browser->directory, name);
|
||||
|
||||
struct stat t = {0};
|
||||
if( stat( fullpath, &t ) != -1 ) {
|
||||
char tooltip[256];
|
||||
snprintf(tooltip, 256,
|
||||
"Path: %s\n"
|
||||
"Type: %lld\n" // file type and mode
|
||||
"Size: %lld\n" // file size
|
||||
"Owner: %lld\n" // user ID of file owner
|
||||
"Modified: %s (%lld)", // last modification date
|
||||
name, (int64_t)t.st_mode, (int64_t)t.st_size, (int64_t)t.st_uid, ctime(&t.st_mtime), (int64_t)t.st_mtime
|
||||
);
|
||||
nk_tooltip(ctx, tooltip);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}}
|
||||
}
|
||||
|
||||
if (index != -1) {
|
||||
BROWSER_PRINTF("%s + %s = ", browser->directory, browser->directories[index]);
|
||||
size_t n = strlen(browser->directory);
|
||||
snprintf(browser->directory + n, BROWSER_MAX_PATH - n, "%s/", browser->directories[index]);
|
||||
BROWSER_PRINTF("%s\n", browser->directory);
|
||||
browser_chdir_and_reload_directory_content(browser, browser->directory);
|
||||
}
|
||||
nk_group_end(ctx);
|
||||
}
|
||||
|
||||
return clicked;
|
||||
}
|
||||
|
||||
static struct nk_image icon_load(const char *filename) {
|
||||
texture_t t = texture(filename, 0);
|
||||
return nk_image_id((int)t.id);
|
||||
}
|
||||
|
||||
static struct nk_image icon_load_rect(unsigned id, unsigned w, unsigned h, unsigned wcell, unsigned hcell, unsigned col, unsigned row) {
|
||||
return nk_subimage_id((int)id, w, h, (struct nk_rect){ wcell * col, hcell * row, wcell, hcell });
|
||||
}
|
||||
|
||||
|
||||
/* demo:
|
||||
|
||||
struct browser browser = {0};
|
||||
browser_init(&browser);
|
||||
browser_config_dir(nk_image, BROWSER_HOME);
|
||||
browser_config_dir(nk_image, BROWSER_PROJECT);
|
||||
// [...]
|
||||
browser_config_type(nk_image, ".ext1.ext2.ext3.");
|
||||
browser_config_type(nk_image, ".ext1.ext2.ext3.");
|
||||
browser_config_type(nk_image, ".ext1.ext2.ext3.");
|
||||
// [...]
|
||||
|
||||
[...]
|
||||
|
||||
if( nk_begin(ctx, "window", ...) ) {
|
||||
struct nk_rect total_space = nk_window_get_content_region(ctx);
|
||||
if( browser_run(ctx, &browser, 0, total_space) ) {
|
||||
puts( browser->directory );
|
||||
puts( browser->file );
|
||||
}
|
||||
}
|
||||
nk_end();
|
||||
*/
|
||||
// file browser for nuklear, based on https://github.com/vurtun/nuklear/blob/master/example/file_browser.c (public domain)
|
||||
// - rlyeh, public domain
|
||||
//
|
||||
// changelog:
|
||||
// - ported to V4K api
|
||||
// - namespaced symbols
|
||||
// - diverse win32 fixes
|
||||
// - adaptive cols/rows
|
||||
// - removed nk_begin()/nk_end() pairs
|
||||
// - dangling nk_group_begin/end() pairs
|
||||
// - simplified file<->media_group concept
|
||||
// - minor cosmetics
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <direct.h> // _getcwd()
|
||||
#else
|
||||
#include <unistd.h> // getcwd()
|
||||
#include <pwd.h> // getpwuid()
|
||||
#define _popen popen
|
||||
#define _pclose pclose
|
||||
#endif
|
||||
|
||||
const char** old_file_list(const char *cwd, const char *masks) {
|
||||
ASSERT(strend(cwd, "/"), "Error: dirs like '%s' must end with slash", cwd);
|
||||
|
||||
static __thread array(char*) list = 0;
|
||||
const char *arg0 = cwd; // app_path();
|
||||
int larg0 = strlen(arg0);
|
||||
|
||||
for( int i = 0; i < array_count(list); ++i ) {
|
||||
FREE(list[i]);
|
||||
}
|
||||
array_resize(list, 0);//array_free(list);
|
||||
|
||||
for each_substring(masks,";",it) {
|
||||
int recurse = !!strstr(it, "**");
|
||||
#if is(win32)
|
||||
char *glob = va("dir %s/b/o:n \"%s\\%s\" 2> NUL", recurse ? "/s":"", cwd, it);
|
||||
#else // linux, osx
|
||||
char *glob = va("find %s %s -name \"%s\" | sort", cwd, !recurse ? "-maxdepth 1":"-type f", it);
|
||||
#endif
|
||||
for( FILE *in = _popen(glob, "r"); in; _pclose(in), in = 0) {
|
||||
char buf[1024], *line = buf;
|
||||
while( fgets(buf, sizeof(buf), in) ) {
|
||||
// clean up
|
||||
if( strstr(line, arg0) ) line = buf + larg0;
|
||||
if( !memcmp(line, "./", 2) ) line += 2;
|
||||
int len = strlen(line); while( len > 0 && line[len-1] < 32 ) line[--len] = 0;
|
||||
if( line[0] == '\0' ) continue;
|
||||
// do not insert system folders/files
|
||||
for(int i = 0; i < len; ++i ) if(line[i] == '\\') line[i] = '/';
|
||||
if( line[0] == '.' ) if( !strcmp(line,".git") || !strcmp(line,".vs") || !strcmp(line,".") || !strcmp(line,"..") ) continue;
|
||||
if( strstr(line, "/.") ) continue;
|
||||
// insert copy
|
||||
#if is(win32)
|
||||
char *copy = STRDUP(line); // full path already provided
|
||||
#else
|
||||
// while(line[0] == '/') ++line;
|
||||
char *copy = STRDUP(va("%s%s", cwd, line)); // need to prepend path
|
||||
#endif
|
||||
array_push(list, copy);
|
||||
}
|
||||
}
|
||||
}
|
||||
array_push(list, 0); // terminator
|
||||
return (const char**)list;
|
||||
}
|
||||
|
||||
#if 1
|
||||
#define BROWSER_PRINTF(...) do {} while(0)
|
||||
#else
|
||||
#define BROWSER_PRINTF printf
|
||||
#endif
|
||||
|
||||
enum browser_groups {
|
||||
BROWSER_FOLDER,
|
||||
BROWSER_HOME,
|
||||
BROWSER_DESKTOP,
|
||||
BROWSER_COMPUTER,
|
||||
BROWSER_PROJECT,
|
||||
BROWSER_MAXFOLDERS,
|
||||
|
||||
BROWSER_MAXTYPES = 64,
|
||||
};
|
||||
|
||||
struct browser_media_group {
|
||||
unsigned icon;
|
||||
const char *extensions;
|
||||
};
|
||||
|
||||
struct browser_media {
|
||||
int font;
|
||||
int icon_sheet;
|
||||
struct nk_image custom_folders[BROWSER_MAXFOLDERS];
|
||||
struct nk_image custom_files[BROWSER_MAXTYPES];
|
||||
struct browser_media_group group[BROWSER_MAXTYPES];
|
||||
} media = {0};
|
||||
|
||||
void browser_config_dir(struct nk_image icon, unsigned counter) {
|
||||
if( counter < BROWSER_MAXFOLDERS ) {
|
||||
media.custom_folders[ counter ] = icon;
|
||||
}
|
||||
}
|
||||
|
||||
void browser_config_type(struct nk_image icon, const char *extensions) {
|
||||
static int counter = 0;
|
||||
if( counter < BROWSER_MAXTYPES ) {
|
||||
media.custom_files[ counter ] = icon;
|
||||
media.group[ counter ].icon = counter;
|
||||
media.group[ counter ].extensions = extensions;
|
||||
++counter;
|
||||
}
|
||||
}
|
||||
|
||||
#define BROWSER_MAX_PATH 512
|
||||
struct browser {
|
||||
/* path */
|
||||
char file[BROWSER_MAX_PATH]; // selection
|
||||
char directory[BROWSER_MAX_PATH]; // current cwd while browsing
|
||||
|
||||
char home[BROWSER_MAX_PATH];
|
||||
char desktop[BROWSER_MAX_PATH];
|
||||
char computer[BROWSER_MAX_PATH];
|
||||
char project[BROWSER_MAX_PATH]; // cwd when first invoked
|
||||
|
||||
/* directory content */
|
||||
array(char*) files;
|
||||
array(char*) directories;
|
||||
size_t file_count;
|
||||
size_t dir_count;
|
||||
|
||||
/* filtered directory content */
|
||||
array(char*) ffiles;
|
||||
array(char*) fdirectories;
|
||||
|
||||
/* view mode */
|
||||
bool listing;
|
||||
float zooming;
|
||||
};
|
||||
|
||||
static struct nk_image* media_icon_for_file(const char *file) {
|
||||
/* extract extension .xxx from file */
|
||||
char *ext = strrchr(file, '.');
|
||||
if( ext && strlen(ext) < 16 ) {
|
||||
char ext_dot[16+1];
|
||||
snprintf(ext_dot, 16, "%s.", ext);
|
||||
/* check for all file definition of all groups for fitting extension. skip first group (default) */
|
||||
for (int i = 1; i < BROWSER_MAXTYPES && media.group[i].extensions; ++i) {
|
||||
if( strstri(media.group[i].extensions, ext_dot) ) {
|
||||
return &media.custom_files[ media.group[i].icon ];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// return first (default) group
|
||||
return &media.custom_files[0];
|
||||
}
|
||||
|
||||
|
||||
static void browser_reload_directory_content(struct browser *browser, const char *path) {
|
||||
if(path[0] == '\0') path = va("./");
|
||||
if(!strend(path, "/")) path = va("%s/", path);
|
||||
|
||||
for(int i = 0; i < array_count(browser->files); ++i) FREE(browser->files[i]);
|
||||
for(int i = 0; i < array_count(browser->directories); ++i) FREE(browser->directories[i]);
|
||||
|
||||
array_resize(browser->files, 0);
|
||||
array_resize(browser->directories, 0);
|
||||
|
||||
BROWSER_PRINTF("searching at %s\n", path);
|
||||
|
||||
const char** list = old_file_list(path, "*");
|
||||
for( int i = 0; list[i]; ++i ) {
|
||||
|
||||
char *absolute = file_pathabs(ifndef(win32, list[i], va("%s/%s", path, list[i]))); // ../dir/./file.ext -> c:/prj/dir/file.ext
|
||||
BROWSER_PRINTF("%s->%s %d->", list[i], absolute, file_directory(absolute) );
|
||||
|
||||
if( file_directory(absolute) ) {
|
||||
// remove last '/' if present. ok to overwrite absolute var, file_*() API returns writeable strings.
|
||||
char *dir = absolute; if( dir[ strlen(dir) - 1 ] == '/' ) dir[ strlen(dir) - 1 ] = '\0';
|
||||
|
||||
dir = file_name(dir); // /home/rlyeh/prj/v4k/art -> art
|
||||
BROWSER_PRINTF("%s\n", dir);
|
||||
|
||||
if( dir[0] != '.' ) // skip special files, folders and internal files like .git or .art.zip
|
||||
array_push(browser->directories, STRDUP(dir));
|
||||
} else {
|
||||
const char *fname = file_name(absolute);
|
||||
|
||||
BROWSER_PRINTF("%s\n", fname);
|
||||
|
||||
if( fname[0] != '.' ) // skip special files, folders and internal files like .git or .art.zip
|
||||
array_push(browser->files, STRDUP(fname));
|
||||
}
|
||||
}
|
||||
|
||||
browser->file_count = array_count(browser->files);
|
||||
browser->dir_count = array_count(browser->directories);
|
||||
}
|
||||
|
||||
static void browser_chdir_and_reload_directory_content(struct browser *browser, const char *path) {
|
||||
if( path != browser->directory ) strncpy(browser->directory, path, BROWSER_MAX_PATH);
|
||||
browser_reload_directory_content(browser, path);
|
||||
}
|
||||
|
||||
static void browser_init(struct browser *browser) {
|
||||
memset(browser, 0, sizeof(*browser));
|
||||
{
|
||||
/* load files and sub-directory list */
|
||||
const char *home = getenv("HOME");
|
||||
#ifdef _WIN32
|
||||
if (!home) home = getenv("USERPROFILE");
|
||||
#else
|
||||
if (!home) home = getpwuid(getuid())->pw_dir;
|
||||
#endif
|
||||
snprintf(browser->home, BROWSER_MAX_PATH, "%s/", home);
|
||||
snprintf(browser->desktop, BROWSER_MAX_PATH, "%s/Desktop/", home);
|
||||
snprintf(browser->computer, BROWSER_MAX_PATH, "%s", ifdef(win32, va("%.*s", 3, getenv("windir")), "/"));
|
||||
{
|
||||
ifdef(win32, _getcwd, getcwd)(browser->project, sizeof(browser->project) - 1); // -1 == room for '/'
|
||||
strcat(browser->project, "/");
|
||||
}
|
||||
|
||||
BROWSER_PRINTF("%s\n", browser->home);
|
||||
BROWSER_PRINTF("%s\n", browser->desktop);
|
||||
BROWSER_PRINTF("%s\n", browser->computer);
|
||||
BROWSER_PRINTF("%s\n", browser->project);
|
||||
|
||||
browser_chdir_and_reload_directory_content(browser, browser->project);
|
||||
}
|
||||
}
|
||||
|
||||
static void browser_free(struct browser *browser) {
|
||||
for(int i = 0; i < array_count(browser->files); ++i) FREE(browser->files[i]);
|
||||
for(int i = 0; i < array_count(browser->directories); ++i) FREE(browser->directories[i]);
|
||||
array_free(browser->files);
|
||||
array_free(browser->directories);
|
||||
array_free(browser->ffiles);
|
||||
array_free(browser->fdirectories);
|
||||
|
||||
memset(browser, 0, sizeof(*browser));
|
||||
}
|
||||
|
||||
static int browser_run(struct nk_context *ctx, struct browser *browser, int windowed, struct nk_rect total_space) {
|
||||
int clicked = 0;
|
||||
|
||||
static float ratio[] = {0.25f, NK_UNDEFINED};
|
||||
|
||||
float spacing_x = ctx->style.window.spacing.x;
|
||||
|
||||
/* output path directory selector in the menubar */
|
||||
ctx->style.window.spacing.x = 0;
|
||||
if( windowed ) nk_menubar_begin(ctx);
|
||||
{
|
||||
char *d = browser->directory;
|
||||
#ifdef _WIN32
|
||||
char *begin = d;
|
||||
#else
|
||||
char *begin = d + 1;
|
||||
#endif
|
||||
nk_layout_row_template_begin(ctx, 25);
|
||||
nk_layout_row_template_push_variable(ctx, 40);
|
||||
nk_layout_row_template_push_variable(ctx, 40);
|
||||
nk_layout_row_template_push_variable(ctx, 40);
|
||||
nk_layout_row_template_end(ctx);
|
||||
|
||||
if (nk_button_label(ctx, !browser->listing ? ICON_MD_LIST : ICON_MD_GRID_VIEW)) {
|
||||
browser->listing ^= 1;
|
||||
}
|
||||
|
||||
while (*d++) {
|
||||
if (*d == '/') {
|
||||
*d = '\0';
|
||||
if (nk_button_label(ctx, va("%s" ICON_MD_ARROW_RIGHT, file_name(begin)))) {
|
||||
*d++ = '/'; *d = '\0';
|
||||
browser_chdir_and_reload_directory_content(browser, browser->directory);
|
||||
break;
|
||||
}
|
||||
*d = '/';
|
||||
begin = d + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
if( windowed ) nk_menubar_end(ctx);
|
||||
ctx->style.window.spacing.x = spacing_x;
|
||||
|
||||
if(nk_window_has_focus(ctx)) {
|
||||
browser->zooming = clampf( browser->zooming + (input(KEY_LCTRL) || input(KEY_RCTRL)) * input_diff(MOUSE_W) * 0.1, 1, 3);
|
||||
}
|
||||
|
||||
bool compact = 0, tiny = browser->listing; // compact, no left panel. tiny, no large icons
|
||||
size_t cols = total_space.w / (100 * browser->zooming);
|
||||
int icon_height = (67 * browser->zooming) * (tiny ? 0.33 : 1.); // icon height (96) + button padding (??). originally: 135
|
||||
/**/ if( tiny ) cols = (int)cols+1.5, cols /= 2, compact = total_space.w < 500; // cols <= 2;
|
||||
else cols = (int)cols+1, compact = total_space.w < 500; // cols <= 5;
|
||||
if( cols < 1 ) cols=1;
|
||||
|
||||
/* window layout */
|
||||
nk_layout_row(ctx, NK_DYNAMIC, total_space.h, compact ? 1 : 2, compact ? ratio+1 : ratio);
|
||||
if( !compact )
|
||||
if( nk_group_begin(ctx, "Special", NK_WINDOW_NO_SCROLLBAR) ) {
|
||||
nk_layout_row_dynamic(ctx, 40, 1);
|
||||
|
||||
if (nk_button_image_label(ctx,media.custom_folders[BROWSER_HOME],"Home",NK_TEXT_RIGHT))
|
||||
browser_chdir_and_reload_directory_content(browser, browser->home);
|
||||
|
||||
if (nk_button_image_label(ctx,media.custom_folders[BROWSER_DESKTOP],"Desktop",NK_TEXT_RIGHT))
|
||||
browser_chdir_and_reload_directory_content(browser, browser->desktop);
|
||||
|
||||
if (nk_button_image_label(ctx,media.custom_folders[BROWSER_COMPUTER],"Computer",NK_TEXT_RIGHT))
|
||||
browser_chdir_and_reload_directory_content(browser, browser->computer);
|
||||
|
||||
if (nk_button_image_label(ctx,media.custom_folders[BROWSER_PROJECT],"Project",NK_TEXT_RIGHT))
|
||||
browser_chdir_and_reload_directory_content(browser, browser->project);
|
||||
|
||||
nk_group_end(ctx);
|
||||
}
|
||||
|
||||
/* output directory content window */
|
||||
if(nk_group_begin(ctx, "Content", windowed ? NK_WINDOW_NO_SCROLLBAR : 0)) {
|
||||
|
||||
array(char*) *directories = &browser->directories;
|
||||
array(char*) *files = &browser->files;
|
||||
|
||||
if( ui_filter && ui_filter[0] ) {
|
||||
array_resize(browser->fdirectories, 0);
|
||||
array_resize(browser->ffiles, 0);
|
||||
|
||||
for each_array(browser->directories,char*,k)
|
||||
if( strstri(k, ui_filter) )
|
||||
array_push(browser->fdirectories, k);
|
||||
|
||||
for each_array(browser->files,char*,k)
|
||||
if( strstri(k, ui_filter) )
|
||||
array_push(browser->ffiles, k);
|
||||
|
||||
directories = &browser->fdirectories;
|
||||
files = &browser->ffiles;
|
||||
}
|
||||
|
||||
int dir_count = array_count(*directories);
|
||||
int file_count = array_count(*files);
|
||||
|
||||
int index = -1;
|
||||
size_t i = 0, j = 0, k = 0;
|
||||
size_t rows = 0;
|
||||
size_t count = dir_count + file_count;
|
||||
|
||||
rows = count / cols;
|
||||
for (i = 0; i <= rows; i += 1) {
|
||||
if(!tiny)
|
||||
{size_t n = j + cols;
|
||||
nk_layout_row_dynamic(ctx, icon_height, (int)cols);
|
||||
for (; j < count && j < n; ++j) {
|
||||
size_t t = j-dir_count;
|
||||
|
||||
/* draw one row of icons */
|
||||
if (j < dir_count) {
|
||||
/* draw and execute directory buttons */
|
||||
if (nk_button_image(ctx,media.custom_folders[BROWSER_FOLDER]))
|
||||
index = (int)j;
|
||||
} else {
|
||||
/* draw and execute files buttons */
|
||||
struct nk_image *icon;
|
||||
size_t fileIndex = ((size_t)j - dir_count);
|
||||
icon = media_icon_for_file((*files)[fileIndex]);
|
||||
if (nk_button_image(ctx, *icon)) {
|
||||
snprintf(browser->file, BROWSER_MAX_PATH, "%s%s", browser->directory, browser->files[fileIndex]);
|
||||
clicked = 1;
|
||||
}
|
||||
}
|
||||
}}
|
||||
if(!tiny)
|
||||
{size_t n = k + cols;
|
||||
nk_layout_row_dynamic(ctx, 20, (int)cols);
|
||||
for (; k < count && k < n; k++) {
|
||||
size_t t = k-dir_count;
|
||||
|
||||
/* draw one row of labels */
|
||||
if (k < dir_count) {
|
||||
nk_label(ctx, (*directories)[k], NK_TEXT_CENTERED);
|
||||
} else {
|
||||
nk_label(ctx, (*files)[t], NK_TEXT_CENTERED);
|
||||
}
|
||||
}}
|
||||
if(tiny)
|
||||
{size_t n = j + cols;
|
||||
nk_layout_row_dynamic(ctx, icon_height, (int)cols);
|
||||
for (; j < count && j < n; ++j) {
|
||||
size_t t = j-dir_count;
|
||||
|
||||
/* draw one row of icons */
|
||||
if (j < dir_count) {
|
||||
/* draw and execute directory buttons */
|
||||
if (nk_button_image_label(ctx,media.custom_folders[BROWSER_FOLDER], (*directories)[j], NK_TEXT_RIGHT))
|
||||
index = (int)j;
|
||||
} else {
|
||||
/* draw and execute files buttons */
|
||||
struct nk_image *icon;
|
||||
size_t fileIndex = ((size_t)j - dir_count);
|
||||
icon = media_icon_for_file((*files)[fileIndex]);
|
||||
if (nk_button_image_label(ctx, *icon, (*files)[t],NK_TEXT_RIGHT)) {
|
||||
snprintf(browser->file, BROWSER_MAX_PATH, "%s%s", browser->directory, browser->files[fileIndex]);
|
||||
clicked = 1;
|
||||
}
|
||||
}
|
||||
#if 0
|
||||
bool has_focus = nk_window_has_focus(ctx); // @fixme: move out of loop
|
||||
bool has_popups = ui_popups(); // @fixme: move out of loop
|
||||
if( !has_popups && has_focus ) {
|
||||
struct nk_rect bounds = nk_widget_bounds(ctx);
|
||||
if (nk_input_is_mouse_hovering_rect(&ctx->input, bounds) ) {
|
||||
|
||||
char *name = j < dir_count ? (*directories)[j] : (*files)[j-dir_count];
|
||||
|
||||
char fullpath[PATH_MAX];
|
||||
snprintf(fullpath, PATH_MAX, "%s%s", browser->directory, name);
|
||||
|
||||
struct stat t = {0};
|
||||
if( stat( fullpath, &t ) != -1 ) {
|
||||
char tooltip[256];
|
||||
snprintf(tooltip, 256,
|
||||
"Path: %s\n"
|
||||
"Type: %lld\n" // file type and mode
|
||||
"Size: %lld\n" // file size
|
||||
"Owner: %lld\n" // user ID of file owner
|
||||
"Modified: %s (%lld)", // last modification date
|
||||
name, (int64_t)t.st_mode, (int64_t)t.st_size, (int64_t)t.st_uid, ctime(&t.st_mtime), (int64_t)t.st_mtime
|
||||
);
|
||||
nk_tooltip(ctx, tooltip);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}}
|
||||
}
|
||||
|
||||
if (index != -1) {
|
||||
BROWSER_PRINTF("%s + %s = ", browser->directory, browser->directories[index]);
|
||||
size_t n = strlen(browser->directory);
|
||||
snprintf(browser->directory + n, BROWSER_MAX_PATH - n, "%s/", browser->directories[index]);
|
||||
BROWSER_PRINTF("%s\n", browser->directory);
|
||||
browser_chdir_and_reload_directory_content(browser, browser->directory);
|
||||
}
|
||||
nk_group_end(ctx);
|
||||
}
|
||||
|
||||
return clicked;
|
||||
}
|
||||
|
||||
static struct nk_image icon_load(const char *filename) {
|
||||
texture_t t = texture(filename, 0);
|
||||
return nk_image_id((int)t.id);
|
||||
}
|
||||
|
||||
static struct nk_image icon_load_rect(unsigned id, unsigned w, unsigned h, unsigned wcell, unsigned hcell, unsigned col, unsigned row) {
|
||||
return nk_subimage_id((int)id, w, h, (struct nk_rect){ wcell * col, hcell * row, wcell, hcell });
|
||||
}
|
||||
|
||||
|
||||
/* demo:
|
||||
|
||||
struct browser browser = {0};
|
||||
browser_init(&browser);
|
||||
browser_config_dir(nk_image, BROWSER_HOME);
|
||||
browser_config_dir(nk_image, BROWSER_PROJECT);
|
||||
// [...]
|
||||
browser_config_type(nk_image, ".ext1.ext2.ext3.");
|
||||
browser_config_type(nk_image, ".ext1.ext2.ext3.");
|
||||
browser_config_type(nk_image, ".ext1.ext2.ext3.");
|
||||
// [...]
|
||||
|
||||
[...]
|
||||
|
||||
if( nk_begin(ctx, "window", ...) ) {
|
||||
struct nk_rect total_space = nk_window_get_content_region(ctx);
|
||||
if( browser_run(ctx, &browser, 0, total_space) ) {
|
||||
puts( browser->directory );
|
||||
puts( browser->file );
|
||||
}
|
||||
}
|
||||
nk_end();
|
||||
*/
|
||||
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -1,409 +1,409 @@
|
|||
/* Progressive Mesh type Polygon Reduction Algorithm
|
||||
*
|
||||
* 1998: Original version by Stan Melax (c) 1998
|
||||
* Permission to use any of this code wherever you want is granted..
|
||||
* Although, please do acknowledge authorship if appropriate.
|
||||
*
|
||||
* 2014: Code style upgraded to be more consistent with graphics/gamedev conventions. Relicensed as MIT/PD.
|
||||
* Stan Melax: "Yes, this code can be licensed with the same license as the original. That should be fine."
|
||||
*
|
||||
* 2020: C version by Cloud Wu (c) 2020. Licensed as MIT/PD.
|
||||
*/
|
||||
|
||||
static inline void array_find_and_remove(array(int) arr, int v) {
|
||||
for( int i = 0, end = array_count(arr); i < end; i++ )
|
||||
if( arr[i] == v ) { array_erase_fast(arr, i); --end; break; }
|
||||
}
|
||||
|
||||
#include <assert.h>
|
||||
#include <math.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
struct triangle_n {
|
||||
int vertex[3]; // the 3 points (id) that make this tri
|
||||
vec3 normal; // unit vector othogonal to this face
|
||||
};
|
||||
|
||||
struct vertex {
|
||||
vec3 position; // location of point in euclidean space
|
||||
array(int) neighbor; // adjacent vertices
|
||||
array(int) face; // adjacent triangles
|
||||
int id; // place of vertex in original Array
|
||||
int collapse; // candidate vertex (id) for collapse
|
||||
float objdist; // cached cost of collapsing edge
|
||||
};
|
||||
|
||||
struct mesh {
|
||||
struct vertex *v;
|
||||
struct triangle_n *t;
|
||||
int n_face;
|
||||
int n_vertex;
|
||||
};
|
||||
|
||||
// array
|
||||
|
||||
static inline struct vertex *Vertex(struct mesh *M, int id) { return M->v + id; }
|
||||
static inline struct triangle_n *Triangle(struct mesh *M, int id) { return M->t + id; }
|
||||
static inline struct triangle_n *Face(struct mesh *M, struct vertex *v, int idx) { return M->t + v->face[idx]; }
|
||||
|
||||
static void AddVertex(struct mesh *M, const float *v) {
|
||||
int id = M->n_vertex++;
|
||||
struct vertex * tmp = Vertex(M, id);
|
||||
tmp->position = ptr3(v);
|
||||
tmp->neighbor = NULL;
|
||||
tmp->face = NULL;
|
||||
tmp->id = id;
|
||||
tmp->collapse = -1;
|
||||
tmp->objdist = 0;
|
||||
}
|
||||
|
||||
static void RemoveVertex(struct mesh *M, int id) {
|
||||
struct vertex * v = Vertex(M, id);
|
||||
ASSERT(v->id == id);
|
||||
ASSERT(array_count(v->face) == 0);
|
||||
for (int i=0;i<array_count(v->face);i++) {
|
||||
struct vertex * nv = Vertex(M, v->face[i]);
|
||||
array_find_and_remove(nv->neighbor, id);
|
||||
}
|
||||
v->id = -1; // invalid vertex id
|
||||
array_free(v->neighbor);
|
||||
array_free(v->face);
|
||||
}
|
||||
|
||||
static void ComputeNormal(struct mesh *M, struct triangle_n *t) {
|
||||
struct vertex * v0 = Vertex(M, t->vertex[0]);
|
||||
struct vertex * v1 = Vertex(M, t->vertex[1]);
|
||||
struct vertex * v2 = Vertex(M, t->vertex[2]);
|
||||
vec3 a = sub3(v1->position, v0->position);
|
||||
vec3 b = sub3(v2->position, v1->position);
|
||||
t->normal = norm3(cross3(a,b));
|
||||
}
|
||||
|
||||
static void AddNeighbor(struct mesh *M, int vid, int id) {
|
||||
struct vertex *v = Vertex(M, vid);
|
||||
for (int i=0;i<array_count(v->neighbor);i++) {
|
||||
if (v->neighbor[i] == id)
|
||||
return;
|
||||
}
|
||||
array_push(v->neighbor, id);
|
||||
}
|
||||
|
||||
static void AddTriangle(struct mesh *M, const int v[3]) {
|
||||
if (v[0] == v[1] || v[0] == v[2] || v[1] == v[2])
|
||||
return;
|
||||
ASSERT(v[0] < M->n_vertex);
|
||||
ASSERT(v[1] < M->n_vertex);
|
||||
ASSERT(v[2] < M->n_vertex);
|
||||
int id = M->n_face++;
|
||||
struct triangle_n * tmp = Triangle(M, id);
|
||||
tmp->vertex[0] = v[0];
|
||||
tmp->vertex[1] = v[1];
|
||||
tmp->vertex[2] = v[2];
|
||||
ComputeNormal(M, tmp);
|
||||
|
||||
for(int i=0;i<3;i++) {
|
||||
struct vertex *obj = Vertex(M, v[i]);
|
||||
array_push(obj->face, id);
|
||||
}
|
||||
|
||||
AddNeighbor(M, v[0], v[1]);
|
||||
AddNeighbor(M, v[0], v[2]);
|
||||
AddNeighbor(M, v[1], v[0]);
|
||||
AddNeighbor(M, v[1], v[2]);
|
||||
AddNeighbor(M, v[2], v[0]);
|
||||
AddNeighbor(M, v[2], v[1]);
|
||||
}
|
||||
|
||||
static int HasVertex(struct triangle_n * t, int vid) {
|
||||
return (t->vertex[0] == vid || t->vertex[1] == vid || t->vertex[2] == vid);
|
||||
}
|
||||
|
||||
static void RemoveIfNonNeighbor_(struct mesh *M, struct vertex *v, int id) {
|
||||
for (int i=0;i<array_count(v->neighbor);i++) {
|
||||
if (v->neighbor[i] == id) {
|
||||
for (int j=0;j<array_count(v->face);j++) {
|
||||
if (HasVertex(Face(M, v, j), id))
|
||||
return;
|
||||
}
|
||||
// remove from neighbors
|
||||
array_erase_fast(v->neighbor, i);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void RemoveIfNonNeighbor(struct mesh *M, struct vertex *v0, struct vertex *v1) {
|
||||
if (v0 == NULL || v1 == NULL)
|
||||
return;
|
||||
RemoveIfNonNeighbor_(M, v0, v1->id);
|
||||
RemoveIfNonNeighbor_(M, v1, v0->id);
|
||||
}
|
||||
|
||||
static void RemoveTriangle(struct mesh *M, int id) {
|
||||
struct triangle_n * face = Triangle(M, id);
|
||||
struct vertex * v[3];
|
||||
for (int i=0;i<3;i++) {
|
||||
v[i] = Vertex(M, face->vertex[i]);
|
||||
if (v[i]->id < 0)
|
||||
v[i] = NULL;
|
||||
else {
|
||||
array_find_and_remove(v[i]->face, id);
|
||||
}
|
||||
}
|
||||
RemoveIfNonNeighbor(M, v[0], v[1]);
|
||||
RemoveIfNonNeighbor(M, v[1], v[2]);
|
||||
RemoveIfNonNeighbor(M, v[2], v[0]);
|
||||
}
|
||||
|
||||
static void ReplaceVertex(struct mesh *M, int faceid, int oldid, int newid) {
|
||||
struct triangle_n * face = Triangle(M, faceid);
|
||||
ASSERT(oldid >=0 && newid >= 0);
|
||||
ASSERT(HasVertex(face, oldid));
|
||||
ASSERT(!HasVertex(face, newid));
|
||||
if(oldid==face->vertex[0]){
|
||||
face->vertex[0]=newid;
|
||||
} else if(oldid==face->vertex[1]){
|
||||
face->vertex[1]=newid;
|
||||
} else {
|
||||
face->vertex[2]=newid;
|
||||
}
|
||||
struct vertex *vold = Vertex(M, oldid);
|
||||
struct vertex *vnew = Vertex(M, newid);
|
||||
|
||||
array_find_and_remove(vold->face, faceid);
|
||||
array_push(vnew->face, faceid);
|
||||
|
||||
RemoveIfNonNeighbor(M, vold, Vertex(M, face->vertex[0]));
|
||||
RemoveIfNonNeighbor(M, vold, Vertex(M, face->vertex[1]));
|
||||
RemoveIfNonNeighbor(M, vold, Vertex(M, face->vertex[2]));
|
||||
|
||||
AddNeighbor(M, face->vertex[0], face->vertex[1]);
|
||||
AddNeighbor(M, face->vertex[0], face->vertex[2]);
|
||||
AddNeighbor(M, face->vertex[1], face->vertex[0]);
|
||||
AddNeighbor(M, face->vertex[1], face->vertex[2]);
|
||||
AddNeighbor(M, face->vertex[2], face->vertex[0]);
|
||||
AddNeighbor(M, face->vertex[2], face->vertex[1]);
|
||||
|
||||
ComputeNormal(M, face);
|
||||
}
|
||||
|
||||
static void MeshInit(struct mesh *M, int vert_n, int tri_n) {
|
||||
M->n_face = 0;
|
||||
M->n_vertex = 0;
|
||||
M->v = (struct vertex *)MALLOC(vert_n * sizeof(struct vertex));
|
||||
M->t = (struct triangle_n *)MALLOC(tri_n * sizeof(struct triangle));
|
||||
}
|
||||
|
||||
static void MeshFree(struct mesh *M) {
|
||||
FREE(M->v);
|
||||
FREE(M->t);
|
||||
}
|
||||
|
||||
static float ComputeEdgeCollapseCost(struct mesh *M, struct vertex *u, int vid) {
|
||||
// if we collapse edge uv by moving u to v then how
|
||||
// much different will the model change, i.e. how much "error".
|
||||
// Texture, vertex normal, and border vertex code was removed
|
||||
// to keep this demo as simple as possible.
|
||||
// The method of determining cost was designed in order
|
||||
// to exploit small and coplanar regions for
|
||||
// effective polygon reduction.
|
||||
// Is is possible to add some checks here to see if "folds"
|
||||
// would be generated. i.e. normal of a remaining face gets
|
||||
// flipped. I never seemed to run into this problem and
|
||||
// therefore never added code to detect this case.
|
||||
struct vertex *v = Vertex(M, vid);
|
||||
vec3 tmp = sub3(v->position, u->position);
|
||||
float edgelength = len3(tmp);
|
||||
float curvature=0;
|
||||
|
||||
// find the "sides" triangles that are on the edge uv
|
||||
array(int) sides = 0;
|
||||
for (int i = 0; i<array_count(u->face); i++) {
|
||||
if (HasVertex(Face(M, u, i), vid)) {
|
||||
array_push(sides, u->face[i]);
|
||||
}
|
||||
}
|
||||
// use the triangle facing most away from the sides
|
||||
// to determine our curvature term
|
||||
for (int i = 0; i<array_count(u->face); i++) {
|
||||
float mincurv=1; // curve for face i and closer side to it
|
||||
for (int j = 0; j<array_count(sides); j++) {
|
||||
float dotprod = dot3(Triangle(M, u->face[i])->normal,
|
||||
Triangle(M, sides[j])->normal); // use dot product of face normals.
|
||||
float t = (1-dotprod)/2.0f;
|
||||
if (t < mincurv) {
|
||||
mincurv = t;
|
||||
}
|
||||
}
|
||||
if (mincurv > curvature)
|
||||
curvature = mincurv;
|
||||
}
|
||||
array_free(sides);
|
||||
// the more coplanar the lower the curvature term
|
||||
return edgelength * curvature;
|
||||
}
|
||||
|
||||
static void ComputeEdgeCostAtVertex(struct mesh *M, struct vertex *v) {
|
||||
// compute the edge collapse cost for all edges that start
|
||||
// from vertex v. Since we are only interested in reducing
|
||||
// the object by selecting the min cost edge at each step, we
|
||||
// only cache the cost of the least cost edge at this vertex
|
||||
// (in member variable collapse) as well as the value of the
|
||||
// cost (in member variable objdist).
|
||||
if (array_count(v->neighbor) == 0) {
|
||||
// v doesn't have neighbors so it costs nothing to collapse
|
||||
v->collapse=-1;
|
||||
v->objdist=-0.01f;
|
||||
return;
|
||||
}
|
||||
v->objdist = 1000000;
|
||||
v->collapse=-1;
|
||||
// search all neighboring edges for "least cost" edge
|
||||
for (int i = 0; i<array_count(v->neighbor); i++) {
|
||||
float dist = ComputeEdgeCollapseCost(M, v, v->neighbor[i]);
|
||||
if(dist<v->objdist) {
|
||||
v->collapse=v->neighbor[i]; // candidate for edge collapse
|
||||
v->objdist=dist; // cost of the collapse
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void ComputeAllEdgeCollapseCosts(struct mesh *M) {
|
||||
// For all the edges, compute the difference it would make
|
||||
// to the model if it was collapsed. The least of these
|
||||
// per vertex is cached in each vertex object.
|
||||
for (int i = 0; i<M->n_vertex; i++) {
|
||||
ComputeEdgeCostAtVertex(M, Vertex(M, i));
|
||||
}
|
||||
}
|
||||
|
||||
static void Collapse(struct mesh *M, int uid, int vid) {
|
||||
// Collapse the edge uv by moving vertex u onto v
|
||||
// Actually remove tris on uv, then update tris that
|
||||
// have u to have v, and then remove u.
|
||||
struct vertex *u = Vertex(M, uid);
|
||||
if(vid < 0) {
|
||||
// u is a vertex all by itself so just delete it
|
||||
RemoveVertex(M, uid);
|
||||
return;
|
||||
}
|
||||
|
||||
array(int) tmp = 0;
|
||||
|
||||
// make tmp a Array of all the neighbors of u
|
||||
for (int i = 0; i<array_count(u->neighbor); i++) {
|
||||
array_push(tmp, u->neighbor[i]);
|
||||
}
|
||||
// delete triangles on edge uv:
|
||||
for( int i = array_count(u->face); i--; ) {
|
||||
if (HasVertex(Face(M, u, i), vid)) {
|
||||
RemoveTriangle(M, u->face[i]);
|
||||
}
|
||||
}
|
||||
// update remaining triangles to have v instead of u
|
||||
for( int i = array_count(u->face); i--; ) {
|
||||
ReplaceVertex(M, u->face[i], uid, vid);
|
||||
}
|
||||
RemoveVertex(M, uid);
|
||||
// recompute the edge collapse costs for neighboring vertices
|
||||
for (int i = 0; i<array_count(tmp); i++) {
|
||||
ComputeEdgeCostAtVertex(M, Vertex(M, tmp[i]));
|
||||
}
|
||||
|
||||
array_free(tmp);
|
||||
}
|
||||
|
||||
static struct vertex *MinimumCostEdge(struct mesh *M) {
|
||||
// Find the edge that when collapsed will affect model the least.
|
||||
// This function actually returns a Vertex, the second vertex
|
||||
// of the edge (collapse candidate) is stored in the vertex data.
|
||||
// Serious optimization opportunity here: this function currently
|
||||
// does a sequential search through an unsorted Array :-(
|
||||
// Our algorithm could be O(n*lg(n)) instead of O(n*n)
|
||||
struct vertex *mn = NULL;
|
||||
for (int i = 0; i<M->n_vertex; i++) {
|
||||
struct vertex *v = Vertex(M, i);
|
||||
if (v->id >=0) {
|
||||
if (mn == NULL || v->objdist < mn->objdist) {
|
||||
mn = v;
|
||||
}
|
||||
}
|
||||
}
|
||||
return mn;
|
||||
}
|
||||
|
||||
/*
|
||||
* The function ProgressiveMesh() takes a model in an "indexed face
|
||||
* set" sort of way. i.e. Array of vertices and Array of triangles.
|
||||
* The function then does the polygon reduction algorithm
|
||||
* internally and reduces the model all the way down to 0
|
||||
* vertices and then returns the order in which the
|
||||
* vertices are collapsed and to which neighbor each vertex
|
||||
* is collapsed to. More specifically the returned "permutation"
|
||||
* indicates how to reorder your vertices so you can render
|
||||
* an object by using the first n vertices (for the n
|
||||
* vertex version). After permuting your vertices, the
|
||||
* map Array indicates to which vertex each vertex is collapsed to.
|
||||
*/
|
||||
|
||||
API void ProgressiveMesh(int vert_n, int vert_stride, const float *v, int tri_n, const int *tri, int *map, int *permutation) {
|
||||
struct mesh M;
|
||||
MeshInit(&M, vert_n, tri_n);
|
||||
|
||||
// put input data into our data structures M
|
||||
const char * tmp = (const char *)v;
|
||||
for (int i=0;i<vert_n;i++, tmp += vert_stride ) {
|
||||
AddVertex(&M, (const float *)tmp);
|
||||
}
|
||||
|
||||
for (int i=0;i<tri_n;i++) {
|
||||
AddTriangle(&M, &tri[i*3]);
|
||||
}
|
||||
|
||||
ComputeAllEdgeCollapseCosts(&M); // cache all edge collapse costs
|
||||
|
||||
for (int i = vert_n-1; i>=0; i--) {
|
||||
// get the next vertex to collapse
|
||||
struct vertex *mn = MinimumCostEdge(&M);
|
||||
// keep track of this vertex, i.e. the collapse ordering
|
||||
permutation[mn->id] = i;
|
||||
// keep track of vertex to which we collapse to
|
||||
map[i] = mn->collapse;
|
||||
// Collapse this edge
|
||||
Collapse(&M, mn->id, mn->collapse);
|
||||
}
|
||||
|
||||
// reorder the map Array based on the collapse ordering
|
||||
for (int i = 0; i<vert_n; i++) {
|
||||
map[i] = (map[i]==-1)?0:permutation[map[i]];
|
||||
}
|
||||
// The caller of this function should reorder their vertices
|
||||
// according to the returned "permutation".
|
||||
|
||||
MeshFree(&M);
|
||||
}
|
||||
|
||||
/*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2014 Stan Melax
|
||||
* Copyright (c) 2020 Cloud Wu
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
/* Progressive Mesh type Polygon Reduction Algorithm
|
||||
*
|
||||
* 1998: Original version by Stan Melax (c) 1998
|
||||
* Permission to use any of this code wherever you want is granted..
|
||||
* Although, please do acknowledge authorship if appropriate.
|
||||
*
|
||||
* 2014: Code style upgraded to be more consistent with graphics/gamedev conventions. Relicensed as MIT/PD.
|
||||
* Stan Melax: "Yes, this code can be licensed with the same license as the original. That should be fine."
|
||||
*
|
||||
* 2020: C version by Cloud Wu (c) 2020. Licensed as MIT/PD.
|
||||
*/
|
||||
|
||||
static inline void array_find_and_remove(array(int) arr, int v) {
|
||||
for( int i = 0, end = array_count(arr); i < end; i++ )
|
||||
if( arr[i] == v ) { array_erase_fast(arr, i); --end; break; }
|
||||
}
|
||||
|
||||
#include <assert.h>
|
||||
#include <math.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
struct triangle_n {
|
||||
int vertex[3]; // the 3 points (id) that make this tri
|
||||
vec3 normal; // unit vector othogonal to this face
|
||||
};
|
||||
|
||||
struct vertex {
|
||||
vec3 position; // location of point in euclidean space
|
||||
array(int) neighbor; // adjacent vertices
|
||||
array(int) face; // adjacent triangles
|
||||
int id; // place of vertex in original Array
|
||||
int collapse; // candidate vertex (id) for collapse
|
||||
float objdist; // cached cost of collapsing edge
|
||||
};
|
||||
|
||||
struct mesh {
|
||||
struct vertex *v;
|
||||
struct triangle_n *t;
|
||||
int n_face;
|
||||
int n_vertex;
|
||||
};
|
||||
|
||||
// array
|
||||
|
||||
static inline struct vertex *Vertex(struct mesh *M, int id) { return M->v + id; }
|
||||
static inline struct triangle_n *Triangle(struct mesh *M, int id) { return M->t + id; }
|
||||
static inline struct triangle_n *Face(struct mesh *M, struct vertex *v, int idx) { return M->t + v->face[idx]; }
|
||||
|
||||
static void AddVertex(struct mesh *M, const float *v) {
|
||||
int id = M->n_vertex++;
|
||||
struct vertex * tmp = Vertex(M, id);
|
||||
tmp->position = ptr3(v);
|
||||
tmp->neighbor = NULL;
|
||||
tmp->face = NULL;
|
||||
tmp->id = id;
|
||||
tmp->collapse = -1;
|
||||
tmp->objdist = 0;
|
||||
}
|
||||
|
||||
static void RemoveVertex(struct mesh *M, int id) {
|
||||
struct vertex * v = Vertex(M, id);
|
||||
ASSERT(v->id == id);
|
||||
ASSERT(array_count(v->face) == 0);
|
||||
for (int i=0;i<array_count(v->face);i++) {
|
||||
struct vertex * nv = Vertex(M, v->face[i]);
|
||||
array_find_and_remove(nv->neighbor, id);
|
||||
}
|
||||
v->id = -1; // invalid vertex id
|
||||
array_free(v->neighbor);
|
||||
array_free(v->face);
|
||||
}
|
||||
|
||||
static void ComputeNormal(struct mesh *M, struct triangle_n *t) {
|
||||
struct vertex * v0 = Vertex(M, t->vertex[0]);
|
||||
struct vertex * v1 = Vertex(M, t->vertex[1]);
|
||||
struct vertex * v2 = Vertex(M, t->vertex[2]);
|
||||
vec3 a = sub3(v1->position, v0->position);
|
||||
vec3 b = sub3(v2->position, v1->position);
|
||||
t->normal = norm3(cross3(a,b));
|
||||
}
|
||||
|
||||
static void AddNeighbor(struct mesh *M, int vid, int id) {
|
||||
struct vertex *v = Vertex(M, vid);
|
||||
for (int i=0;i<array_count(v->neighbor);i++) {
|
||||
if (v->neighbor[i] == id)
|
||||
return;
|
||||
}
|
||||
array_push(v->neighbor, id);
|
||||
}
|
||||
|
||||
static void AddTriangle(struct mesh *M, const int v[3]) {
|
||||
if (v[0] == v[1] || v[0] == v[2] || v[1] == v[2])
|
||||
return;
|
||||
ASSERT(v[0] < M->n_vertex);
|
||||
ASSERT(v[1] < M->n_vertex);
|
||||
ASSERT(v[2] < M->n_vertex);
|
||||
int id = M->n_face++;
|
||||
struct triangle_n * tmp = Triangle(M, id);
|
||||
tmp->vertex[0] = v[0];
|
||||
tmp->vertex[1] = v[1];
|
||||
tmp->vertex[2] = v[2];
|
||||
ComputeNormal(M, tmp);
|
||||
|
||||
for(int i=0;i<3;i++) {
|
||||
struct vertex *obj = Vertex(M, v[i]);
|
||||
array_push(obj->face, id);
|
||||
}
|
||||
|
||||
AddNeighbor(M, v[0], v[1]);
|
||||
AddNeighbor(M, v[0], v[2]);
|
||||
AddNeighbor(M, v[1], v[0]);
|
||||
AddNeighbor(M, v[1], v[2]);
|
||||
AddNeighbor(M, v[2], v[0]);
|
||||
AddNeighbor(M, v[2], v[1]);
|
||||
}
|
||||
|
||||
static int HasVertex(struct triangle_n * t, int vid) {
|
||||
return (t->vertex[0] == vid || t->vertex[1] == vid || t->vertex[2] == vid);
|
||||
}
|
||||
|
||||
static void RemoveIfNonNeighbor_(struct mesh *M, struct vertex *v, int id) {
|
||||
for (int i=0;i<array_count(v->neighbor);i++) {
|
||||
if (v->neighbor[i] == id) {
|
||||
for (int j=0;j<array_count(v->face);j++) {
|
||||
if (HasVertex(Face(M, v, j), id))
|
||||
return;
|
||||
}
|
||||
// remove from neighbors
|
||||
array_erase_fast(v->neighbor, i);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void RemoveIfNonNeighbor(struct mesh *M, struct vertex *v0, struct vertex *v1) {
|
||||
if (v0 == NULL || v1 == NULL)
|
||||
return;
|
||||
RemoveIfNonNeighbor_(M, v0, v1->id);
|
||||
RemoveIfNonNeighbor_(M, v1, v0->id);
|
||||
}
|
||||
|
||||
static void RemoveTriangle(struct mesh *M, int id) {
|
||||
struct triangle_n * face = Triangle(M, id);
|
||||
struct vertex * v[3];
|
||||
for (int i=0;i<3;i++) {
|
||||
v[i] = Vertex(M, face->vertex[i]);
|
||||
if (v[i]->id < 0)
|
||||
v[i] = NULL;
|
||||
else {
|
||||
array_find_and_remove(v[i]->face, id);
|
||||
}
|
||||
}
|
||||
RemoveIfNonNeighbor(M, v[0], v[1]);
|
||||
RemoveIfNonNeighbor(M, v[1], v[2]);
|
||||
RemoveIfNonNeighbor(M, v[2], v[0]);
|
||||
}
|
||||
|
||||
static void ReplaceVertex(struct mesh *M, int faceid, int oldid, int newid) {
|
||||
struct triangle_n * face = Triangle(M, faceid);
|
||||
ASSERT(oldid >=0 && newid >= 0);
|
||||
ASSERT(HasVertex(face, oldid));
|
||||
ASSERT(!HasVertex(face, newid));
|
||||
if(oldid==face->vertex[0]){
|
||||
face->vertex[0]=newid;
|
||||
} else if(oldid==face->vertex[1]){
|
||||
face->vertex[1]=newid;
|
||||
} else {
|
||||
face->vertex[2]=newid;
|
||||
}
|
||||
struct vertex *vold = Vertex(M, oldid);
|
||||
struct vertex *vnew = Vertex(M, newid);
|
||||
|
||||
array_find_and_remove(vold->face, faceid);
|
||||
array_push(vnew->face, faceid);
|
||||
|
||||
RemoveIfNonNeighbor(M, vold, Vertex(M, face->vertex[0]));
|
||||
RemoveIfNonNeighbor(M, vold, Vertex(M, face->vertex[1]));
|
||||
RemoveIfNonNeighbor(M, vold, Vertex(M, face->vertex[2]));
|
||||
|
||||
AddNeighbor(M, face->vertex[0], face->vertex[1]);
|
||||
AddNeighbor(M, face->vertex[0], face->vertex[2]);
|
||||
AddNeighbor(M, face->vertex[1], face->vertex[0]);
|
||||
AddNeighbor(M, face->vertex[1], face->vertex[2]);
|
||||
AddNeighbor(M, face->vertex[2], face->vertex[0]);
|
||||
AddNeighbor(M, face->vertex[2], face->vertex[1]);
|
||||
|
||||
ComputeNormal(M, face);
|
||||
}
|
||||
|
||||
static void MeshInit(struct mesh *M, int vert_n, int tri_n) {
|
||||
M->n_face = 0;
|
||||
M->n_vertex = 0;
|
||||
M->v = (struct vertex *)MALLOC(vert_n * sizeof(struct vertex));
|
||||
M->t = (struct triangle_n *)MALLOC(tri_n * sizeof(struct triangle));
|
||||
}
|
||||
|
||||
static void MeshFree(struct mesh *M) {
|
||||
FREE(M->v);
|
||||
FREE(M->t);
|
||||
}
|
||||
|
||||
static float ComputeEdgeCollapseCost(struct mesh *M, struct vertex *u, int vid) {
|
||||
// if we collapse edge uv by moving u to v then how
|
||||
// much different will the model change, i.e. how much "error".
|
||||
// Texture, vertex normal, and border vertex code was removed
|
||||
// to keep this demo as simple as possible.
|
||||
// The method of determining cost was designed in order
|
||||
// to exploit small and coplanar regions for
|
||||
// effective polygon reduction.
|
||||
// Is is possible to add some checks here to see if "folds"
|
||||
// would be generated. i.e. normal of a remaining face gets
|
||||
// flipped. I never seemed to run into this problem and
|
||||
// therefore never added code to detect this case.
|
||||
struct vertex *v = Vertex(M, vid);
|
||||
vec3 tmp = sub3(v->position, u->position);
|
||||
float edgelength = len3(tmp);
|
||||
float curvature=0;
|
||||
|
||||
// find the "sides" triangles that are on the edge uv
|
||||
array(int) sides = 0;
|
||||
for (int i = 0; i<array_count(u->face); i++) {
|
||||
if (HasVertex(Face(M, u, i), vid)) {
|
||||
array_push(sides, u->face[i]);
|
||||
}
|
||||
}
|
||||
// use the triangle facing most away from the sides
|
||||
// to determine our curvature term
|
||||
for (int i = 0; i<array_count(u->face); i++) {
|
||||
float mincurv=1; // curve for face i and closer side to it
|
||||
for (int j = 0; j<array_count(sides); j++) {
|
||||
float dotprod = dot3(Triangle(M, u->face[i])->normal,
|
||||
Triangle(M, sides[j])->normal); // use dot product of face normals.
|
||||
float t = (1-dotprod)/2.0f;
|
||||
if (t < mincurv) {
|
||||
mincurv = t;
|
||||
}
|
||||
}
|
||||
if (mincurv > curvature)
|
||||
curvature = mincurv;
|
||||
}
|
||||
array_free(sides);
|
||||
// the more coplanar the lower the curvature term
|
||||
return edgelength * curvature;
|
||||
}
|
||||
|
||||
static void ComputeEdgeCostAtVertex(struct mesh *M, struct vertex *v) {
|
||||
// compute the edge collapse cost for all edges that start
|
||||
// from vertex v. Since we are only interested in reducing
|
||||
// the object by selecting the min cost edge at each step, we
|
||||
// only cache the cost of the least cost edge at this vertex
|
||||
// (in member variable collapse) as well as the value of the
|
||||
// cost (in member variable objdist).
|
||||
if (array_count(v->neighbor) == 0) {
|
||||
// v doesn't have neighbors so it costs nothing to collapse
|
||||
v->collapse=-1;
|
||||
v->objdist=-0.01f;
|
||||
return;
|
||||
}
|
||||
v->objdist = 1000000;
|
||||
v->collapse=-1;
|
||||
// search all neighboring edges for "least cost" edge
|
||||
for (int i = 0; i<array_count(v->neighbor); i++) {
|
||||
float dist = ComputeEdgeCollapseCost(M, v, v->neighbor[i]);
|
||||
if(dist<v->objdist) {
|
||||
v->collapse=v->neighbor[i]; // candidate for edge collapse
|
||||
v->objdist=dist; // cost of the collapse
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void ComputeAllEdgeCollapseCosts(struct mesh *M) {
|
||||
// For all the edges, compute the difference it would make
|
||||
// to the model if it was collapsed. The least of these
|
||||
// per vertex is cached in each vertex object.
|
||||
for (int i = 0; i<M->n_vertex; i++) {
|
||||
ComputeEdgeCostAtVertex(M, Vertex(M, i));
|
||||
}
|
||||
}
|
||||
|
||||
static void Collapse(struct mesh *M, int uid, int vid) {
|
||||
// Collapse the edge uv by moving vertex u onto v
|
||||
// Actually remove tris on uv, then update tris that
|
||||
// have u to have v, and then remove u.
|
||||
struct vertex *u = Vertex(M, uid);
|
||||
if(vid < 0) {
|
||||
// u is a vertex all by itself so just delete it
|
||||
RemoveVertex(M, uid);
|
||||
return;
|
||||
}
|
||||
|
||||
array(int) tmp = 0;
|
||||
|
||||
// make tmp a Array of all the neighbors of u
|
||||
for (int i = 0; i<array_count(u->neighbor); i++) {
|
||||
array_push(tmp, u->neighbor[i]);
|
||||
}
|
||||
// delete triangles on edge uv:
|
||||
for( int i = array_count(u->face); i--; ) {
|
||||
if (HasVertex(Face(M, u, i), vid)) {
|
||||
RemoveTriangle(M, u->face[i]);
|
||||
}
|
||||
}
|
||||
// update remaining triangles to have v instead of u
|
||||
for( int i = array_count(u->face); i--; ) {
|
||||
ReplaceVertex(M, u->face[i], uid, vid);
|
||||
}
|
||||
RemoveVertex(M, uid);
|
||||
// recompute the edge collapse costs for neighboring vertices
|
||||
for (int i = 0; i<array_count(tmp); i++) {
|
||||
ComputeEdgeCostAtVertex(M, Vertex(M, tmp[i]));
|
||||
}
|
||||
|
||||
array_free(tmp);
|
||||
}
|
||||
|
||||
static struct vertex *MinimumCostEdge(struct mesh *M) {
|
||||
// Find the edge that when collapsed will affect model the least.
|
||||
// This function actually returns a Vertex, the second vertex
|
||||
// of the edge (collapse candidate) is stored in the vertex data.
|
||||
// Serious optimization opportunity here: this function currently
|
||||
// does a sequential search through an unsorted Array :-(
|
||||
// Our algorithm could be O(n*lg(n)) instead of O(n*n)
|
||||
struct vertex *mn = NULL;
|
||||
for (int i = 0; i<M->n_vertex; i++) {
|
||||
struct vertex *v = Vertex(M, i);
|
||||
if (v->id >=0) {
|
||||
if (mn == NULL || v->objdist < mn->objdist) {
|
||||
mn = v;
|
||||
}
|
||||
}
|
||||
}
|
||||
return mn;
|
||||
}
|
||||
|
||||
/*
|
||||
* The function ProgressiveMesh() takes a model in an "indexed face
|
||||
* set" sort of way. i.e. Array of vertices and Array of triangles.
|
||||
* The function then does the polygon reduction algorithm
|
||||
* internally and reduces the model all the way down to 0
|
||||
* vertices and then returns the order in which the
|
||||
* vertices are collapsed and to which neighbor each vertex
|
||||
* is collapsed to. More specifically the returned "permutation"
|
||||
* indicates how to reorder your vertices so you can render
|
||||
* an object by using the first n vertices (for the n
|
||||
* vertex version). After permuting your vertices, the
|
||||
* map Array indicates to which vertex each vertex is collapsed to.
|
||||
*/
|
||||
|
||||
API void ProgressiveMesh(int vert_n, int vert_stride, const float *v, int tri_n, const int *tri, int *map, int *permutation) {
|
||||
struct mesh M;
|
||||
MeshInit(&M, vert_n, tri_n);
|
||||
|
||||
// put input data into our data structures M
|
||||
const char * tmp = (const char *)v;
|
||||
for (int i=0;i<vert_n;i++, tmp += vert_stride ) {
|
||||
AddVertex(&M, (const float *)tmp);
|
||||
}
|
||||
|
||||
for (int i=0;i<tri_n;i++) {
|
||||
AddTriangle(&M, &tri[i*3]);
|
||||
}
|
||||
|
||||
ComputeAllEdgeCollapseCosts(&M); // cache all edge collapse costs
|
||||
|
||||
for (int i = vert_n-1; i>=0; i--) {
|
||||
// get the next vertex to collapse
|
||||
struct vertex *mn = MinimumCostEdge(&M);
|
||||
// keep track of this vertex, i.e. the collapse ordering
|
||||
permutation[mn->id] = i;
|
||||
// keep track of vertex to which we collapse to
|
||||
map[i] = mn->collapse;
|
||||
// Collapse this edge
|
||||
Collapse(&M, mn->id, mn->collapse);
|
||||
}
|
||||
|
||||
// reorder the map Array based on the collapse ordering
|
||||
for (int i = 0; i<vert_n; i++) {
|
||||
map[i] = (map[i]==-1)?0:permutation[map[i]];
|
||||
}
|
||||
// The caller of this function should reorder their vertices
|
||||
// according to the returned "permutation".
|
||||
|
||||
MeshFree(&M);
|
||||
}
|
||||
|
||||
/*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2014 Stan Melax
|
||||
* Copyright (c) 2020 Cloud Wu
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,373 +1,373 @@
|
|||
/* rpmalloc.h - Memory allocator - Public Domain - 2016 Mattias Jansson
|
||||
*
|
||||
* This library provides a cross-platform lock free thread caching malloc implementation in C11.
|
||||
* The latest source code is always available at
|
||||
*
|
||||
* https://github.com/mjansson/rpmalloc
|
||||
*
|
||||
* This library is put in the public domain; you can redistribute it and/or modify it without any restrictions.
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#if defined(__clang__) || defined(__GNUC__)
|
||||
# define RPMALLOC_EXPORT __attribute__((visibility("default")))
|
||||
# define RPMALLOC_ALLOCATOR
|
||||
# if (defined(__clang_major__) && (__clang_major__ < 4)) || (defined(__GNUC__) && defined(ENABLE_PRELOAD) && ENABLE_PRELOAD)
|
||||
# define RPMALLOC_ATTRIB_MALLOC
|
||||
# define RPMALLOC_ATTRIB_ALLOC_SIZE(size)
|
||||
# define RPMALLOC_ATTRIB_ALLOC_SIZE2(count, size)
|
||||
# else
|
||||
# define RPMALLOC_ATTRIB_MALLOC __attribute__((__malloc__))
|
||||
# define RPMALLOC_ATTRIB_ALLOC_SIZE(size) __attribute__((alloc_size(size)))
|
||||
# define RPMALLOC_ATTRIB_ALLOC_SIZE2(count, size) __attribute__((alloc_size(count, size)))
|
||||
# endif
|
||||
# define RPMALLOC_CDECL
|
||||
#elif defined(_MSC_VER)
|
||||
# define RPMALLOC_EXPORT
|
||||
# define RPMALLOC_ALLOCATOR __declspec(allocator) __declspec(restrict)
|
||||
# define RPMALLOC_ATTRIB_MALLOC
|
||||
# define RPMALLOC_ATTRIB_ALLOC_SIZE(size)
|
||||
# define RPMALLOC_ATTRIB_ALLOC_SIZE2(count,size)
|
||||
# define RPMALLOC_CDECL __cdecl
|
||||
#else
|
||||
# define RPMALLOC_EXPORT
|
||||
# define RPMALLOC_ALLOCATOR
|
||||
# define RPMALLOC_ATTRIB_MALLOC
|
||||
# define RPMALLOC_ATTRIB_ALLOC_SIZE(size)
|
||||
# define RPMALLOC_ATTRIB_ALLOC_SIZE2(count,size)
|
||||
# define RPMALLOC_CDECL
|
||||
#endif
|
||||
|
||||
//! Define RPMALLOC_CONFIGURABLE to enable configuring sizes. Will introduce
|
||||
// a very small overhead due to some size calculations not being compile time constants
|
||||
#ifndef RPMALLOC_CONFIGURABLE
|
||||
#define RPMALLOC_CONFIGURABLE 0
|
||||
#endif
|
||||
|
||||
//! Define RPMALLOC_FIRST_CLASS_HEAPS to enable heap based API (rpmalloc_heap_* functions).
|
||||
// Will introduce a very small overhead to track fully allocated spans in heaps
|
||||
#ifndef RPMALLOC_FIRST_CLASS_HEAPS
|
||||
#define RPMALLOC_FIRST_CLASS_HEAPS 0
|
||||
#endif
|
||||
|
||||
//! Flag to rpaligned_realloc to not preserve content in reallocation
|
||||
#define RPMALLOC_NO_PRESERVE 1
|
||||
//! Flag to rpaligned_realloc to fail and return null pointer if grow cannot be done in-place,
|
||||
// in which case the original pointer is still valid (just like a call to realloc which failes to allocate
|
||||
// a new block).
|
||||
#define RPMALLOC_GROW_OR_FAIL 2
|
||||
|
||||
typedef struct rpmalloc_global_statistics_t {
|
||||
//! Current amount of virtual memory mapped, all of which might not have been committed (only if ENABLE_STATISTICS=1)
|
||||
size_t mapped;
|
||||
//! Peak amount of virtual memory mapped, all of which might not have been committed (only if ENABLE_STATISTICS=1)
|
||||
size_t mapped_peak;
|
||||
//! Current amount of memory in global caches for small and medium sizes (<32KiB)
|
||||
size_t cached;
|
||||
//! Current amount of memory allocated in huge allocations, i.e larger than LARGE_SIZE_LIMIT which is 2MiB by default (only if ENABLE_STATISTICS=1)
|
||||
size_t huge_alloc;
|
||||
//! Peak amount of memory allocated in huge allocations, i.e larger than LARGE_SIZE_LIMIT which is 2MiB by default (only if ENABLE_STATISTICS=1)
|
||||
size_t huge_alloc_peak;
|
||||
//! Total amount of memory mapped since initialization (only if ENABLE_STATISTICS=1)
|
||||
size_t mapped_total;
|
||||
//! Total amount of memory unmapped since initialization (only if ENABLE_STATISTICS=1)
|
||||
size_t unmapped_total;
|
||||
} rpmalloc_global_statistics_t;
|
||||
|
||||
typedef struct rpmalloc_thread_statistics_t {
|
||||
//! Current number of bytes available in thread size class caches for small and medium sizes (<32KiB)
|
||||
size_t sizecache;
|
||||
//! Current number of bytes available in thread span caches for small and medium sizes (<32KiB)
|
||||
size_t spancache;
|
||||
//! Total number of bytes transitioned from thread cache to global cache (only if ENABLE_STATISTICS=1)
|
||||
size_t thread_to_global;
|
||||
//! Total number of bytes transitioned from global cache to thread cache (only if ENABLE_STATISTICS=1)
|
||||
size_t global_to_thread;
|
||||
//! Per span count statistics (only if ENABLE_STATISTICS=1)
|
||||
struct {
|
||||
//! Currently used number of spans
|
||||
size_t current;
|
||||
//! High water mark of spans used
|
||||
size_t peak;
|
||||
//! Number of spans transitioned to global cache
|
||||
size_t to_global;
|
||||
//! Number of spans transitioned from global cache
|
||||
size_t from_global;
|
||||
//! Number of spans transitioned to thread cache
|
||||
size_t to_cache;
|
||||
//! Number of spans transitioned from thread cache
|
||||
size_t from_cache;
|
||||
//! Number of spans transitioned to reserved state
|
||||
size_t to_reserved;
|
||||
//! Number of spans transitioned from reserved state
|
||||
size_t from_reserved;
|
||||
//! Number of raw memory map calls (not hitting the reserve spans but resulting in actual OS mmap calls)
|
||||
size_t map_calls;
|
||||
} span_use[64];
|
||||
//! Per size class statistics (only if ENABLE_STATISTICS=1)
|
||||
struct {
|
||||
//! Current number of allocations
|
||||
size_t alloc_current;
|
||||
//! Peak number of allocations
|
||||
size_t alloc_peak;
|
||||
//! Total number of allocations
|
||||
size_t alloc_total;
|
||||
//! Total number of frees
|
||||
size_t free_total;
|
||||
//! Number of spans transitioned to cache
|
||||
size_t spans_to_cache;
|
||||
//! Number of spans transitioned from cache
|
||||
size_t spans_from_cache;
|
||||
//! Number of spans transitioned from reserved state
|
||||
size_t spans_from_reserved;
|
||||
//! Number of raw memory map calls (not hitting the reserve spans but resulting in actual OS mmap calls)
|
||||
size_t map_calls;
|
||||
} size_use[128];
|
||||
} rpmalloc_thread_statistics_t;
|
||||
|
||||
typedef struct rpmalloc_config_t {
|
||||
//! Map memory pages for the given number of bytes. The returned address MUST be
|
||||
// aligned to the rpmalloc span size, which will always be a power of two.
|
||||
// Optionally the function can store an alignment offset in the offset variable
|
||||
// in case it performs alignment and the returned pointer is offset from the
|
||||
// actual start of the memory region due to this alignment. The alignment offset
|
||||
// will be passed to the memory unmap function. The alignment offset MUST NOT be
|
||||
// larger than 65535 (storable in an uint16_t), if it is you must use natural
|
||||
// alignment to shift it into 16 bits. If you set a memory_map function, you
|
||||
// must also set a memory_unmap function or else the default implementation will
|
||||
// be used for both. This function must be thread safe, it can be called by
|
||||
// multiple threads simultaneously.
|
||||
void* (*memory_map)(size_t size, size_t* offset);
|
||||
//! Unmap the memory pages starting at address and spanning the given number of bytes.
|
||||
// If release is set to non-zero, the unmap is for an entire span range as returned by
|
||||
// a previous call to memory_map and that the entire range should be released. The
|
||||
// release argument holds the size of the entire span range. If release is set to 0,
|
||||
// the unmap is a partial decommit of a subset of the mapped memory range.
|
||||
// If you set a memory_unmap function, you must also set a memory_map function or
|
||||
// else the default implementation will be used for both. This function must be thread
|
||||
// safe, it can be called by multiple threads simultaneously.
|
||||
void (*memory_unmap)(void* address, size_t size, size_t offset, size_t release);
|
||||
//! Called when an assert fails, if asserts are enabled. Will use the standard assert()
|
||||
// if this is not set.
|
||||
void (*error_callback)(const char* message);
|
||||
//! Called when a call to map memory pages fails (out of memory). If this callback is
|
||||
// not set or returns zero the library will return a null pointer in the allocation
|
||||
// call. If this callback returns non-zero the map call will be retried. The argument
|
||||
// passed is the number of bytes that was requested in the map call. Only used if
|
||||
// the default system memory map function is used (memory_map callback is not set).
|
||||
int (*map_fail_callback)(size_t size);
|
||||
//! Size of memory pages. The page size MUST be a power of two. All memory mapping
|
||||
// requests to memory_map will be made with size set to a multiple of the page size.
|
||||
// Used if RPMALLOC_CONFIGURABLE is defined to 1, otherwise system page size is used.
|
||||
size_t page_size;
|
||||
//! Size of a span of memory blocks. MUST be a power of two, and in [4096,262144]
|
||||
// range (unless 0 - set to 0 to use the default span size). Used if RPMALLOC_CONFIGURABLE
|
||||
// is defined to 1.
|
||||
size_t span_size;
|
||||
//! Number of spans to map at each request to map new virtual memory blocks. This can
|
||||
// be used to minimize the system call overhead at the cost of virtual memory address
|
||||
// space. The extra mapped pages will not be written until actually used, so physical
|
||||
// committed memory should not be affected in the default implementation. Will be
|
||||
// aligned to a multiple of spans that match memory page size in case of huge pages.
|
||||
size_t span_map_count;
|
||||
//! Enable use of large/huge pages. If this flag is set to non-zero and page size is
|
||||
// zero, the allocator will try to enable huge pages and auto detect the configuration.
|
||||
// If this is set to non-zero and page_size is also non-zero, the allocator will
|
||||
// assume huge pages have been configured and enabled prior to initializing the
|
||||
// allocator.
|
||||
// For Windows, see https://docs.microsoft.com/en-us/windows/desktop/memory/large-page-support
|
||||
// For Linux, see https://www.kernel.org/doc/Documentation/vm/hugetlbpage.txt
|
||||
int enable_huge_pages;
|
||||
//! Respectively allocated pages and huge allocated pages names for systems
|
||||
// supporting it to be able to distinguish among anonymous regions.
|
||||
const char *page_name;
|
||||
const char *huge_page_name;
|
||||
} rpmalloc_config_t;
|
||||
|
||||
//! Initialize allocator with default configuration
|
||||
RPMALLOC_EXPORT int
|
||||
rpmalloc_initialize(void);
|
||||
|
||||
//! Initialize allocator with given configuration
|
||||
RPMALLOC_EXPORT int
|
||||
rpmalloc_initialize_config(const rpmalloc_config_t* config);
|
||||
|
||||
//! Get allocator configuration
|
||||
RPMALLOC_EXPORT const rpmalloc_config_t*
|
||||
rpmalloc_config(void);
|
||||
|
||||
//! Finalize allocator
|
||||
RPMALLOC_EXPORT void
|
||||
rpmalloc_finalize(void);
|
||||
|
||||
//! Initialize allocator for calling thread
|
||||
RPMALLOC_EXPORT void
|
||||
rpmalloc_thread_initialize(void);
|
||||
|
||||
//! Finalize allocator for calling thread
|
||||
RPMALLOC_EXPORT void
|
||||
rpmalloc_thread_finalize(int release_caches);
|
||||
|
||||
//! Perform deferred deallocations pending for the calling thread heap
|
||||
RPMALLOC_EXPORT void
|
||||
rpmalloc_thread_collect(void);
|
||||
|
||||
//! Query if allocator is initialized for calling thread
|
||||
RPMALLOC_EXPORT int
|
||||
rpmalloc_is_thread_initialized(void);
|
||||
|
||||
//! Get per-thread statistics
|
||||
RPMALLOC_EXPORT void
|
||||
rpmalloc_thread_statistics(rpmalloc_thread_statistics_t* stats);
|
||||
|
||||
//! Get global statistics
|
||||
RPMALLOC_EXPORT void
|
||||
rpmalloc_global_statistics(rpmalloc_global_statistics_t* stats);
|
||||
|
||||
//! Dump all statistics in human readable format to file (should be a FILE*)
|
||||
RPMALLOC_EXPORT void
|
||||
rpmalloc_dump_statistics(void* file);
|
||||
|
||||
//! Allocate a memory block of at least the given size
|
||||
RPMALLOC_EXPORT RPMALLOC_ALLOCATOR void*
|
||||
rpmalloc(size_t size) RPMALLOC_ATTRIB_MALLOC RPMALLOC_ATTRIB_ALLOC_SIZE(1);
|
||||
|
||||
//! Free the given memory block
|
||||
RPMALLOC_EXPORT void
|
||||
rpfree(void* ptr);
|
||||
|
||||
//! Allocate a memory block of at least the given size and zero initialize it
|
||||
RPMALLOC_EXPORT RPMALLOC_ALLOCATOR void*
|
||||
rpcalloc(size_t num, size_t size) RPMALLOC_ATTRIB_MALLOC RPMALLOC_ATTRIB_ALLOC_SIZE2(1, 2);
|
||||
|
||||
//! Reallocate the given block to at least the given size
|
||||
RPMALLOC_EXPORT RPMALLOC_ALLOCATOR void*
|
||||
rprealloc(void* ptr, size_t size) RPMALLOC_ATTRIB_MALLOC RPMALLOC_ATTRIB_ALLOC_SIZE(2);
|
||||
|
||||
//! Reallocate the given block to at least the given size and alignment,
|
||||
// with optional control flags (see RPMALLOC_NO_PRESERVE).
|
||||
// Alignment must be a power of two and a multiple of sizeof(void*),
|
||||
// and should ideally be less than memory page size. A caveat of rpmalloc
|
||||
// internals is that this must also be strictly less than the span size (default 64KiB)
|
||||
RPMALLOC_EXPORT RPMALLOC_ALLOCATOR void*
|
||||
rpaligned_realloc(void* ptr, size_t alignment, size_t size, size_t oldsize, unsigned int flags) RPMALLOC_ATTRIB_MALLOC RPMALLOC_ATTRIB_ALLOC_SIZE(3);
|
||||
|
||||
//! Allocate a memory block of at least the given size and alignment.
|
||||
// Alignment must be a power of two and a multiple of sizeof(void*),
|
||||
// and should ideally be less than memory page size. A caveat of rpmalloc
|
||||
// internals is that this must also be strictly less than the span size (default 64KiB)
|
||||
RPMALLOC_EXPORT RPMALLOC_ALLOCATOR void*
|
||||
rpaligned_alloc(size_t alignment, size_t size) RPMALLOC_ATTRIB_MALLOC RPMALLOC_ATTRIB_ALLOC_SIZE(2);
|
||||
|
||||
//! Allocate a memory block of at least the given size and alignment, and zero initialize it.
|
||||
// Alignment must be a power of two and a multiple of sizeof(void*),
|
||||
// and should ideally be less than memory page size. A caveat of rpmalloc
|
||||
// internals is that this must also be strictly less than the span size (default 64KiB)
|
||||
RPMALLOC_EXPORT RPMALLOC_ALLOCATOR void*
|
||||
rpaligned_calloc(size_t alignment, size_t num, size_t size) RPMALLOC_ATTRIB_MALLOC RPMALLOC_ATTRIB_ALLOC_SIZE2(2, 3);
|
||||
|
||||
//! Allocate a memory block of at least the given size and alignment.
|
||||
// Alignment must be a power of two and a multiple of sizeof(void*),
|
||||
// and should ideally be less than memory page size. A caveat of rpmalloc
|
||||
// internals is that this must also be strictly less than the span size (default 64KiB)
|
||||
RPMALLOC_EXPORT RPMALLOC_ALLOCATOR void*
|
||||
rpmemalign(size_t alignment, size_t size) RPMALLOC_ATTRIB_MALLOC RPMALLOC_ATTRIB_ALLOC_SIZE(2);
|
||||
|
||||
//! Allocate a memory block of at least the given size and alignment.
|
||||
// Alignment must be a power of two and a multiple of sizeof(void*),
|
||||
// and should ideally be less than memory page size. A caveat of rpmalloc
|
||||
// internals is that this must also be strictly less than the span size (default 64KiB)
|
||||
RPMALLOC_EXPORT int
|
||||
rpposix_memalign(void** memptr, size_t alignment, size_t size);
|
||||
|
||||
//! Query the usable size of the given memory block (from given pointer to the end of block)
|
||||
RPMALLOC_EXPORT size_t
|
||||
rpmalloc_usable_size(void* ptr);
|
||||
|
||||
//! Dummy empty function for forcing linker symbol inclusion
|
||||
RPMALLOC_EXPORT void
|
||||
rpmalloc_linker_reference(void);
|
||||
|
||||
#if RPMALLOC_FIRST_CLASS_HEAPS
|
||||
|
||||
//! Heap type
|
||||
typedef struct heap_t rpmalloc_heap_t;
|
||||
|
||||
//! Acquire a new heap. Will reuse existing released heaps or allocate memory for a new heap
|
||||
// if none available. Heap API is implemented with the strict assumption that only one single
|
||||
// thread will call heap functions for a given heap at any given time, no functions are thread safe.
|
||||
RPMALLOC_EXPORT rpmalloc_heap_t*
|
||||
rpmalloc_heap_acquire(void);
|
||||
|
||||
//! Release a heap (does NOT free the memory allocated by the heap, use rpmalloc_heap_free_all before destroying the heap).
|
||||
// Releasing a heap will enable it to be reused by other threads. Safe to pass a null pointer.
|
||||
RPMALLOC_EXPORT void
|
||||
rpmalloc_heap_release(rpmalloc_heap_t* heap);
|
||||
|
||||
//! Allocate a memory block of at least the given size using the given heap.
|
||||
RPMALLOC_EXPORT RPMALLOC_ALLOCATOR void*
|
||||
rpmalloc_heap_alloc(rpmalloc_heap_t* heap, size_t size) RPMALLOC_ATTRIB_MALLOC RPMALLOC_ATTRIB_ALLOC_SIZE(2);
|
||||
|
||||
//! Allocate a memory block of at least the given size using the given heap. The returned
|
||||
// block will have the requested alignment. Alignment must be a power of two and a multiple of sizeof(void*),
|
||||
// and should ideally be less than memory page size. A caveat of rpmalloc
|
||||
// internals is that this must also be strictly less than the span size (default 64KiB).
|
||||
RPMALLOC_EXPORT RPMALLOC_ALLOCATOR void*
|
||||
rpmalloc_heap_aligned_alloc(rpmalloc_heap_t* heap, size_t alignment, size_t size) RPMALLOC_ATTRIB_MALLOC RPMALLOC_ATTRIB_ALLOC_SIZE(3);
|
||||
|
||||
//! Allocate a memory block of at least the given size using the given heap and zero initialize it.
|
||||
RPMALLOC_EXPORT RPMALLOC_ALLOCATOR void*
|
||||
rpmalloc_heap_calloc(rpmalloc_heap_t* heap, size_t num, size_t size) RPMALLOC_ATTRIB_MALLOC RPMALLOC_ATTRIB_ALLOC_SIZE2(2, 3);
|
||||
|
||||
//! Allocate a memory block of at least the given size using the given heap and zero initialize it. The returned
|
||||
// block will have the requested alignment. Alignment must either be zero, or a power of two and a multiple of sizeof(void*),
|
||||
// and should ideally be less than memory page size. A caveat of rpmalloc
|
||||
// internals is that this must also be strictly less than the span size (default 64KiB).
|
||||
RPMALLOC_EXPORT RPMALLOC_ALLOCATOR void*
|
||||
rpmalloc_heap_aligned_calloc(rpmalloc_heap_t* heap, size_t alignment, size_t num, size_t size) RPMALLOC_ATTRIB_MALLOC RPMALLOC_ATTRIB_ALLOC_SIZE2(2, 3);
|
||||
|
||||
//! Reallocate the given block to at least the given size. The memory block MUST be allocated
|
||||
// by the same heap given to this function.
|
||||
RPMALLOC_EXPORT RPMALLOC_ALLOCATOR void*
|
||||
rpmalloc_heap_realloc(rpmalloc_heap_t* heap, void* ptr, size_t size, unsigned int flags) RPMALLOC_ATTRIB_MALLOC RPMALLOC_ATTRIB_ALLOC_SIZE(3);
|
||||
|
||||
//! Reallocate the given block to at least the given size. The memory block MUST be allocated
|
||||
// by the same heap given to this function. The returned block will have the requested alignment.
|
||||
// Alignment must be either zero, or a power of two and a multiple of sizeof(void*), and should ideally be
|
||||
// less than memory page size. A caveat of rpmalloc internals is that this must also be strictly less than
|
||||
// the span size (default 64KiB).
|
||||
RPMALLOC_EXPORT RPMALLOC_ALLOCATOR void*
|
||||
rpmalloc_heap_aligned_realloc(rpmalloc_heap_t* heap, void* ptr, size_t alignment, size_t size, unsigned int flags) RPMALLOC_ATTRIB_MALLOC RPMALLOC_ATTRIB_ALLOC_SIZE(4);
|
||||
|
||||
//! Free the given memory block from the given heap. The memory block MUST be allocated
|
||||
// by the same heap given to this function.
|
||||
RPMALLOC_EXPORT void
|
||||
rpmalloc_heap_free(rpmalloc_heap_t* heap, void* ptr);
|
||||
|
||||
//! Free all memory allocated by the heap
|
||||
RPMALLOC_EXPORT void
|
||||
rpmalloc_heap_free_all(rpmalloc_heap_t* heap);
|
||||
|
||||
//! Set the given heap as the current heap for the calling thread. A heap MUST only be current heap
|
||||
// for a single thread, a heap can never be shared between multiple threads. The previous
|
||||
// current heap for the calling thread is released to be reused by other threads.
|
||||
RPMALLOC_EXPORT void
|
||||
rpmalloc_heap_thread_set_current(rpmalloc_heap_t* heap);
|
||||
|
||||
//! Returns which heap the given pointer is allocated on
|
||||
RPMALLOC_EXPORT rpmalloc_heap_t*
|
||||
rpmalloc_get_heap_for_ptr(void* ptr);
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
/* rpmalloc.h - Memory allocator - Public Domain - 2016 Mattias Jansson
|
||||
*
|
||||
* This library provides a cross-platform lock free thread caching malloc implementation in C11.
|
||||
* The latest source code is always available at
|
||||
*
|
||||
* https://github.com/mjansson/rpmalloc
|
||||
*
|
||||
* This library is put in the public domain; you can redistribute it and/or modify it without any restrictions.
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#if defined(__clang__) || defined(__GNUC__)
|
||||
# define RPMALLOC_EXPORT __attribute__((visibility("default")))
|
||||
# define RPMALLOC_ALLOCATOR
|
||||
# if (defined(__clang_major__) && (__clang_major__ < 4)) || (defined(__GNUC__) && defined(ENABLE_PRELOAD) && ENABLE_PRELOAD)
|
||||
# define RPMALLOC_ATTRIB_MALLOC
|
||||
# define RPMALLOC_ATTRIB_ALLOC_SIZE(size)
|
||||
# define RPMALLOC_ATTRIB_ALLOC_SIZE2(count, size)
|
||||
# else
|
||||
# define RPMALLOC_ATTRIB_MALLOC __attribute__((__malloc__))
|
||||
# define RPMALLOC_ATTRIB_ALLOC_SIZE(size) __attribute__((alloc_size(size)))
|
||||
# define RPMALLOC_ATTRIB_ALLOC_SIZE2(count, size) __attribute__((alloc_size(count, size)))
|
||||
# endif
|
||||
# define RPMALLOC_CDECL
|
||||
#elif defined(_MSC_VER)
|
||||
# define RPMALLOC_EXPORT
|
||||
# define RPMALLOC_ALLOCATOR __declspec(allocator) __declspec(restrict)
|
||||
# define RPMALLOC_ATTRIB_MALLOC
|
||||
# define RPMALLOC_ATTRIB_ALLOC_SIZE(size)
|
||||
# define RPMALLOC_ATTRIB_ALLOC_SIZE2(count,size)
|
||||
# define RPMALLOC_CDECL __cdecl
|
||||
#else
|
||||
# define RPMALLOC_EXPORT
|
||||
# define RPMALLOC_ALLOCATOR
|
||||
# define RPMALLOC_ATTRIB_MALLOC
|
||||
# define RPMALLOC_ATTRIB_ALLOC_SIZE(size)
|
||||
# define RPMALLOC_ATTRIB_ALLOC_SIZE2(count,size)
|
||||
# define RPMALLOC_CDECL
|
||||
#endif
|
||||
|
||||
//! Define RPMALLOC_CONFIGURABLE to enable configuring sizes. Will introduce
|
||||
// a very small overhead due to some size calculations not being compile time constants
|
||||
#ifndef RPMALLOC_CONFIGURABLE
|
||||
#define RPMALLOC_CONFIGURABLE 0
|
||||
#endif
|
||||
|
||||
//! Define RPMALLOC_FIRST_CLASS_HEAPS to enable heap based API (rpmalloc_heap_* functions).
|
||||
// Will introduce a very small overhead to track fully allocated spans in heaps
|
||||
#ifndef RPMALLOC_FIRST_CLASS_HEAPS
|
||||
#define RPMALLOC_FIRST_CLASS_HEAPS 0
|
||||
#endif
|
||||
|
||||
//! Flag to rpaligned_realloc to not preserve content in reallocation
|
||||
#define RPMALLOC_NO_PRESERVE 1
|
||||
//! Flag to rpaligned_realloc to fail and return null pointer if grow cannot be done in-place,
|
||||
// in which case the original pointer is still valid (just like a call to realloc which failes to allocate
|
||||
// a new block).
|
||||
#define RPMALLOC_GROW_OR_FAIL 2
|
||||
|
||||
typedef struct rpmalloc_global_statistics_t {
|
||||
//! Current amount of virtual memory mapped, all of which might not have been committed (only if ENABLE_STATISTICS=1)
|
||||
size_t mapped;
|
||||
//! Peak amount of virtual memory mapped, all of which might not have been committed (only if ENABLE_STATISTICS=1)
|
||||
size_t mapped_peak;
|
||||
//! Current amount of memory in global caches for small and medium sizes (<32KiB)
|
||||
size_t cached;
|
||||
//! Current amount of memory allocated in huge allocations, i.e larger than LARGE_SIZE_LIMIT which is 2MiB by default (only if ENABLE_STATISTICS=1)
|
||||
size_t huge_alloc;
|
||||
//! Peak amount of memory allocated in huge allocations, i.e larger than LARGE_SIZE_LIMIT which is 2MiB by default (only if ENABLE_STATISTICS=1)
|
||||
size_t huge_alloc_peak;
|
||||
//! Total amount of memory mapped since initialization (only if ENABLE_STATISTICS=1)
|
||||
size_t mapped_total;
|
||||
//! Total amount of memory unmapped since initialization (only if ENABLE_STATISTICS=1)
|
||||
size_t unmapped_total;
|
||||
} rpmalloc_global_statistics_t;
|
||||
|
||||
typedef struct rpmalloc_thread_statistics_t {
|
||||
//! Current number of bytes available in thread size class caches for small and medium sizes (<32KiB)
|
||||
size_t sizecache;
|
||||
//! Current number of bytes available in thread span caches for small and medium sizes (<32KiB)
|
||||
size_t spancache;
|
||||
//! Total number of bytes transitioned from thread cache to global cache (only if ENABLE_STATISTICS=1)
|
||||
size_t thread_to_global;
|
||||
//! Total number of bytes transitioned from global cache to thread cache (only if ENABLE_STATISTICS=1)
|
||||
size_t global_to_thread;
|
||||
//! Per span count statistics (only if ENABLE_STATISTICS=1)
|
||||
struct {
|
||||
//! Currently used number of spans
|
||||
size_t current;
|
||||
//! High water mark of spans used
|
||||
size_t peak;
|
||||
//! Number of spans transitioned to global cache
|
||||
size_t to_global;
|
||||
//! Number of spans transitioned from global cache
|
||||
size_t from_global;
|
||||
//! Number of spans transitioned to thread cache
|
||||
size_t to_cache;
|
||||
//! Number of spans transitioned from thread cache
|
||||
size_t from_cache;
|
||||
//! Number of spans transitioned to reserved state
|
||||
size_t to_reserved;
|
||||
//! Number of spans transitioned from reserved state
|
||||
size_t from_reserved;
|
||||
//! Number of raw memory map calls (not hitting the reserve spans but resulting in actual OS mmap calls)
|
||||
size_t map_calls;
|
||||
} span_use[64];
|
||||
//! Per size class statistics (only if ENABLE_STATISTICS=1)
|
||||
struct {
|
||||
//! Current number of allocations
|
||||
size_t alloc_current;
|
||||
//! Peak number of allocations
|
||||
size_t alloc_peak;
|
||||
//! Total number of allocations
|
||||
size_t alloc_total;
|
||||
//! Total number of frees
|
||||
size_t free_total;
|
||||
//! Number of spans transitioned to cache
|
||||
size_t spans_to_cache;
|
||||
//! Number of spans transitioned from cache
|
||||
size_t spans_from_cache;
|
||||
//! Number of spans transitioned from reserved state
|
||||
size_t spans_from_reserved;
|
||||
//! Number of raw memory map calls (not hitting the reserve spans but resulting in actual OS mmap calls)
|
||||
size_t map_calls;
|
||||
} size_use[128];
|
||||
} rpmalloc_thread_statistics_t;
|
||||
|
||||
typedef struct rpmalloc_config_t {
|
||||
//! Map memory pages for the given number of bytes. The returned address MUST be
|
||||
// aligned to the rpmalloc span size, which will always be a power of two.
|
||||
// Optionally the function can store an alignment offset in the offset variable
|
||||
// in case it performs alignment and the returned pointer is offset from the
|
||||
// actual start of the memory region due to this alignment. The alignment offset
|
||||
// will be passed to the memory unmap function. The alignment offset MUST NOT be
|
||||
// larger than 65535 (storable in an uint16_t), if it is you must use natural
|
||||
// alignment to shift it into 16 bits. If you set a memory_map function, you
|
||||
// must also set a memory_unmap function or else the default implementation will
|
||||
// be used for both. This function must be thread safe, it can be called by
|
||||
// multiple threads simultaneously.
|
||||
void* (*memory_map)(size_t size, size_t* offset);
|
||||
//! Unmap the memory pages starting at address and spanning the given number of bytes.
|
||||
// If release is set to non-zero, the unmap is for an entire span range as returned by
|
||||
// a previous call to memory_map and that the entire range should be released. The
|
||||
// release argument holds the size of the entire span range. If release is set to 0,
|
||||
// the unmap is a partial decommit of a subset of the mapped memory range.
|
||||
// If you set a memory_unmap function, you must also set a memory_map function or
|
||||
// else the default implementation will be used for both. This function must be thread
|
||||
// safe, it can be called by multiple threads simultaneously.
|
||||
void (*memory_unmap)(void* address, size_t size, size_t offset, size_t release);
|
||||
//! Called when an assert fails, if asserts are enabled. Will use the standard assert()
|
||||
// if this is not set.
|
||||
void (*error_callback)(const char* message);
|
||||
//! Called when a call to map memory pages fails (out of memory). If this callback is
|
||||
// not set or returns zero the library will return a null pointer in the allocation
|
||||
// call. If this callback returns non-zero the map call will be retried. The argument
|
||||
// passed is the number of bytes that was requested in the map call. Only used if
|
||||
// the default system memory map function is used (memory_map callback is not set).
|
||||
int (*map_fail_callback)(size_t size);
|
||||
//! Size of memory pages. The page size MUST be a power of two. All memory mapping
|
||||
// requests to memory_map will be made with size set to a multiple of the page size.
|
||||
// Used if RPMALLOC_CONFIGURABLE is defined to 1, otherwise system page size is used.
|
||||
size_t page_size;
|
||||
//! Size of a span of memory blocks. MUST be a power of two, and in [4096,262144]
|
||||
// range (unless 0 - set to 0 to use the default span size). Used if RPMALLOC_CONFIGURABLE
|
||||
// is defined to 1.
|
||||
size_t span_size;
|
||||
//! Number of spans to map at each request to map new virtual memory blocks. This can
|
||||
// be used to minimize the system call overhead at the cost of virtual memory address
|
||||
// space. The extra mapped pages will not be written until actually used, so physical
|
||||
// committed memory should not be affected in the default implementation. Will be
|
||||
// aligned to a multiple of spans that match memory page size in case of huge pages.
|
||||
size_t span_map_count;
|
||||
//! Enable use of large/huge pages. If this flag is set to non-zero and page size is
|
||||
// zero, the allocator will try to enable huge pages and auto detect the configuration.
|
||||
// If this is set to non-zero and page_size is also non-zero, the allocator will
|
||||
// assume huge pages have been configured and enabled prior to initializing the
|
||||
// allocator.
|
||||
// For Windows, see https://docs.microsoft.com/en-us/windows/desktop/memory/large-page-support
|
||||
// For Linux, see https://www.kernel.org/doc/Documentation/vm/hugetlbpage.txt
|
||||
int enable_huge_pages;
|
||||
//! Respectively allocated pages and huge allocated pages names for systems
|
||||
// supporting it to be able to distinguish among anonymous regions.
|
||||
const char *page_name;
|
||||
const char *huge_page_name;
|
||||
} rpmalloc_config_t;
|
||||
|
||||
//! Initialize allocator with default configuration
|
||||
RPMALLOC_EXPORT int
|
||||
rpmalloc_initialize(void);
|
||||
|
||||
//! Initialize allocator with given configuration
|
||||
RPMALLOC_EXPORT int
|
||||
rpmalloc_initialize_config(const rpmalloc_config_t* config);
|
||||
|
||||
//! Get allocator configuration
|
||||
RPMALLOC_EXPORT const rpmalloc_config_t*
|
||||
rpmalloc_config(void);
|
||||
|
||||
//! Finalize allocator
|
||||
RPMALLOC_EXPORT void
|
||||
rpmalloc_finalize(void);
|
||||
|
||||
//! Initialize allocator for calling thread
|
||||
RPMALLOC_EXPORT void
|
||||
rpmalloc_thread_initialize(void);
|
||||
|
||||
//! Finalize allocator for calling thread
|
||||
RPMALLOC_EXPORT void
|
||||
rpmalloc_thread_finalize(int release_caches);
|
||||
|
||||
//! Perform deferred deallocations pending for the calling thread heap
|
||||
RPMALLOC_EXPORT void
|
||||
rpmalloc_thread_collect(void);
|
||||
|
||||
//! Query if allocator is initialized for calling thread
|
||||
RPMALLOC_EXPORT int
|
||||
rpmalloc_is_thread_initialized(void);
|
||||
|
||||
//! Get per-thread statistics
|
||||
RPMALLOC_EXPORT void
|
||||
rpmalloc_thread_statistics(rpmalloc_thread_statistics_t* stats);
|
||||
|
||||
//! Get global statistics
|
||||
RPMALLOC_EXPORT void
|
||||
rpmalloc_global_statistics(rpmalloc_global_statistics_t* stats);
|
||||
|
||||
//! Dump all statistics in human readable format to file (should be a FILE*)
|
||||
RPMALLOC_EXPORT void
|
||||
rpmalloc_dump_statistics(void* file);
|
||||
|
||||
//! Allocate a memory block of at least the given size
|
||||
RPMALLOC_EXPORT RPMALLOC_ALLOCATOR void*
|
||||
rpmalloc(size_t size) RPMALLOC_ATTRIB_MALLOC RPMALLOC_ATTRIB_ALLOC_SIZE(1);
|
||||
|
||||
//! Free the given memory block
|
||||
RPMALLOC_EXPORT void
|
||||
rpfree(void* ptr);
|
||||
|
||||
//! Allocate a memory block of at least the given size and zero initialize it
|
||||
RPMALLOC_EXPORT RPMALLOC_ALLOCATOR void*
|
||||
rpcalloc(size_t num, size_t size) RPMALLOC_ATTRIB_MALLOC RPMALLOC_ATTRIB_ALLOC_SIZE2(1, 2);
|
||||
|
||||
//! Reallocate the given block to at least the given size
|
||||
RPMALLOC_EXPORT RPMALLOC_ALLOCATOR void*
|
||||
rprealloc(void* ptr, size_t size) RPMALLOC_ATTRIB_MALLOC RPMALLOC_ATTRIB_ALLOC_SIZE(2);
|
||||
|
||||
//! Reallocate the given block to at least the given size and alignment,
|
||||
// with optional control flags (see RPMALLOC_NO_PRESERVE).
|
||||
// Alignment must be a power of two and a multiple of sizeof(void*),
|
||||
// and should ideally be less than memory page size. A caveat of rpmalloc
|
||||
// internals is that this must also be strictly less than the span size (default 64KiB)
|
||||
RPMALLOC_EXPORT RPMALLOC_ALLOCATOR void*
|
||||
rpaligned_realloc(void* ptr, size_t alignment, size_t size, size_t oldsize, unsigned int flags) RPMALLOC_ATTRIB_MALLOC RPMALLOC_ATTRIB_ALLOC_SIZE(3);
|
||||
|
||||
//! Allocate a memory block of at least the given size and alignment.
|
||||
// Alignment must be a power of two and a multiple of sizeof(void*),
|
||||
// and should ideally be less than memory page size. A caveat of rpmalloc
|
||||
// internals is that this must also be strictly less than the span size (default 64KiB)
|
||||
RPMALLOC_EXPORT RPMALLOC_ALLOCATOR void*
|
||||
rpaligned_alloc(size_t alignment, size_t size) RPMALLOC_ATTRIB_MALLOC RPMALLOC_ATTRIB_ALLOC_SIZE(2);
|
||||
|
||||
//! Allocate a memory block of at least the given size and alignment, and zero initialize it.
|
||||
// Alignment must be a power of two and a multiple of sizeof(void*),
|
||||
// and should ideally be less than memory page size. A caveat of rpmalloc
|
||||
// internals is that this must also be strictly less than the span size (default 64KiB)
|
||||
RPMALLOC_EXPORT RPMALLOC_ALLOCATOR void*
|
||||
rpaligned_calloc(size_t alignment, size_t num, size_t size) RPMALLOC_ATTRIB_MALLOC RPMALLOC_ATTRIB_ALLOC_SIZE2(2, 3);
|
||||
|
||||
//! Allocate a memory block of at least the given size and alignment.
|
||||
// Alignment must be a power of two and a multiple of sizeof(void*),
|
||||
// and should ideally be less than memory page size. A caveat of rpmalloc
|
||||
// internals is that this must also be strictly less than the span size (default 64KiB)
|
||||
RPMALLOC_EXPORT RPMALLOC_ALLOCATOR void*
|
||||
rpmemalign(size_t alignment, size_t size) RPMALLOC_ATTRIB_MALLOC RPMALLOC_ATTRIB_ALLOC_SIZE(2);
|
||||
|
||||
//! Allocate a memory block of at least the given size and alignment.
|
||||
// Alignment must be a power of two and a multiple of sizeof(void*),
|
||||
// and should ideally be less than memory page size. A caveat of rpmalloc
|
||||
// internals is that this must also be strictly less than the span size (default 64KiB)
|
||||
RPMALLOC_EXPORT int
|
||||
rpposix_memalign(void** memptr, size_t alignment, size_t size);
|
||||
|
||||
//! Query the usable size of the given memory block (from given pointer to the end of block)
|
||||
RPMALLOC_EXPORT size_t
|
||||
rpmalloc_usable_size(void* ptr);
|
||||
|
||||
//! Dummy empty function for forcing linker symbol inclusion
|
||||
RPMALLOC_EXPORT void
|
||||
rpmalloc_linker_reference(void);
|
||||
|
||||
#if RPMALLOC_FIRST_CLASS_HEAPS
|
||||
|
||||
//! Heap type
|
||||
typedef struct heap_t rpmalloc_heap_t;
|
||||
|
||||
//! Acquire a new heap. Will reuse existing released heaps or allocate memory for a new heap
|
||||
// if none available. Heap API is implemented with the strict assumption that only one single
|
||||
// thread will call heap functions for a given heap at any given time, no functions are thread safe.
|
||||
RPMALLOC_EXPORT rpmalloc_heap_t*
|
||||
rpmalloc_heap_acquire(void);
|
||||
|
||||
//! Release a heap (does NOT free the memory allocated by the heap, use rpmalloc_heap_free_all before destroying the heap).
|
||||
// Releasing a heap will enable it to be reused by other threads. Safe to pass a null pointer.
|
||||
RPMALLOC_EXPORT void
|
||||
rpmalloc_heap_release(rpmalloc_heap_t* heap);
|
||||
|
||||
//! Allocate a memory block of at least the given size using the given heap.
|
||||
RPMALLOC_EXPORT RPMALLOC_ALLOCATOR void*
|
||||
rpmalloc_heap_alloc(rpmalloc_heap_t* heap, size_t size) RPMALLOC_ATTRIB_MALLOC RPMALLOC_ATTRIB_ALLOC_SIZE(2);
|
||||
|
||||
//! Allocate a memory block of at least the given size using the given heap. The returned
|
||||
// block will have the requested alignment. Alignment must be a power of two and a multiple of sizeof(void*),
|
||||
// and should ideally be less than memory page size. A caveat of rpmalloc
|
||||
// internals is that this must also be strictly less than the span size (default 64KiB).
|
||||
RPMALLOC_EXPORT RPMALLOC_ALLOCATOR void*
|
||||
rpmalloc_heap_aligned_alloc(rpmalloc_heap_t* heap, size_t alignment, size_t size) RPMALLOC_ATTRIB_MALLOC RPMALLOC_ATTRIB_ALLOC_SIZE(3);
|
||||
|
||||
//! Allocate a memory block of at least the given size using the given heap and zero initialize it.
|
||||
RPMALLOC_EXPORT RPMALLOC_ALLOCATOR void*
|
||||
rpmalloc_heap_calloc(rpmalloc_heap_t* heap, size_t num, size_t size) RPMALLOC_ATTRIB_MALLOC RPMALLOC_ATTRIB_ALLOC_SIZE2(2, 3);
|
||||
|
||||
//! Allocate a memory block of at least the given size using the given heap and zero initialize it. The returned
|
||||
// block will have the requested alignment. Alignment must either be zero, or a power of two and a multiple of sizeof(void*),
|
||||
// and should ideally be less than memory page size. A caveat of rpmalloc
|
||||
// internals is that this must also be strictly less than the span size (default 64KiB).
|
||||
RPMALLOC_EXPORT RPMALLOC_ALLOCATOR void*
|
||||
rpmalloc_heap_aligned_calloc(rpmalloc_heap_t* heap, size_t alignment, size_t num, size_t size) RPMALLOC_ATTRIB_MALLOC RPMALLOC_ATTRIB_ALLOC_SIZE2(2, 3);
|
||||
|
||||
//! Reallocate the given block to at least the given size. The memory block MUST be allocated
|
||||
// by the same heap given to this function.
|
||||
RPMALLOC_EXPORT RPMALLOC_ALLOCATOR void*
|
||||
rpmalloc_heap_realloc(rpmalloc_heap_t* heap, void* ptr, size_t size, unsigned int flags) RPMALLOC_ATTRIB_MALLOC RPMALLOC_ATTRIB_ALLOC_SIZE(3);
|
||||
|
||||
//! Reallocate the given block to at least the given size. The memory block MUST be allocated
|
||||
// by the same heap given to this function. The returned block will have the requested alignment.
|
||||
// Alignment must be either zero, or a power of two and a multiple of sizeof(void*), and should ideally be
|
||||
// less than memory page size. A caveat of rpmalloc internals is that this must also be strictly less than
|
||||
// the span size (default 64KiB).
|
||||
RPMALLOC_EXPORT RPMALLOC_ALLOCATOR void*
|
||||
rpmalloc_heap_aligned_realloc(rpmalloc_heap_t* heap, void* ptr, size_t alignment, size_t size, unsigned int flags) RPMALLOC_ATTRIB_MALLOC RPMALLOC_ATTRIB_ALLOC_SIZE(4);
|
||||
|
||||
//! Free the given memory block from the given heap. The memory block MUST be allocated
|
||||
// by the same heap given to this function.
|
||||
RPMALLOC_EXPORT void
|
||||
rpmalloc_heap_free(rpmalloc_heap_t* heap, void* ptr);
|
||||
|
||||
//! Free all memory allocated by the heap
|
||||
RPMALLOC_EXPORT void
|
||||
rpmalloc_heap_free_all(rpmalloc_heap_t* heap);
|
||||
|
||||
//! Set the given heap as the current heap for the calling thread. A heap MUST only be current heap
|
||||
// for a single thread, a heap can never be shared between multiple threads. The previous
|
||||
// current heap for the calling thread is released to be reused by other threads.
|
||||
RPMALLOC_EXPORT void
|
||||
rpmalloc_heap_thread_set_current(rpmalloc_heap_t* heap);
|
||||
|
||||
//! Returns which heap the given pointer is allocated on
|
||||
RPMALLOC_EXPORT rpmalloc_heap_t*
|
||||
rpmalloc_get_heap_for_ptr(void* ptr);
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,481 +1,481 @@
|
|||
/** 1D, 2D, 3D and 4D float Perlin Simplex noise */
|
||||
/** Original code, stefan gustavson (PD). */
|
||||
|
||||
#ifdef SIMPLEX_C
|
||||
|
||||
/* SimplexNoise1234, Simplex noise with true analytic
|
||||
* derivative in 1D to 4D.
|
||||
*
|
||||
* Author: Stefan Gustavson, 2003-2005
|
||||
* Contact: stefan.gustavson@liu.se
|
||||
*
|
||||
* This code was GPL licensed until February 2011.
|
||||
* As the original author of this code, I hereby
|
||||
* release it into the public domain.
|
||||
* Please feel free to use it for whatever you want.
|
||||
* Credit is appreciated where appropriate, and I also
|
||||
* appreciate being told where this code finds any use,
|
||||
* but you may do as you like.
|
||||
*/
|
||||
|
||||
/*
|
||||
* This implementation is "Simplex Noise" as presented by
|
||||
* Ken Perlin at a relatively obscure and not often cited course
|
||||
* session "Real-Time Shading" at Siggraph 2001 (before real
|
||||
* time shading actually took off), under the title "hardware noise".
|
||||
* The 3D function is numerically equivalent to his Java reference
|
||||
* code available in the PDF course notes, although I re-implemented
|
||||
* it from scratch to get more readable code. The 1D, 2D and 4D cases
|
||||
* were implemented from scratch by me from Ken Perlin's text.
|
||||
*
|
||||
* This file has no dependencies on any other file, not even its own
|
||||
* header file. The header file is made for use by external code only.
|
||||
*/
|
||||
|
||||
// We don't really need to include this, but play nice and do it anyway.
|
||||
//#include "noise.c"
|
||||
|
||||
#define FASTFLOOR(x) ( ((int)(x)<=(x)) ? ((int)x) : (((int)x)-1) )
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// Static data
|
||||
|
||||
/*
|
||||
* Permutation table. This is just a random jumble of all numbers 0-255,
|
||||
* repeated twice to avoid wrapping the index at 255 for each lookup.
|
||||
* This needs to be exactly the same for all instances on all platforms,
|
||||
* so it's easiest to just keep it as static explicit data.
|
||||
* This also removes the need for any initialisation of this class.
|
||||
*
|
||||
* Note that making this an int[] instead of a char[] might make the
|
||||
* code run faster on platforms with a high penalty for unaligned single
|
||||
* byte addressing. Intel x86 is generally single-byte-friendly, but
|
||||
* some other CPUs are faster with 4-aligned reads.
|
||||
* However, a char[] is smaller, which avoids cache trashing, and that
|
||||
* is probably the most important aspect on most architectures.
|
||||
* This array is accessed a *lot* by the noise functions.
|
||||
* A vector-valued noise over 3D accesses it 96 times, and a
|
||||
* float-valued 4D noise 64 times. We want this to fit in the cache!
|
||||
*/
|
||||
unsigned char perm[512] = {151,160,137,91,90,15,
|
||||
131,13,201,95,96,53,194,233,7,225,140,36,103,30,69,142,8,99,37,240,21,10,23,
|
||||
190, 6,148,247,120,234,75,0,26,197,62,94,252,219,203,117,35,11,32,57,177,33,
|
||||
88,237,149,56,87,174,20,125,136,171,168, 68,175,74,165,71,134,139,48,27,166,
|
||||
77,146,158,231,83,111,229,122,60,211,133,230,220,105,92,41,55,46,245,40,244,
|
||||
102,143,54, 65,25,63,161, 1,216,80,73,209,76,132,187,208, 89,18,169,200,196,
|
||||
135,130,116,188,159,86,164,100,109,198,173,186, 3,64,52,217,226,250,124,123,
|
||||
5,202,38,147,118,126,255,82,85,212,207,206,59,227,47,16,58,17,182,189,28,42,
|
||||
223,183,170,213,119,248,152, 2,44,154,163, 70,221,153,101,155,167, 43,172,9,
|
||||
129,22,39,253, 19,98,108,110,79,113,224,232,178,185, 112,104,218,246,97,228,
|
||||
251,34,242,193,238,210,144,12,191,179,162,241, 81,51,145,235,249,14,239,107,
|
||||
49,192,214, 31,181,199,106,157,184, 84,204,176,115,121,50,45,127, 4,150,254,
|
||||
138,236,205,93,222,114,67,29,24,72,243,141,128,195,78,66,215,61,156,180,
|
||||
151,160,137,91,90,15,
|
||||
131,13,201,95,96,53,194,233,7,225,140,36,103,30,69,142,8,99,37,240,21,10,23,
|
||||
190, 6,148,247,120,234,75,0,26,197,62,94,252,219,203,117,35,11,32,57,177,33,
|
||||
88,237,149,56,87,174,20,125,136,171,168, 68,175,74,165,71,134,139,48,27,166,
|
||||
77,146,158,231,83,111,229,122,60,211,133,230,220,105,92,41,55,46,245,40,244,
|
||||
102,143,54, 65,25,63,161, 1,216,80,73,209,76,132,187,208, 89,18,169,200,196,
|
||||
135,130,116,188,159,86,164,100,109,198,173,186, 3,64,52,217,226,250,124,123,
|
||||
5,202,38,147,118,126,255,82,85,212,207,206,59,227,47,16,58,17,182,189,28,42,
|
||||
223,183,170,213,119,248,152, 2,44,154,163, 70,221,153,101,155,167, 43,172,9,
|
||||
129,22,39,253, 19,98,108,110,79,113,224,232,178,185, 112,104,218,246,97,228,
|
||||
251,34,242,193,238,210,144,12,191,179,162,241, 81,51,145,235,249,14,239,107,
|
||||
49,192,214, 31,181,199,106,157,184, 84,204,176,115,121,50,45,127, 4,150,254,
|
||||
138,236,205,93,222,114,67,29,24,72,243,141,128,195,78,66,215,61,156,180
|
||||
};
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
|
||||
/*
|
||||
* Helper functions to compute gradients-dot-residualvectors (1D to 4D)
|
||||
* Note that these generate gradients of more than unit length. To make
|
||||
* a close match with the value range of classic Perlin noise, the final
|
||||
* noise values need to be rescaled to fit nicely within [-1,1].
|
||||
* (The simplex noise functions as such also have different scaling.)
|
||||
* Note also that these noise functions are the most practical and useful
|
||||
* signed version of Perlin noise. To return values according to the
|
||||
* RenderMan specification from the SL noise() and pnoise() functions,
|
||||
* the noise values need to be scaled and offset to [0,1], like this:
|
||||
* float SLnoise = (noise(x,y,z) + 1.0) * 0.5;
|
||||
*/
|
||||
|
||||
float grad1( int hash, float x ) {
|
||||
int h = hash & 15;
|
||||
float grad = 1.0f + (h & 7); // Gradient value 1.0, 2.0, ..., 8.0
|
||||
if (h&8) grad = -grad; // Set a random sign for the gradient
|
||||
return ( grad * x ); // Multiply the gradient with the distance
|
||||
}
|
||||
|
||||
float grad2( int hash, float x, float y ) {
|
||||
int h = hash & 7; // Convert low 3 bits of hash code
|
||||
float u = h<4 ? x : y; // into 8 simple gradient directions,
|
||||
float v = h<4 ? y : x; // and compute the dot product with (x,y).
|
||||
return ((h&1)? -u : u) + ((h&2)? -2.0f*v : 2.0f*v);
|
||||
}
|
||||
|
||||
float grad3( int hash, float x, float y , float z ) {
|
||||
int h = hash & 15; // Convert low 4 bits of hash code into 12 simple
|
||||
float u = h<8 ? x : y; // gradient directions, and compute dot product.
|
||||
float v = h<4 ? y : h==12||h==14 ? x : z; // Fix repeats at h = 12 to 15
|
||||
return ((h&1)? -u : u) + ((h&2)? -v : v);
|
||||
}
|
||||
|
||||
float grad4( int hash, float x, float y, float z, float t ) {
|
||||
int h = hash & 31; // Convert low 5 bits of hash code into 32 simple
|
||||
float u = h<24 ? x : y; // gradient directions, and compute dot product.
|
||||
float v = h<16 ? y : z;
|
||||
float w = h<8 ? z : t;
|
||||
return ((h&1)? -u : u) + ((h&2)? -v : v) + ((h&4)? -w : w);
|
||||
}
|
||||
|
||||
// A lookup table to traverse the simplex around a given point in 4D.
|
||||
// Details can be found where this table is used, in the 4D noise method.
|
||||
/* TODO: This should not be required, backport it from Bill's GLSL code! */
|
||||
static unsigned char simplex[64][4] = {
|
||||
{0,1,2,3},{0,1,3,2},{0,0,0,0},{0,2,3,1},{0,0,0,0},{0,0,0,0},{0,0,0,0},{1,2,3,0},
|
||||
{0,2,1,3},{0,0,0,0},{0,3,1,2},{0,3,2,1},{0,0,0,0},{0,0,0,0},{0,0,0,0},{1,3,2,0},
|
||||
{0,0,0,0},{0,0,0,0},{0,0,0,0},{0,0,0,0},{0,0,0,0},{0,0,0,0},{0,0,0,0},{0,0,0,0},
|
||||
{1,2,0,3},{0,0,0,0},{1,3,0,2},{0,0,0,0},{0,0,0,0},{0,0,0,0},{2,3,0,1},{2,3,1,0},
|
||||
{1,0,2,3},{1,0,3,2},{0,0,0,0},{0,0,0,0},{0,0,0,0},{2,0,3,1},{0,0,0,0},{2,1,3,0},
|
||||
{0,0,0,0},{0,0,0,0},{0,0,0,0},{0,0,0,0},{0,0,0,0},{0,0,0,0},{0,0,0,0},{0,0,0,0},
|
||||
{2,0,1,3},{0,0,0,0},{0,0,0,0},{0,0,0,0},{3,0,1,2},{3,0,2,1},{0,0,0,0},{3,1,2,0},
|
||||
{2,1,0,3},{0,0,0,0},{0,0,0,0},{0,0,0,0},{3,1,0,2},{0,0,0,0},{3,2,0,1},{3,2,1,0}};
|
||||
|
||||
// 1D simplex noise
|
||||
float snoise1(float x) {
|
||||
|
||||
int i0 = FASTFLOOR(x);
|
||||
int i1 = i0 + 1;
|
||||
float x0 = x - i0;
|
||||
float x1 = x0 - 1.0f;
|
||||
|
||||
float n0, n1;
|
||||
|
||||
float t0 = 1.0f - x0*x0;
|
||||
// if(t0 < 0.0f) t0 = 0.0f; // this never happens for the 1D case
|
||||
t0 *= t0;
|
||||
n0 = t0 * t0 * grad1(perm[i0 & 0xff], x0);
|
||||
|
||||
float t1 = 1.0f - x1*x1;
|
||||
// if(t1 < 0.0f) t1 = 0.0f; // this never happens for the 1D case
|
||||
t1 *= t1;
|
||||
n1 = t1 * t1 * grad1(perm[i1 & 0xff], x1);
|
||||
// The maximum value of this noise is 8*(3/4)^4 = 2.53125
|
||||
// A factor of 0.395 would scale to fit exactly within [-1,1], but
|
||||
// we want to match PRMan's 1D noise, so we scale it down some more.
|
||||
return 0.25f * (n0 + n1);
|
||||
|
||||
}
|
||||
|
||||
// 2D simplex noise
|
||||
float snoise2(float x, float y) {
|
||||
|
||||
#define F2 0.366025403 // F2 = 0.5*(sqrt(3.0)-1.0)
|
||||
#define G2 0.211324865 // G2 = (3.0-Math.sqrt(3.0))/6.0
|
||||
|
||||
float n0, n1, n2; // Noise contributions from the three corners
|
||||
|
||||
// Skew the input space to determine which simplex cell we're in
|
||||
float s = (x+y)*F2; // Hairy factor for 2D
|
||||
float xs = x + s;
|
||||
float ys = y + s;
|
||||
int i = FASTFLOOR(xs);
|
||||
int j = FASTFLOOR(ys);
|
||||
|
||||
float t = (float)(i+j)*G2;
|
||||
float X0 = i-t; // Unskew the cell origin back to (x,y) space
|
||||
float Y0 = j-t;
|
||||
float x0 = x-X0; // The x,y distances from the cell origin
|
||||
float y0 = y-Y0;
|
||||
|
||||
// For the 2D case, the simplex shape is an equilateral triangle.
|
||||
// Determine which simplex we are in.
|
||||
int i1, j1; // Offsets for second (middle) corner of simplex in (i,j) coords
|
||||
if(x0>y0) {i1=1; j1=0;} // lower triangle, XY order: (0,0)->(1,0)->(1,1)
|
||||
else {i1=0; j1=1;} // upper triangle, YX order: (0,0)->(0,1)->(1,1)
|
||||
|
||||
// A step of (1,0) in (i,j) means a step of (1-c,-c) in (x,y), and
|
||||
// a step of (0,1) in (i,j) means a step of (-c,1-c) in (x,y), where
|
||||
// c = (3-sqrt(3))/6
|
||||
|
||||
float x1 = x0 - i1 + G2; // Offsets for middle corner in (x,y) unskewed coords
|
||||
float y1 = y0 - j1 + G2;
|
||||
float x2 = x0 - 1.0f + 2.0f * G2; // Offsets for last corner in (x,y) unskewed coords
|
||||
float y2 = y0 - 1.0f + 2.0f * G2;
|
||||
|
||||
// Wrap the integer indices at 256, to avoid indexing perm[] out of bounds
|
||||
int ii = i & 0xff;
|
||||
int jj = j & 0xff;
|
||||
|
||||
// Calculate the contribution from the three corners
|
||||
float t0 = 0.5f - x0*x0-y0*y0;
|
||||
if(t0 < 0.0f) n0 = 0.0f;
|
||||
else {
|
||||
t0 *= t0;
|
||||
n0 = t0 * t0 * grad2(perm[ii+perm[jj]], x0, y0);
|
||||
}
|
||||
|
||||
float t1 = 0.5f - x1*x1-y1*y1;
|
||||
if(t1 < 0.0f) n1 = 0.0f;
|
||||
else {
|
||||
t1 *= t1;
|
||||
n1 = t1 * t1 * grad2(perm[ii+i1+perm[jj+j1]], x1, y1);
|
||||
}
|
||||
|
||||
float t2 = 0.5f - x2*x2-y2*y2;
|
||||
if(t2 < 0.0f) n2 = 0.0f;
|
||||
else {
|
||||
t2 *= t2;
|
||||
n2 = t2 * t2 * grad2(perm[ii+1+perm[jj+1]], x2, y2);
|
||||
}
|
||||
|
||||
// Add contributions from each corner to get the final noise value.
|
||||
// The result is scaled to return values in the interval [-1,1].
|
||||
return 40.0f * (n0 + n1 + n2); // TODO: The scale factor is preliminary!
|
||||
}
|
||||
|
||||
// 3D simplex noise
|
||||
float snoise3(float x, float y, float z) {
|
||||
|
||||
// Simple skewing factors for the 3D case
|
||||
#define F3 0.333333333
|
||||
#define G3 0.166666667
|
||||
|
||||
float n0, n1, n2, n3; // Noise contributions from the four corners
|
||||
|
||||
// Skew the input space to determine which simplex cell we're in
|
||||
float s = (x+y+z)*F3; // Very nice and simple skew factor for 3D
|
||||
float xs = x+s;
|
||||
float ys = y+s;
|
||||
float zs = z+s;
|
||||
int i = FASTFLOOR(xs);
|
||||
int j = FASTFLOOR(ys);
|
||||
int k = FASTFLOOR(zs);
|
||||
|
||||
float t = (float)(i+j+k)*G3;
|
||||
float X0 = i-t; // Unskew the cell origin back to (x,y,z) space
|
||||
float Y0 = j-t;
|
||||
float Z0 = k-t;
|
||||
float x0 = x-X0; // The x,y,z distances from the cell origin
|
||||
float y0 = y-Y0;
|
||||
float z0 = z-Z0;
|
||||
|
||||
// For the 3D case, the simplex shape is a slightly irregular tetrahedron.
|
||||
// Determine which simplex we are in.
|
||||
int i1, j1, k1; // Offsets for second corner of simplex in (i,j,k) coords
|
||||
int i2, j2, k2; // Offsets for third corner of simplex in (i,j,k) coords
|
||||
|
||||
/* This code would benefit from a backport from the GLSL version! */
|
||||
if(x0>=y0) {
|
||||
if(y0>=z0)
|
||||
{ i1=1; j1=0; k1=0; i2=1; j2=1; k2=0; } // X Y Z order
|
||||
else if(x0>=z0) { i1=1; j1=0; k1=0; i2=1; j2=0; k2=1; } // X Z Y order
|
||||
else { i1=0; j1=0; k1=1; i2=1; j2=0; k2=1; } // Z X Y order
|
||||
}
|
||||
else { // x0<y0
|
||||
if(y0<z0) { i1=0; j1=0; k1=1; i2=0; j2=1; k2=1; } // Z Y X order
|
||||
else if(x0<z0) { i1=0; j1=1; k1=0; i2=0; j2=1; k2=1; } // Y Z X order
|
||||
else { i1=0; j1=1; k1=0; i2=1; j2=1; k2=0; } // Y X Z order
|
||||
}
|
||||
|
||||
// A step of (1,0,0) in (i,j,k) means a step of (1-c,-c,-c) in (x,y,z),
|
||||
// a step of (0,1,0) in (i,j,k) means a step of (-c,1-c,-c) in (x,y,z), and
|
||||
// a step of (0,0,1) in (i,j,k) means a step of (-c,-c,1-c) in (x,y,z), where
|
||||
// c = 1/6.
|
||||
|
||||
float x1 = x0 - i1 + G3; // Offsets for second corner in (x,y,z) coords
|
||||
float y1 = y0 - j1 + G3;
|
||||
float z1 = z0 - k1 + G3;
|
||||
float x2 = x0 - i2 + 2.0f*G3; // Offsets for third corner in (x,y,z) coords
|
||||
float y2 = y0 - j2 + 2.0f*G3;
|
||||
float z2 = z0 - k2 + 2.0f*G3;
|
||||
float x3 = x0 - 1.0f + 3.0f*G3; // Offsets for last corner in (x,y,z) coords
|
||||
float y3 = y0 - 1.0f + 3.0f*G3;
|
||||
float z3 = z0 - 1.0f + 3.0f*G3;
|
||||
|
||||
// Wrap the integer indices at 256, to avoid indexing perm[] out of bounds
|
||||
int ii = i & 0xff;
|
||||
int jj = j & 0xff;
|
||||
int kk = k & 0xff;
|
||||
|
||||
// Calculate the contribution from the four corners
|
||||
float t0 = 0.5f - x0*x0 - y0*y0 - z0*z0;
|
||||
if(t0 < 0.0f) n0 = 0.0f;
|
||||
else {
|
||||
t0 *= t0;
|
||||
n0 = t0 * t0 * grad3(perm[ii+perm[jj+perm[kk]]], x0, y0, z0);
|
||||
}
|
||||
|
||||
float t1 = 0.5f - x1*x1 - y1*y1 - z1*z1;
|
||||
if(t1 < 0.0f) n1 = 0.0f;
|
||||
else {
|
||||
t1 *= t1;
|
||||
n1 = t1 * t1 * grad3(perm[ii+i1+perm[jj+j1+perm[kk+k1]]], x1, y1, z1);
|
||||
}
|
||||
|
||||
float t2 = 0.5f - x2*x2 - y2*y2 - z2*z2;
|
||||
if(t2 < 0.0f) n2 = 0.0f;
|
||||
else {
|
||||
t2 *= t2;
|
||||
n2 = t2 * t2 * grad3(perm[ii+i2+perm[jj+j2+perm[kk+k2]]], x2, y2, z2);
|
||||
}
|
||||
|
||||
float t3 = 0.5f - x3*x3 - y3*y3 - z3*z3;
|
||||
if(t3<0.0f) n3 = 0.0f;
|
||||
else {
|
||||
t3 *= t3;
|
||||
n3 = t3 * t3 * grad3(perm[ii+1+perm[jj+1+perm[kk+1]]], x3, y3, z3);
|
||||
}
|
||||
|
||||
// Add contributions from each corner to get the final noise value.
|
||||
// The result is scaled to stay just inside [-1,1]
|
||||
return 72.0f * (n0 + n1 + n2 + n3);
|
||||
}
|
||||
|
||||
|
||||
// 4D simplex noise
|
||||
float snoise4(float x, float y, float z, float w) {
|
||||
|
||||
// The skewing and unskewing factors are hairy again for the 4D case
|
||||
#define F4 0.309016994 // F4 = (Math.sqrt(5.0)-1.0)/4.0
|
||||
#define G4 0.138196601 // G4 = (5.0-Math.sqrt(5.0))/20.0
|
||||
|
||||
float n0, n1, n2, n3, n4; // Noise contributions from the five corners
|
||||
|
||||
// Skew the (x,y,z,w) space to determine which cell of 24 simplices we're in
|
||||
float s = (x + y + z + w) * F4; // Factor for 4D skewing
|
||||
float xs = x + s;
|
||||
float ys = y + s;
|
||||
float zs = z + s;
|
||||
float ws = w + s;
|
||||
int i = FASTFLOOR(xs);
|
||||
int j = FASTFLOOR(ys);
|
||||
int k = FASTFLOOR(zs);
|
||||
int l = FASTFLOOR(ws);
|
||||
|
||||
float t = (i + j + k + l) * G4; // Factor for 4D unskewing
|
||||
float X0 = i - t; // Unskew the cell origin back to (x,y,z,w) space
|
||||
float Y0 = j - t;
|
||||
float Z0 = k - t;
|
||||
float W0 = l - t;
|
||||
|
||||
float x0 = x - X0; // The x,y,z,w distances from the cell origin
|
||||
float y0 = y - Y0;
|
||||
float z0 = z - Z0;
|
||||
float w0 = w - W0;
|
||||
|
||||
// For the 4D case, the simplex is a 4D shape I won't even try to describe.
|
||||
// To find out which of the 24 possible simplices we're in, we need to
|
||||
// determine the magnitude ordering of x0, y0, z0 and w0.
|
||||
// The method below is a good way of finding the ordering of x,y,z,w and
|
||||
// then find the correct traversal order for the simplex we?re in.
|
||||
// First, six pair-wise comparisons are performed between each possible pair
|
||||
// of the four coordinates, and the results are used to add up binary bits
|
||||
// for an integer index.
|
||||
int c1 = (x0 > y0) ? 32 : 0;
|
||||
int c2 = (x0 > z0) ? 16 : 0;
|
||||
int c3 = (y0 > z0) ? 8 : 0;
|
||||
int c4 = (x0 > w0) ? 4 : 0;
|
||||
int c5 = (y0 > w0) ? 2 : 0;
|
||||
int c6 = (z0 > w0) ? 1 : 0;
|
||||
int c = c1 + c2 + c3 + c4 + c5 + c6;
|
||||
|
||||
int i1, j1, k1, l1; // The integer offsets for the second simplex corner
|
||||
int i2, j2, k2, l2; // The integer offsets for the third simplex corner
|
||||
int i3, j3, k3, l3; // The integer offsets for the fourth simplex corner
|
||||
|
||||
// simplex[c] is a 4-vector with the numbers 0, 1, 2 and 3 in some order.
|
||||
// Many values of c will never occur, since e.g. x>y>z>w makes x<z, y<w and x<w
|
||||
// impossible. Only the 24 indices which have non-zero entries make any sense.
|
||||
// We use a thresholding to set the coordinates in turn from the largest magnitude.
|
||||
// The number 3 in the "simplex" array is at the position of the largest coordinate.
|
||||
i1 = simplex[c][0]>=3 ? 1 : 0;
|
||||
j1 = simplex[c][1]>=3 ? 1 : 0;
|
||||
k1 = simplex[c][2]>=3 ? 1 : 0;
|
||||
l1 = simplex[c][3]>=3 ? 1 : 0;
|
||||
// The number 2 in the "simplex" array is at the second largest coordinate.
|
||||
i2 = simplex[c][0]>=2 ? 1 : 0;
|
||||
j2 = simplex[c][1]>=2 ? 1 : 0;
|
||||
k2 = simplex[c][2]>=2 ? 1 : 0;
|
||||
l2 = simplex[c][3]>=2 ? 1 : 0;
|
||||
// The number 1 in the "simplex" array is at the second smallest coordinate.
|
||||
i3 = simplex[c][0]>=1 ? 1 : 0;
|
||||
j3 = simplex[c][1]>=1 ? 1 : 0;
|
||||
k3 = simplex[c][2]>=1 ? 1 : 0;
|
||||
l3 = simplex[c][3]>=1 ? 1 : 0;
|
||||
// The fifth corner has all coordinate offsets = 1, so no need to look that up.
|
||||
|
||||
float x1 = x0 - i1 + G4; // Offsets for second corner in (x,y,z,w) coords
|
||||
float y1 = y0 - j1 + G4;
|
||||
float z1 = z0 - k1 + G4;
|
||||
float w1 = w0 - l1 + G4;
|
||||
float x2 = x0 - i2 + 2.0f*G4; // Offsets for third corner in (x,y,z,w) coords
|
||||
float y2 = y0 - j2 + 2.0f*G4;
|
||||
float z2 = z0 - k2 + 2.0f*G4;
|
||||
float w2 = w0 - l2 + 2.0f*G4;
|
||||
float x3 = x0 - i3 + 3.0f*G4; // Offsets for fourth corner in (x,y,z,w) coords
|
||||
float y3 = y0 - j3 + 3.0f*G4;
|
||||
float z3 = z0 - k3 + 3.0f*G4;
|
||||
float w3 = w0 - l3 + 3.0f*G4;
|
||||
float x4 = x0 - 1.0f + 4.0f*G4; // Offsets for last corner in (x,y,z,w) coords
|
||||
float y4 = y0 - 1.0f + 4.0f*G4;
|
||||
float z4 = z0 - 1.0f + 4.0f*G4;
|
||||
float w4 = w0 - 1.0f + 4.0f*G4;
|
||||
|
||||
// Wrap the integer indices at 256, to avoid indexing perm[] out of bounds
|
||||
int ii = i & 0xff;
|
||||
int jj = j & 0xff;
|
||||
int kk = k & 0xff;
|
||||
int ll = l & 0xff;
|
||||
|
||||
// Calculate the contribution from the five corners
|
||||
float t0 = 0.5f - x0*x0 - y0*y0 - z0*z0 - w0*w0;
|
||||
if(t0 < 0.0f) n0 = 0.0f;
|
||||
else {
|
||||
t0 *= t0;
|
||||
n0 = t0 * t0 * grad4(perm[ii+perm[jj+perm[kk+perm[ll]]]], x0, y0, z0, w0);
|
||||
}
|
||||
|
||||
float t1 = 0.5f - x1*x1 - y1*y1 - z1*z1 - w1*w1;
|
||||
if(t1 < 0.0f) n1 = 0.0f;
|
||||
else {
|
||||
t1 *= t1;
|
||||
n1 = t1 * t1 * grad4(perm[ii+i1+perm[jj+j1+perm[kk+k1+perm[ll+l1]]]], x1, y1, z1, w1);
|
||||
}
|
||||
|
||||
float t2 = 0.5f - x2*x2 - y2*y2 - z2*z2 - w2*w2;
|
||||
if(t2 < 0.0f) n2 = 0.0f;
|
||||
else {
|
||||
t2 *= t2;
|
||||
n2 = t2 * t2 * grad4(perm[ii+i2+perm[jj+j2+perm[kk+k2+perm[ll+l2]]]], x2, y2, z2, w2);
|
||||
}
|
||||
|
||||
float t3 = 0.5f - x3*x3 - y3*y3 - z3*z3 - w3*w3;
|
||||
if(t3 < 0.0f) n3 = 0.0f;
|
||||
else {
|
||||
t3 *= t3;
|
||||
n3 = t3 * t3 * grad4(perm[ii+i3+perm[jj+j3+perm[kk+k3+perm[ll+l3]]]], x3, y3, z3, w3);
|
||||
}
|
||||
|
||||
float t4 = 0.5f - x4*x4 - y4*y4 - z4*z4 - w4*w4;
|
||||
if(t4 < 0.0f) n4 = 0.0f;
|
||||
else {
|
||||
t4 *= t4;
|
||||
n4 = t4 * t4 * grad4(perm[ii+1+perm[jj+1+perm[kk+1+perm[ll+1]]]], x4, y4, z4, w4);
|
||||
}
|
||||
|
||||
// Sum up and scale the result to cover the range [-1,1]
|
||||
return 62.0f * (n0 + n1 + n2 + n3 + n4);
|
||||
}
|
||||
|
||||
#undef F2
|
||||
#undef G2
|
||||
|
||||
#undef F3
|
||||
#undef G3
|
||||
|
||||
#undef F4
|
||||
#undef G4
|
||||
|
||||
#endif
|
||||
/** 1D, 2D, 3D and 4D float Perlin Simplex noise */
|
||||
/** Original code, stefan gustavson (PD). */
|
||||
|
||||
#ifdef SIMPLEX_C
|
||||
|
||||
/* SimplexNoise1234, Simplex noise with true analytic
|
||||
* derivative in 1D to 4D.
|
||||
*
|
||||
* Author: Stefan Gustavson, 2003-2005
|
||||
* Contact: stefan.gustavson@liu.se
|
||||
*
|
||||
* This code was GPL licensed until February 2011.
|
||||
* As the original author of this code, I hereby
|
||||
* release it into the public domain.
|
||||
* Please feel free to use it for whatever you want.
|
||||
* Credit is appreciated where appropriate, and I also
|
||||
* appreciate being told where this code finds any use,
|
||||
* but you may do as you like.
|
||||
*/
|
||||
|
||||
/*
|
||||
* This implementation is "Simplex Noise" as presented by
|
||||
* Ken Perlin at a relatively obscure and not often cited course
|
||||
* session "Real-Time Shading" at Siggraph 2001 (before real
|
||||
* time shading actually took off), under the title "hardware noise".
|
||||
* The 3D function is numerically equivalent to his Java reference
|
||||
* code available in the PDF course notes, although I re-implemented
|
||||
* it from scratch to get more readable code. The 1D, 2D and 4D cases
|
||||
* were implemented from scratch by me from Ken Perlin's text.
|
||||
*
|
||||
* This file has no dependencies on any other file, not even its own
|
||||
* header file. The header file is made for use by external code only.
|
||||
*/
|
||||
|
||||
// We don't really need to include this, but play nice and do it anyway.
|
||||
//#include "noise.c"
|
||||
|
||||
#define FASTFLOOR(x) ( ((int)(x)<=(x)) ? ((int)x) : (((int)x)-1) )
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// Static data
|
||||
|
||||
/*
|
||||
* Permutation table. This is just a random jumble of all numbers 0-255,
|
||||
* repeated twice to avoid wrapping the index at 255 for each lookup.
|
||||
* This needs to be exactly the same for all instances on all platforms,
|
||||
* so it's easiest to just keep it as static explicit data.
|
||||
* This also removes the need for any initialisation of this class.
|
||||
*
|
||||
* Note that making this an int[] instead of a char[] might make the
|
||||
* code run faster on platforms with a high penalty for unaligned single
|
||||
* byte addressing. Intel x86 is generally single-byte-friendly, but
|
||||
* some other CPUs are faster with 4-aligned reads.
|
||||
* However, a char[] is smaller, which avoids cache trashing, and that
|
||||
* is probably the most important aspect on most architectures.
|
||||
* This array is accessed a *lot* by the noise functions.
|
||||
* A vector-valued noise over 3D accesses it 96 times, and a
|
||||
* float-valued 4D noise 64 times. We want this to fit in the cache!
|
||||
*/
|
||||
unsigned char perm[512] = {151,160,137,91,90,15,
|
||||
131,13,201,95,96,53,194,233,7,225,140,36,103,30,69,142,8,99,37,240,21,10,23,
|
||||
190, 6,148,247,120,234,75,0,26,197,62,94,252,219,203,117,35,11,32,57,177,33,
|
||||
88,237,149,56,87,174,20,125,136,171,168, 68,175,74,165,71,134,139,48,27,166,
|
||||
77,146,158,231,83,111,229,122,60,211,133,230,220,105,92,41,55,46,245,40,244,
|
||||
102,143,54, 65,25,63,161, 1,216,80,73,209,76,132,187,208, 89,18,169,200,196,
|
||||
135,130,116,188,159,86,164,100,109,198,173,186, 3,64,52,217,226,250,124,123,
|
||||
5,202,38,147,118,126,255,82,85,212,207,206,59,227,47,16,58,17,182,189,28,42,
|
||||
223,183,170,213,119,248,152, 2,44,154,163, 70,221,153,101,155,167, 43,172,9,
|
||||
129,22,39,253, 19,98,108,110,79,113,224,232,178,185, 112,104,218,246,97,228,
|
||||
251,34,242,193,238,210,144,12,191,179,162,241, 81,51,145,235,249,14,239,107,
|
||||
49,192,214, 31,181,199,106,157,184, 84,204,176,115,121,50,45,127, 4,150,254,
|
||||
138,236,205,93,222,114,67,29,24,72,243,141,128,195,78,66,215,61,156,180,
|
||||
151,160,137,91,90,15,
|
||||
131,13,201,95,96,53,194,233,7,225,140,36,103,30,69,142,8,99,37,240,21,10,23,
|
||||
190, 6,148,247,120,234,75,0,26,197,62,94,252,219,203,117,35,11,32,57,177,33,
|
||||
88,237,149,56,87,174,20,125,136,171,168, 68,175,74,165,71,134,139,48,27,166,
|
||||
77,146,158,231,83,111,229,122,60,211,133,230,220,105,92,41,55,46,245,40,244,
|
||||
102,143,54, 65,25,63,161, 1,216,80,73,209,76,132,187,208, 89,18,169,200,196,
|
||||
135,130,116,188,159,86,164,100,109,198,173,186, 3,64,52,217,226,250,124,123,
|
||||
5,202,38,147,118,126,255,82,85,212,207,206,59,227,47,16,58,17,182,189,28,42,
|
||||
223,183,170,213,119,248,152, 2,44,154,163, 70,221,153,101,155,167, 43,172,9,
|
||||
129,22,39,253, 19,98,108,110,79,113,224,232,178,185, 112,104,218,246,97,228,
|
||||
251,34,242,193,238,210,144,12,191,179,162,241, 81,51,145,235,249,14,239,107,
|
||||
49,192,214, 31,181,199,106,157,184, 84,204,176,115,121,50,45,127, 4,150,254,
|
||||
138,236,205,93,222,114,67,29,24,72,243,141,128,195,78,66,215,61,156,180
|
||||
};
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
|
||||
/*
|
||||
* Helper functions to compute gradients-dot-residualvectors (1D to 4D)
|
||||
* Note that these generate gradients of more than unit length. To make
|
||||
* a close match with the value range of classic Perlin noise, the final
|
||||
* noise values need to be rescaled to fit nicely within [-1,1].
|
||||
* (The simplex noise functions as such also have different scaling.)
|
||||
* Note also that these noise functions are the most practical and useful
|
||||
* signed version of Perlin noise. To return values according to the
|
||||
* RenderMan specification from the SL noise() and pnoise() functions,
|
||||
* the noise values need to be scaled and offset to [0,1], like this:
|
||||
* float SLnoise = (noise(x,y,z) + 1.0) * 0.5;
|
||||
*/
|
||||
|
||||
float grad1( int hash, float x ) {
|
||||
int h = hash & 15;
|
||||
float grad = 1.0f + (h & 7); // Gradient value 1.0, 2.0, ..., 8.0
|
||||
if (h&8) grad = -grad; // Set a random sign for the gradient
|
||||
return ( grad * x ); // Multiply the gradient with the distance
|
||||
}
|
||||
|
||||
float grad2( int hash, float x, float y ) {
|
||||
int h = hash & 7; // Convert low 3 bits of hash code
|
||||
float u = h<4 ? x : y; // into 8 simple gradient directions,
|
||||
float v = h<4 ? y : x; // and compute the dot product with (x,y).
|
||||
return ((h&1)? -u : u) + ((h&2)? -2.0f*v : 2.0f*v);
|
||||
}
|
||||
|
||||
float grad3( int hash, float x, float y , float z ) {
|
||||
int h = hash & 15; // Convert low 4 bits of hash code into 12 simple
|
||||
float u = h<8 ? x : y; // gradient directions, and compute dot product.
|
||||
float v = h<4 ? y : h==12||h==14 ? x : z; // Fix repeats at h = 12 to 15
|
||||
return ((h&1)? -u : u) + ((h&2)? -v : v);
|
||||
}
|
||||
|
||||
float grad4( int hash, float x, float y, float z, float t ) {
|
||||
int h = hash & 31; // Convert low 5 bits of hash code into 32 simple
|
||||
float u = h<24 ? x : y; // gradient directions, and compute dot product.
|
||||
float v = h<16 ? y : z;
|
||||
float w = h<8 ? z : t;
|
||||
return ((h&1)? -u : u) + ((h&2)? -v : v) + ((h&4)? -w : w);
|
||||
}
|
||||
|
||||
// A lookup table to traverse the simplex around a given point in 4D.
|
||||
// Details can be found where this table is used, in the 4D noise method.
|
||||
/* TODO: This should not be required, backport it from Bill's GLSL code! */
|
||||
static unsigned char simplex[64][4] = {
|
||||
{0,1,2,3},{0,1,3,2},{0,0,0,0},{0,2,3,1},{0,0,0,0},{0,0,0,0},{0,0,0,0},{1,2,3,0},
|
||||
{0,2,1,3},{0,0,0,0},{0,3,1,2},{0,3,2,1},{0,0,0,0},{0,0,0,0},{0,0,0,0},{1,3,2,0},
|
||||
{0,0,0,0},{0,0,0,0},{0,0,0,0},{0,0,0,0},{0,0,0,0},{0,0,0,0},{0,0,0,0},{0,0,0,0},
|
||||
{1,2,0,3},{0,0,0,0},{1,3,0,2},{0,0,0,0},{0,0,0,0},{0,0,0,0},{2,3,0,1},{2,3,1,0},
|
||||
{1,0,2,3},{1,0,3,2},{0,0,0,0},{0,0,0,0},{0,0,0,0},{2,0,3,1},{0,0,0,0},{2,1,3,0},
|
||||
{0,0,0,0},{0,0,0,0},{0,0,0,0},{0,0,0,0},{0,0,0,0},{0,0,0,0},{0,0,0,0},{0,0,0,0},
|
||||
{2,0,1,3},{0,0,0,0},{0,0,0,0},{0,0,0,0},{3,0,1,2},{3,0,2,1},{0,0,0,0},{3,1,2,0},
|
||||
{2,1,0,3},{0,0,0,0},{0,0,0,0},{0,0,0,0},{3,1,0,2},{0,0,0,0},{3,2,0,1},{3,2,1,0}};
|
||||
|
||||
// 1D simplex noise
|
||||
float snoise1(float x) {
|
||||
|
||||
int i0 = FASTFLOOR(x);
|
||||
int i1 = i0 + 1;
|
||||
float x0 = x - i0;
|
||||
float x1 = x0 - 1.0f;
|
||||
|
||||
float n0, n1;
|
||||
|
||||
float t0 = 1.0f - x0*x0;
|
||||
// if(t0 < 0.0f) t0 = 0.0f; // this never happens for the 1D case
|
||||
t0 *= t0;
|
||||
n0 = t0 * t0 * grad1(perm[i0 & 0xff], x0);
|
||||
|
||||
float t1 = 1.0f - x1*x1;
|
||||
// if(t1 < 0.0f) t1 = 0.0f; // this never happens for the 1D case
|
||||
t1 *= t1;
|
||||
n1 = t1 * t1 * grad1(perm[i1 & 0xff], x1);
|
||||
// The maximum value of this noise is 8*(3/4)^4 = 2.53125
|
||||
// A factor of 0.395 would scale to fit exactly within [-1,1], but
|
||||
// we want to match PRMan's 1D noise, so we scale it down some more.
|
||||
return 0.25f * (n0 + n1);
|
||||
|
||||
}
|
||||
|
||||
// 2D simplex noise
|
||||
float snoise2(float x, float y) {
|
||||
|
||||
#define F2 0.366025403 // F2 = 0.5*(sqrt(3.0)-1.0)
|
||||
#define G2 0.211324865 // G2 = (3.0-Math.sqrt(3.0))/6.0
|
||||
|
||||
float n0, n1, n2; // Noise contributions from the three corners
|
||||
|
||||
// Skew the input space to determine which simplex cell we're in
|
||||
float s = (x+y)*F2; // Hairy factor for 2D
|
||||
float xs = x + s;
|
||||
float ys = y + s;
|
||||
int i = FASTFLOOR(xs);
|
||||
int j = FASTFLOOR(ys);
|
||||
|
||||
float t = (float)(i+j)*G2;
|
||||
float X0 = i-t; // Unskew the cell origin back to (x,y) space
|
||||
float Y0 = j-t;
|
||||
float x0 = x-X0; // The x,y distances from the cell origin
|
||||
float y0 = y-Y0;
|
||||
|
||||
// For the 2D case, the simplex shape is an equilateral triangle.
|
||||
// Determine which simplex we are in.
|
||||
int i1, j1; // Offsets for second (middle) corner of simplex in (i,j) coords
|
||||
if(x0>y0) {i1=1; j1=0;} // lower triangle, XY order: (0,0)->(1,0)->(1,1)
|
||||
else {i1=0; j1=1;} // upper triangle, YX order: (0,0)->(0,1)->(1,1)
|
||||
|
||||
// A step of (1,0) in (i,j) means a step of (1-c,-c) in (x,y), and
|
||||
// a step of (0,1) in (i,j) means a step of (-c,1-c) in (x,y), where
|
||||
// c = (3-sqrt(3))/6
|
||||
|
||||
float x1 = x0 - i1 + G2; // Offsets for middle corner in (x,y) unskewed coords
|
||||
float y1 = y0 - j1 + G2;
|
||||
float x2 = x0 - 1.0f + 2.0f * G2; // Offsets for last corner in (x,y) unskewed coords
|
||||
float y2 = y0 - 1.0f + 2.0f * G2;
|
||||
|
||||
// Wrap the integer indices at 256, to avoid indexing perm[] out of bounds
|
||||
int ii = i & 0xff;
|
||||
int jj = j & 0xff;
|
||||
|
||||
// Calculate the contribution from the three corners
|
||||
float t0 = 0.5f - x0*x0-y0*y0;
|
||||
if(t0 < 0.0f) n0 = 0.0f;
|
||||
else {
|
||||
t0 *= t0;
|
||||
n0 = t0 * t0 * grad2(perm[ii+perm[jj]], x0, y0);
|
||||
}
|
||||
|
||||
float t1 = 0.5f - x1*x1-y1*y1;
|
||||
if(t1 < 0.0f) n1 = 0.0f;
|
||||
else {
|
||||
t1 *= t1;
|
||||
n1 = t1 * t1 * grad2(perm[ii+i1+perm[jj+j1]], x1, y1);
|
||||
}
|
||||
|
||||
float t2 = 0.5f - x2*x2-y2*y2;
|
||||
if(t2 < 0.0f) n2 = 0.0f;
|
||||
else {
|
||||
t2 *= t2;
|
||||
n2 = t2 * t2 * grad2(perm[ii+1+perm[jj+1]], x2, y2);
|
||||
}
|
||||
|
||||
// Add contributions from each corner to get the final noise value.
|
||||
// The result is scaled to return values in the interval [-1,1].
|
||||
return 40.0f * (n0 + n1 + n2); // TODO: The scale factor is preliminary!
|
||||
}
|
||||
|
||||
// 3D simplex noise
|
||||
float snoise3(float x, float y, float z) {
|
||||
|
||||
// Simple skewing factors for the 3D case
|
||||
#define F3 0.333333333
|
||||
#define G3 0.166666667
|
||||
|
||||
float n0, n1, n2, n3; // Noise contributions from the four corners
|
||||
|
||||
// Skew the input space to determine which simplex cell we're in
|
||||
float s = (x+y+z)*F3; // Very nice and simple skew factor for 3D
|
||||
float xs = x+s;
|
||||
float ys = y+s;
|
||||
float zs = z+s;
|
||||
int i = FASTFLOOR(xs);
|
||||
int j = FASTFLOOR(ys);
|
||||
int k = FASTFLOOR(zs);
|
||||
|
||||
float t = (float)(i+j+k)*G3;
|
||||
float X0 = i-t; // Unskew the cell origin back to (x,y,z) space
|
||||
float Y0 = j-t;
|
||||
float Z0 = k-t;
|
||||
float x0 = x-X0; // The x,y,z distances from the cell origin
|
||||
float y0 = y-Y0;
|
||||
float z0 = z-Z0;
|
||||
|
||||
// For the 3D case, the simplex shape is a slightly irregular tetrahedron.
|
||||
// Determine which simplex we are in.
|
||||
int i1, j1, k1; // Offsets for second corner of simplex in (i,j,k) coords
|
||||
int i2, j2, k2; // Offsets for third corner of simplex in (i,j,k) coords
|
||||
|
||||
/* This code would benefit from a backport from the GLSL version! */
|
||||
if(x0>=y0) {
|
||||
if(y0>=z0)
|
||||
{ i1=1; j1=0; k1=0; i2=1; j2=1; k2=0; } // X Y Z order
|
||||
else if(x0>=z0) { i1=1; j1=0; k1=0; i2=1; j2=0; k2=1; } // X Z Y order
|
||||
else { i1=0; j1=0; k1=1; i2=1; j2=0; k2=1; } // Z X Y order
|
||||
}
|
||||
else { // x0<y0
|
||||
if(y0<z0) { i1=0; j1=0; k1=1; i2=0; j2=1; k2=1; } // Z Y X order
|
||||
else if(x0<z0) { i1=0; j1=1; k1=0; i2=0; j2=1; k2=1; } // Y Z X order
|
||||
else { i1=0; j1=1; k1=0; i2=1; j2=1; k2=0; } // Y X Z order
|
||||
}
|
||||
|
||||
// A step of (1,0,0) in (i,j,k) means a step of (1-c,-c,-c) in (x,y,z),
|
||||
// a step of (0,1,0) in (i,j,k) means a step of (-c,1-c,-c) in (x,y,z), and
|
||||
// a step of (0,0,1) in (i,j,k) means a step of (-c,-c,1-c) in (x,y,z), where
|
||||
// c = 1/6.
|
||||
|
||||
float x1 = x0 - i1 + G3; // Offsets for second corner in (x,y,z) coords
|
||||
float y1 = y0 - j1 + G3;
|
||||
float z1 = z0 - k1 + G3;
|
||||
float x2 = x0 - i2 + 2.0f*G3; // Offsets for third corner in (x,y,z) coords
|
||||
float y2 = y0 - j2 + 2.0f*G3;
|
||||
float z2 = z0 - k2 + 2.0f*G3;
|
||||
float x3 = x0 - 1.0f + 3.0f*G3; // Offsets for last corner in (x,y,z) coords
|
||||
float y3 = y0 - 1.0f + 3.0f*G3;
|
||||
float z3 = z0 - 1.0f + 3.0f*G3;
|
||||
|
||||
// Wrap the integer indices at 256, to avoid indexing perm[] out of bounds
|
||||
int ii = i & 0xff;
|
||||
int jj = j & 0xff;
|
||||
int kk = k & 0xff;
|
||||
|
||||
// Calculate the contribution from the four corners
|
||||
float t0 = 0.5f - x0*x0 - y0*y0 - z0*z0;
|
||||
if(t0 < 0.0f) n0 = 0.0f;
|
||||
else {
|
||||
t0 *= t0;
|
||||
n0 = t0 * t0 * grad3(perm[ii+perm[jj+perm[kk]]], x0, y0, z0);
|
||||
}
|
||||
|
||||
float t1 = 0.5f - x1*x1 - y1*y1 - z1*z1;
|
||||
if(t1 < 0.0f) n1 = 0.0f;
|
||||
else {
|
||||
t1 *= t1;
|
||||
n1 = t1 * t1 * grad3(perm[ii+i1+perm[jj+j1+perm[kk+k1]]], x1, y1, z1);
|
||||
}
|
||||
|
||||
float t2 = 0.5f - x2*x2 - y2*y2 - z2*z2;
|
||||
if(t2 < 0.0f) n2 = 0.0f;
|
||||
else {
|
||||
t2 *= t2;
|
||||
n2 = t2 * t2 * grad3(perm[ii+i2+perm[jj+j2+perm[kk+k2]]], x2, y2, z2);
|
||||
}
|
||||
|
||||
float t3 = 0.5f - x3*x3 - y3*y3 - z3*z3;
|
||||
if(t3<0.0f) n3 = 0.0f;
|
||||
else {
|
||||
t3 *= t3;
|
||||
n3 = t3 * t3 * grad3(perm[ii+1+perm[jj+1+perm[kk+1]]], x3, y3, z3);
|
||||
}
|
||||
|
||||
// Add contributions from each corner to get the final noise value.
|
||||
// The result is scaled to stay just inside [-1,1]
|
||||
return 72.0f * (n0 + n1 + n2 + n3);
|
||||
}
|
||||
|
||||
|
||||
// 4D simplex noise
|
||||
float snoise4(float x, float y, float z, float w) {
|
||||
|
||||
// The skewing and unskewing factors are hairy again for the 4D case
|
||||
#define F4 0.309016994 // F4 = (Math.sqrt(5.0)-1.0)/4.0
|
||||
#define G4 0.138196601 // G4 = (5.0-Math.sqrt(5.0))/20.0
|
||||
|
||||
float n0, n1, n2, n3, n4; // Noise contributions from the five corners
|
||||
|
||||
// Skew the (x,y,z,w) space to determine which cell of 24 simplices we're in
|
||||
float s = (x + y + z + w) * F4; // Factor for 4D skewing
|
||||
float xs = x + s;
|
||||
float ys = y + s;
|
||||
float zs = z + s;
|
||||
float ws = w + s;
|
||||
int i = FASTFLOOR(xs);
|
||||
int j = FASTFLOOR(ys);
|
||||
int k = FASTFLOOR(zs);
|
||||
int l = FASTFLOOR(ws);
|
||||
|
||||
float t = (i + j + k + l) * G4; // Factor for 4D unskewing
|
||||
float X0 = i - t; // Unskew the cell origin back to (x,y,z,w) space
|
||||
float Y0 = j - t;
|
||||
float Z0 = k - t;
|
||||
float W0 = l - t;
|
||||
|
||||
float x0 = x - X0; // The x,y,z,w distances from the cell origin
|
||||
float y0 = y - Y0;
|
||||
float z0 = z - Z0;
|
||||
float w0 = w - W0;
|
||||
|
||||
// For the 4D case, the simplex is a 4D shape I won't even try to describe.
|
||||
// To find out which of the 24 possible simplices we're in, we need to
|
||||
// determine the magnitude ordering of x0, y0, z0 and w0.
|
||||
// The method below is a good way of finding the ordering of x,y,z,w and
|
||||
// then find the correct traversal order for the simplex we?re in.
|
||||
// First, six pair-wise comparisons are performed between each possible pair
|
||||
// of the four coordinates, and the results are used to add up binary bits
|
||||
// for an integer index.
|
||||
int c1 = (x0 > y0) ? 32 : 0;
|
||||
int c2 = (x0 > z0) ? 16 : 0;
|
||||
int c3 = (y0 > z0) ? 8 : 0;
|
||||
int c4 = (x0 > w0) ? 4 : 0;
|
||||
int c5 = (y0 > w0) ? 2 : 0;
|
||||
int c6 = (z0 > w0) ? 1 : 0;
|
||||
int c = c1 + c2 + c3 + c4 + c5 + c6;
|
||||
|
||||
int i1, j1, k1, l1; // The integer offsets for the second simplex corner
|
||||
int i2, j2, k2, l2; // The integer offsets for the third simplex corner
|
||||
int i3, j3, k3, l3; // The integer offsets for the fourth simplex corner
|
||||
|
||||
// simplex[c] is a 4-vector with the numbers 0, 1, 2 and 3 in some order.
|
||||
// Many values of c will never occur, since e.g. x>y>z>w makes x<z, y<w and x<w
|
||||
// impossible. Only the 24 indices which have non-zero entries make any sense.
|
||||
// We use a thresholding to set the coordinates in turn from the largest magnitude.
|
||||
// The number 3 in the "simplex" array is at the position of the largest coordinate.
|
||||
i1 = simplex[c][0]>=3 ? 1 : 0;
|
||||
j1 = simplex[c][1]>=3 ? 1 : 0;
|
||||
k1 = simplex[c][2]>=3 ? 1 : 0;
|
||||
l1 = simplex[c][3]>=3 ? 1 : 0;
|
||||
// The number 2 in the "simplex" array is at the second largest coordinate.
|
||||
i2 = simplex[c][0]>=2 ? 1 : 0;
|
||||
j2 = simplex[c][1]>=2 ? 1 : 0;
|
||||
k2 = simplex[c][2]>=2 ? 1 : 0;
|
||||
l2 = simplex[c][3]>=2 ? 1 : 0;
|
||||
// The number 1 in the "simplex" array is at the second smallest coordinate.
|
||||
i3 = simplex[c][0]>=1 ? 1 : 0;
|
||||
j3 = simplex[c][1]>=1 ? 1 : 0;
|
||||
k3 = simplex[c][2]>=1 ? 1 : 0;
|
||||
l3 = simplex[c][3]>=1 ? 1 : 0;
|
||||
// The fifth corner has all coordinate offsets = 1, so no need to look that up.
|
||||
|
||||
float x1 = x0 - i1 + G4; // Offsets for second corner in (x,y,z,w) coords
|
||||
float y1 = y0 - j1 + G4;
|
||||
float z1 = z0 - k1 + G4;
|
||||
float w1 = w0 - l1 + G4;
|
||||
float x2 = x0 - i2 + 2.0f*G4; // Offsets for third corner in (x,y,z,w) coords
|
||||
float y2 = y0 - j2 + 2.0f*G4;
|
||||
float z2 = z0 - k2 + 2.0f*G4;
|
||||
float w2 = w0 - l2 + 2.0f*G4;
|
||||
float x3 = x0 - i3 + 3.0f*G4; // Offsets for fourth corner in (x,y,z,w) coords
|
||||
float y3 = y0 - j3 + 3.0f*G4;
|
||||
float z3 = z0 - k3 + 3.0f*G4;
|
||||
float w3 = w0 - l3 + 3.0f*G4;
|
||||
float x4 = x0 - 1.0f + 4.0f*G4; // Offsets for last corner in (x,y,z,w) coords
|
||||
float y4 = y0 - 1.0f + 4.0f*G4;
|
||||
float z4 = z0 - 1.0f + 4.0f*G4;
|
||||
float w4 = w0 - 1.0f + 4.0f*G4;
|
||||
|
||||
// Wrap the integer indices at 256, to avoid indexing perm[] out of bounds
|
||||
int ii = i & 0xff;
|
||||
int jj = j & 0xff;
|
||||
int kk = k & 0xff;
|
||||
int ll = l & 0xff;
|
||||
|
||||
// Calculate the contribution from the five corners
|
||||
float t0 = 0.5f - x0*x0 - y0*y0 - z0*z0 - w0*w0;
|
||||
if(t0 < 0.0f) n0 = 0.0f;
|
||||
else {
|
||||
t0 *= t0;
|
||||
n0 = t0 * t0 * grad4(perm[ii+perm[jj+perm[kk+perm[ll]]]], x0, y0, z0, w0);
|
||||
}
|
||||
|
||||
float t1 = 0.5f - x1*x1 - y1*y1 - z1*z1 - w1*w1;
|
||||
if(t1 < 0.0f) n1 = 0.0f;
|
||||
else {
|
||||
t1 *= t1;
|
||||
n1 = t1 * t1 * grad4(perm[ii+i1+perm[jj+j1+perm[kk+k1+perm[ll+l1]]]], x1, y1, z1, w1);
|
||||
}
|
||||
|
||||
float t2 = 0.5f - x2*x2 - y2*y2 - z2*z2 - w2*w2;
|
||||
if(t2 < 0.0f) n2 = 0.0f;
|
||||
else {
|
||||
t2 *= t2;
|
||||
n2 = t2 * t2 * grad4(perm[ii+i2+perm[jj+j2+perm[kk+k2+perm[ll+l2]]]], x2, y2, z2, w2);
|
||||
}
|
||||
|
||||
float t3 = 0.5f - x3*x3 - y3*y3 - z3*z3 - w3*w3;
|
||||
if(t3 < 0.0f) n3 = 0.0f;
|
||||
else {
|
||||
t3 *= t3;
|
||||
n3 = t3 * t3 * grad4(perm[ii+i3+perm[jj+j3+perm[kk+k3+perm[ll+l3]]]], x3, y3, z3, w3);
|
||||
}
|
||||
|
||||
float t4 = 0.5f - x4*x4 - y4*y4 - z4*z4 - w4*w4;
|
||||
if(t4 < 0.0f) n4 = 0.0f;
|
||||
else {
|
||||
t4 *= t4;
|
||||
n4 = t4 * t4 * grad4(perm[ii+1+perm[jj+1+perm[kk+1+perm[ll+1]]]], x4, y4, z4, w4);
|
||||
}
|
||||
|
||||
// Sum up and scale the result to cover the range [-1,1]
|
||||
return 62.0f * (n0 + n1 + n2 + n3 + n4);
|
||||
}
|
||||
|
||||
#undef F2
|
||||
#undef G2
|
||||
|
||||
#undef F3
|
||||
#undef G3
|
||||
|
||||
#undef F4
|
||||
#undef G4
|
||||
|
||||
#endif
|
||||
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -1,290 +1,290 @@
|
|||
// https://github.com/BareRose/swrap/blob/master/swrap.h
|
||||
|
||||
/*
|
||||
swrap - Portable, protocol-agnostic TCP and UDP socket wrapper, primarily designed for client-server models in applications such as games
|
||||
|
||||
To the extent possible under law, the author(s) have dedicated all copyright and related and neighboring
|
||||
rights to this software to the public domain worldwide. This software is distributed without any warranty.
|
||||
You should have received a copy of the CC0 Public Domain Dedication along with this software.
|
||||
If not, see <http://creativecommons.org/publicdomain/zero/1.0/>.
|
||||
*/
|
||||
|
||||
/*
|
||||
swrap supports the following three configurations:
|
||||
#define SWRAP_EXTERN
|
||||
Default, should be used when using swrap in multiple compilation units within the same project.
|
||||
#define SWRAP_IMPLEMENTATION
|
||||
Must be defined in exactly one source file within a project for swrap to be found by the linker.
|
||||
#define SWRAP_STATIC
|
||||
Defines all swrap functions as static, useful if swrap is only used in a single compilation unit.
|
||||
*/
|
||||
|
||||
//include only once
|
||||
#ifndef SWRAP_H
|
||||
#define SWRAP_H
|
||||
|
||||
//process configuration
|
||||
#ifdef SWRAP_STATIC
|
||||
#define SWRAP_IMPLEMENTATION
|
||||
#define SWDEF static
|
||||
#else //SWRAP_EXTERN
|
||||
#define SWDEF extern
|
||||
#endif
|
||||
|
||||
//constants
|
||||
#define SWRAP_TCP 0
|
||||
#define SWRAP_UDP 1
|
||||
#define SWRAP_BIND 0
|
||||
#define SWRAP_CONNECT 1
|
||||
#define SWRAP_DEFAULT 0x00
|
||||
#define SWRAP_NOBLOCK 0x01
|
||||
#define SWRAP_NODELAY 0x02
|
||||
|
||||
//structs
|
||||
struct swrap_addr {
|
||||
char data[128]; //enough space to hold any kind of address
|
||||
};
|
||||
|
||||
//function declarations
|
||||
SWDEF int swrapInit();
|
||||
SWDEF int swrapSocket(int, int, char, const char*, const char*);
|
||||
SWDEF void swrapClose(int);
|
||||
SWDEF void swrapTerminate();
|
||||
SWDEF int swrapListen(int, int);
|
||||
SWDEF int swrapAccept(int, struct swrap_addr*);
|
||||
SWDEF int swrapAddress(int, struct swrap_addr*);
|
||||
SWDEF int swrapAddressInfo(struct swrap_addr*, char*, size_t, char*, size_t);
|
||||
SWDEF int swrapSend(int, const char*, size_t);
|
||||
SWDEF int swrapReceive(int, char*, size_t);
|
||||
SWDEF int swrapSendTo(int, struct swrap_addr*, const char*, size_t);
|
||||
SWDEF int swrapReceiveFrom(int, struct swrap_addr*, char*, size_t);
|
||||
SWDEF int swrapSelect(int, double);
|
||||
SWDEF int swrapMultiSelect(int*, size_t, double);
|
||||
|
||||
//implementation section
|
||||
#ifdef SWRAP_IMPLEMENTATION
|
||||
|
||||
//includes
|
||||
#ifdef _WIN32 //windows
|
||||
#include <ws2tcpip.h>
|
||||
#else //unix
|
||||
#include <sys/socket.h>
|
||||
#include <netdb.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include <netinet/tcp.h>
|
||||
#endif
|
||||
#include <stddef.h> //NULL
|
||||
#include <limits.h> //INT_MAX on emscripten //< @r-lyeh: added
|
||||
|
||||
//general functions
|
||||
SWDEF int swrapInit () {
|
||||
//initializes socket functionality, returns 0 on success
|
||||
#ifdef _WIN32
|
||||
WSADATA WsaData;
|
||||
return (WSAStartup(MAKEWORD(2,2), &WsaData) != NO_ERROR);
|
||||
#else
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
SWDEF int swrapSocket (int prot, int mode, char flags, const char* host, const char* serv) {
|
||||
//protocol-agnostically creates a new socket configured according to the given parameters
|
||||
//sockets have to be created and bound/connected all at once to allow for protocol-agnosticity
|
||||
//int: Protocol of the socket, either SWRAP_TCP or SWRAP_UDP for TCP or UDP respectively
|
||||
// SWRAP_TCP: TCP protocol connection-oriented reliable delivery, see swrapListen/Accept
|
||||
// SWRAP_UDP: UDP protocol connectionless unreliable, SWRAP_CONNECT just assigns correspondent
|
||||
//int: Mode of the socket
|
||||
// SWRAP_BIND: Bind to given address (or all interfaces if NULL) and port, e.g. for a server
|
||||
// SWRAP_CONNECT: Connect to given address (localhost if NULL) and port, e.g. for a client
|
||||
//char: Configuration flags, either SWRAP_DEFAULT or a bitwise combination of flags
|
||||
// SWRAP_NOBLOCK: Sets the socket to be non-blocking, default is blocking
|
||||
// SWRAP_NODELAY: Disables Nagle's for TCP sockets, default is enabled
|
||||
//char*: Host/address as a string, can be IPv4, IPv6, etc...
|
||||
//char*: Service/port as a string, e.g. "1728" or "http"
|
||||
//returns socket handle, or -1 on failure
|
||||
struct addrinfo* result, hint = {
|
||||
(mode == SWRAP_BIND) ? AI_PASSIVE : 0, //ai_flags
|
||||
AF_UNSPEC, //ai_family
|
||||
(prot == SWRAP_TCP) ? SOCK_STREAM : SOCK_DGRAM, //ai_socktype
|
||||
0, 0, NULL, NULL, NULL};
|
||||
//get address info
|
||||
if (getaddrinfo(host, serv, &hint, &result)) return -1;
|
||||
//create socket
|
||||
#ifdef _WIN32
|
||||
SOCKET wsck = socket(result->ai_family, result->ai_socktype, result->ai_protocol);
|
||||
if (wsck == INVALID_SOCKET) return -1;
|
||||
//reject socket handle outside int range
|
||||
if (wsck > INT_MAX) {
|
||||
closesocket(wsck);
|
||||
return -1;
|
||||
}
|
||||
//convert to int
|
||||
int sock = wsck;
|
||||
#else
|
||||
int sock = socket(result->ai_family, result->ai_socktype, result->ai_protocol);
|
||||
if (sock == -1) return -1;
|
||||
#endif
|
||||
//make sure IPV6_ONLY is disabled
|
||||
if (result->ai_family == AF_INET6) {
|
||||
int no = 0;
|
||||
setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, (void*)&no, sizeof(no));
|
||||
}
|
||||
//set TCP_NODELAY if applicable
|
||||
if (prot == SWRAP_TCP) {
|
||||
int nodelay = (flags&SWRAP_NODELAY);
|
||||
setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, (void*)&nodelay, sizeof(nodelay));
|
||||
}
|
||||
//bind if applicable
|
||||
if ((mode == SWRAP_BIND)&&(bind(sock, result->ai_addr, result->ai_addrlen))) {
|
||||
swrapClose(sock);
|
||||
return -1;
|
||||
}
|
||||
//set non-blocking if needed
|
||||
if (flags&SWRAP_NOBLOCK) {
|
||||
#ifdef _WIN32
|
||||
DWORD no_block = 1;
|
||||
if (ioctlsocket(sock, FIONBIO, &no_block)) {
|
||||
swrapClose(sock);
|
||||
return -1;
|
||||
}
|
||||
#else
|
||||
if (fcntl(sock, F_SETFL, O_NONBLOCK, 1) == -1) {
|
||||
swrapClose(sock);
|
||||
return -1;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
//connect if applicable (return only relevant if blocking)
|
||||
if ((mode == SWRAP_CONNECT)&&(connect(sock, result->ai_addr, result->ai_addrlen))&&(!(flags&SWRAP_NOBLOCK))) {
|
||||
swrapClose(sock);
|
||||
return -1;
|
||||
}
|
||||
//free address info
|
||||
freeaddrinfo(result);
|
||||
//return socket handle
|
||||
return sock;
|
||||
}
|
||||
SWDEF void swrapClose (int sock) {
|
||||
//closes the given socket
|
||||
#ifdef _WIN32
|
||||
closesocket(sock);
|
||||
#else
|
||||
close(sock);
|
||||
#endif
|
||||
}
|
||||
SWDEF void swrapTerminate () {
|
||||
//terminates socket functionality
|
||||
#ifdef _WIN32
|
||||
WSACleanup();
|
||||
#endif
|
||||
}
|
||||
|
||||
//connection functions
|
||||
SWDEF int swrapListen (int sock, int blog) {
|
||||
//configures the given socket (must be SWRAP_TCP + SWRAP_BIND) to listen for new connections with given maximum backlog
|
||||
//returns 0 on success, non-zero on failure
|
||||
return listen(sock, blog);
|
||||
}
|
||||
SWDEF int swrapAccept (int sock, struct swrap_addr* addr) {
|
||||
//uses the given socket (must be swrapListen) to accept a new incoming connection, optionally returning its address
|
||||
//returns a socket handle for the new connection, or -1 on failure (e.g. if there are no new connections)
|
||||
#ifdef _WIN32
|
||||
int addr_size = sizeof(struct swrap_addr);
|
||||
SOCKET wsck = accept(sock, (struct sockaddr*)addr, (addr) ? &addr_size : NULL);
|
||||
if (wsck == INVALID_SOCKET) return -1;
|
||||
//reject socket handle outside int range
|
||||
if (wsck > INT_MAX) {
|
||||
closesocket(wsck);
|
||||
return -1;
|
||||
}
|
||||
//return new socket
|
||||
return wsck;
|
||||
#else
|
||||
socklen_t addr_size = sizeof(struct swrap_addr);
|
||||
return accept(sock, (struct sockaddr*)addr, (addr) ? &addr_size : NULL);
|
||||
#endif
|
||||
}
|
||||
|
||||
//address functions
|
||||
SWDEF int swrapAddress (int sock, struct swrap_addr* addr) {
|
||||
//writes the address the given socket is bound to into given address pointer, useful when automatically assigning port
|
||||
//returns 0 on success, non-zero on failure
|
||||
#ifdef _WIN32
|
||||
int addr_size = sizeof(struct swrap_addr);
|
||||
#else
|
||||
socklen_t addr_size = sizeof(struct swrap_addr);
|
||||
#endif
|
||||
return getsockname(sock, (struct sockaddr*)addr, &addr_size);
|
||||
}
|
||||
SWDEF int swrapAddressInfo (struct swrap_addr* addr, char* host, size_t host_size, char* serv, size_t serv_size) {
|
||||
//writes the host/address and service/port of given address into given buffers (pointer + size), either buffer may be NULL
|
||||
//returns 0 on success, non-zero on failure
|
||||
return getnameinfo((struct sockaddr*)addr, sizeof(struct swrap_addr), host, host_size, serv, serv_size, 0);
|
||||
}
|
||||
|
||||
//send/receive functions
|
||||
SWDEF int swrapSend (int sock, const char* data, size_t data_size) {
|
||||
//uses the given socket (either SWRAP_CONNECT or returned by swrapAccept) to send given data (pointer + size)
|
||||
//at most INT_MAX bytes of data will be sent, data sizes greater than that are clamped to INT_MAX
|
||||
//returns how much data was actually sent (may be less than data size), or -1 on failure
|
||||
return send(sock, data, (data_size > INT_MAX) ? INT_MAX : data_size, 0);
|
||||
}
|
||||
SWDEF int swrapReceive (int sock, char* data, size_t data_size) {
|
||||
//receives data using given socket (either SWRAP_CONNECT or returned by swrapAccept) into given buffer (pointer + size)
|
||||
//at most INT_MAX bytes of data will be received, buffer sizes greater than INT_MAX have no additional benefit
|
||||
//returns the number of bytes received, 0 on orderly shutdown, or -1 on failure (e.g. no data to receive)
|
||||
return recv(sock, data, (data_size > INT_MAX) ? INT_MAX : data_size, 0);
|
||||
}
|
||||
SWDEF int swrapSendTo (int sock, struct swrap_addr* addr, const char* data, size_t data_size) {
|
||||
//uses the given socket to send given data (pointer + size) to the given swrap_addr (e.g. from swrapReceiveFrom)
|
||||
//at most INT_MAX bytes of data will be sent, data sizes greater than that are clamped to INT_MAX
|
||||
//returns how much data was actually sent (may be less than data size), or -1 on failure
|
||||
return sendto(sock, data, (data_size > INT_MAX) ? INT_MAX : data_size, 0, (struct sockaddr*)addr, sizeof(struct swrap_addr));
|
||||
}
|
||||
SWDEF int swrapReceiveFrom (int sock, struct swrap_addr* addr, char* data, size_t data_size) {
|
||||
//receives data using given socket into given buffer (pointer + size), optionally returning sender's address
|
||||
//at most INT_MAX bytes of data will be received, buffer sizes greater than INT_MAX have no additional benefit
|
||||
//returns the number of bytes received, 0 on orderly shutdown, or -1 on failure (e.g. no data to receive)
|
||||
#ifdef _WIN32
|
||||
int addr_size = sizeof(struct swrap_addr);
|
||||
#else
|
||||
socklen_t addr_size = sizeof(struct swrap_addr);
|
||||
#endif
|
||||
return recvfrom(sock, data, (data_size > INT_MAX) ? INT_MAX : data_size, 0, (struct sockaddr*)addr, &addr_size);
|
||||
}
|
||||
|
||||
//select functions
|
||||
SWDEF int swrapSelect (int sock, double timeout) {
|
||||
//waits either until given socket has new data to receive or given time (in seconds) has passed
|
||||
//if given socket is -1 an empty select will be performed, which is just a sub-second sleep
|
||||
//returns 1 if new data is available, 0 if timeout was reached, and -1 on error
|
||||
fd_set set; struct timeval time;
|
||||
//fd set
|
||||
FD_ZERO(&set);
|
||||
if (sock > -1) FD_SET(sock, &set);
|
||||
//timeout
|
||||
time.tv_sec = timeout;
|
||||
time.tv_usec = (timeout - time.tv_sec)*1000000.0;
|
||||
//return
|
||||
return select(sock+1, &set, NULL, NULL, &time);
|
||||
}
|
||||
SWDEF int swrapMultiSelect (int* socks, size_t socks_size, double timeout) {
|
||||
//waits either until a socket in given list has new data to receive or given time (in seconds) has passed
|
||||
//if the given list length is 0 an empty select will be performed, which is just a sub-second sleep
|
||||
//returns 1 or more if new data is available, 0 if timeout was reached, and -1 on error
|
||||
fd_set set; struct timeval time; int sock_max = -1;
|
||||
//fd set
|
||||
FD_ZERO(&set);
|
||||
for (size_t i = 0; i < socks_size; i++) {
|
||||
if (socks[i] > sock_max) sock_max = socks[i];
|
||||
if (socks[i] > -1) FD_SET(socks[i], &set);
|
||||
}
|
||||
//timeout
|
||||
time.tv_sec = timeout;
|
||||
time.tv_usec = (timeout - time.tv_sec)*1000000.0;
|
||||
//return
|
||||
return select(sock_max+1, &set, NULL, NULL, &time);
|
||||
}
|
||||
|
||||
#endif //SWRAP_IMPLEMENTATION
|
||||
#endif //SWRAP_H
|
||||
// https://github.com/BareRose/swrap/blob/master/swrap.h
|
||||
|
||||
/*
|
||||
swrap - Portable, protocol-agnostic TCP and UDP socket wrapper, primarily designed for client-server models in applications such as games
|
||||
|
||||
To the extent possible under law, the author(s) have dedicated all copyright and related and neighboring
|
||||
rights to this software to the public domain worldwide. This software is distributed without any warranty.
|
||||
You should have received a copy of the CC0 Public Domain Dedication along with this software.
|
||||
If not, see <http://creativecommons.org/publicdomain/zero/1.0/>.
|
||||
*/
|
||||
|
||||
/*
|
||||
swrap supports the following three configurations:
|
||||
#define SWRAP_EXTERN
|
||||
Default, should be used when using swrap in multiple compilation units within the same project.
|
||||
#define SWRAP_IMPLEMENTATION
|
||||
Must be defined in exactly one source file within a project for swrap to be found by the linker.
|
||||
#define SWRAP_STATIC
|
||||
Defines all swrap functions as static, useful if swrap is only used in a single compilation unit.
|
||||
*/
|
||||
|
||||
//include only once
|
||||
#ifndef SWRAP_H
|
||||
#define SWRAP_H
|
||||
|
||||
//process configuration
|
||||
#ifdef SWRAP_STATIC
|
||||
#define SWRAP_IMPLEMENTATION
|
||||
#define SWDEF static
|
||||
#else //SWRAP_EXTERN
|
||||
#define SWDEF extern
|
||||
#endif
|
||||
|
||||
//constants
|
||||
#define SWRAP_TCP 0
|
||||
#define SWRAP_UDP 1
|
||||
#define SWRAP_BIND 0
|
||||
#define SWRAP_CONNECT 1
|
||||
#define SWRAP_DEFAULT 0x00
|
||||
#define SWRAP_NOBLOCK 0x01
|
||||
#define SWRAP_NODELAY 0x02
|
||||
|
||||
//structs
|
||||
struct swrap_addr {
|
||||
char data[128]; //enough space to hold any kind of address
|
||||
};
|
||||
|
||||
//function declarations
|
||||
SWDEF int swrapInit();
|
||||
SWDEF int swrapSocket(int, int, char, const char*, const char*);
|
||||
SWDEF void swrapClose(int);
|
||||
SWDEF void swrapTerminate();
|
||||
SWDEF int swrapListen(int, int);
|
||||
SWDEF int swrapAccept(int, struct swrap_addr*);
|
||||
SWDEF int swrapAddress(int, struct swrap_addr*);
|
||||
SWDEF int swrapAddressInfo(struct swrap_addr*, char*, size_t, char*, size_t);
|
||||
SWDEF int swrapSend(int, const char*, size_t);
|
||||
SWDEF int swrapReceive(int, char*, size_t);
|
||||
SWDEF int swrapSendTo(int, struct swrap_addr*, const char*, size_t);
|
||||
SWDEF int swrapReceiveFrom(int, struct swrap_addr*, char*, size_t);
|
||||
SWDEF int swrapSelect(int, double);
|
||||
SWDEF int swrapMultiSelect(int*, size_t, double);
|
||||
|
||||
//implementation section
|
||||
#ifdef SWRAP_IMPLEMENTATION
|
||||
|
||||
//includes
|
||||
#ifdef _WIN32 //windows
|
||||
#include <ws2tcpip.h>
|
||||
#else //unix
|
||||
#include <sys/socket.h>
|
||||
#include <netdb.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include <netinet/tcp.h>
|
||||
#endif
|
||||
#include <stddef.h> //NULL
|
||||
#include <limits.h> //INT_MAX on emscripten //< @r-lyeh: added
|
||||
|
||||
//general functions
|
||||
SWDEF int swrapInit () {
|
||||
//initializes socket functionality, returns 0 on success
|
||||
#ifdef _WIN32
|
||||
WSADATA WsaData;
|
||||
return (WSAStartup(MAKEWORD(2,2), &WsaData) != NO_ERROR);
|
||||
#else
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
SWDEF int swrapSocket (int prot, int mode, char flags, const char* host, const char* serv) {
|
||||
//protocol-agnostically creates a new socket configured according to the given parameters
|
||||
//sockets have to be created and bound/connected all at once to allow for protocol-agnosticity
|
||||
//int: Protocol of the socket, either SWRAP_TCP or SWRAP_UDP for TCP or UDP respectively
|
||||
// SWRAP_TCP: TCP protocol connection-oriented reliable delivery, see swrapListen/Accept
|
||||
// SWRAP_UDP: UDP protocol connectionless unreliable, SWRAP_CONNECT just assigns correspondent
|
||||
//int: Mode of the socket
|
||||
// SWRAP_BIND: Bind to given address (or all interfaces if NULL) and port, e.g. for a server
|
||||
// SWRAP_CONNECT: Connect to given address (localhost if NULL) and port, e.g. for a client
|
||||
//char: Configuration flags, either SWRAP_DEFAULT or a bitwise combination of flags
|
||||
// SWRAP_NOBLOCK: Sets the socket to be non-blocking, default is blocking
|
||||
// SWRAP_NODELAY: Disables Nagle's for TCP sockets, default is enabled
|
||||
//char*: Host/address as a string, can be IPv4, IPv6, etc...
|
||||
//char*: Service/port as a string, e.g. "1728" or "http"
|
||||
//returns socket handle, or -1 on failure
|
||||
struct addrinfo* result, hint = {
|
||||
(mode == SWRAP_BIND) ? AI_PASSIVE : 0, //ai_flags
|
||||
AF_UNSPEC, //ai_family
|
||||
(prot == SWRAP_TCP) ? SOCK_STREAM : SOCK_DGRAM, //ai_socktype
|
||||
0, 0, NULL, NULL, NULL};
|
||||
//get address info
|
||||
if (getaddrinfo(host, serv, &hint, &result)) return -1;
|
||||
//create socket
|
||||
#ifdef _WIN32
|
||||
SOCKET wsck = socket(result->ai_family, result->ai_socktype, result->ai_protocol);
|
||||
if (wsck == INVALID_SOCKET) return -1;
|
||||
//reject socket handle outside int range
|
||||
if (wsck > INT_MAX) {
|
||||
closesocket(wsck);
|
||||
return -1;
|
||||
}
|
||||
//convert to int
|
||||
int sock = wsck;
|
||||
#else
|
||||
int sock = socket(result->ai_family, result->ai_socktype, result->ai_protocol);
|
||||
if (sock == -1) return -1;
|
||||
#endif
|
||||
//make sure IPV6_ONLY is disabled
|
||||
if (result->ai_family == AF_INET6) {
|
||||
int no = 0;
|
||||
setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, (void*)&no, sizeof(no));
|
||||
}
|
||||
//set TCP_NODELAY if applicable
|
||||
if (prot == SWRAP_TCP) {
|
||||
int nodelay = (flags&SWRAP_NODELAY);
|
||||
setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, (void*)&nodelay, sizeof(nodelay));
|
||||
}
|
||||
//bind if applicable
|
||||
if ((mode == SWRAP_BIND)&&(bind(sock, result->ai_addr, result->ai_addrlen))) {
|
||||
swrapClose(sock);
|
||||
return -1;
|
||||
}
|
||||
//set non-blocking if needed
|
||||
if (flags&SWRAP_NOBLOCK) {
|
||||
#ifdef _WIN32
|
||||
DWORD no_block = 1;
|
||||
if (ioctlsocket(sock, FIONBIO, &no_block)) {
|
||||
swrapClose(sock);
|
||||
return -1;
|
||||
}
|
||||
#else
|
||||
if (fcntl(sock, F_SETFL, O_NONBLOCK, 1) == -1) {
|
||||
swrapClose(sock);
|
||||
return -1;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
//connect if applicable (return only relevant if blocking)
|
||||
if ((mode == SWRAP_CONNECT)&&(connect(sock, result->ai_addr, result->ai_addrlen))&&(!(flags&SWRAP_NOBLOCK))) {
|
||||
swrapClose(sock);
|
||||
return -1;
|
||||
}
|
||||
//free address info
|
||||
freeaddrinfo(result);
|
||||
//return socket handle
|
||||
return sock;
|
||||
}
|
||||
SWDEF void swrapClose (int sock) {
|
||||
//closes the given socket
|
||||
#ifdef _WIN32
|
||||
closesocket(sock);
|
||||
#else
|
||||
close(sock);
|
||||
#endif
|
||||
}
|
||||
SWDEF void swrapTerminate () {
|
||||
//terminates socket functionality
|
||||
#ifdef _WIN32
|
||||
WSACleanup();
|
||||
#endif
|
||||
}
|
||||
|
||||
//connection functions
|
||||
SWDEF int swrapListen (int sock, int blog) {
|
||||
//configures the given socket (must be SWRAP_TCP + SWRAP_BIND) to listen for new connections with given maximum backlog
|
||||
//returns 0 on success, non-zero on failure
|
||||
return listen(sock, blog);
|
||||
}
|
||||
SWDEF int swrapAccept (int sock, struct swrap_addr* addr) {
|
||||
//uses the given socket (must be swrapListen) to accept a new incoming connection, optionally returning its address
|
||||
//returns a socket handle for the new connection, or -1 on failure (e.g. if there are no new connections)
|
||||
#ifdef _WIN32
|
||||
int addr_size = sizeof(struct swrap_addr);
|
||||
SOCKET wsck = accept(sock, (struct sockaddr*)addr, (addr) ? &addr_size : NULL);
|
||||
if (wsck == INVALID_SOCKET) return -1;
|
||||
//reject socket handle outside int range
|
||||
if (wsck > INT_MAX) {
|
||||
closesocket(wsck);
|
||||
return -1;
|
||||
}
|
||||
//return new socket
|
||||
return wsck;
|
||||
#else
|
||||
socklen_t addr_size = sizeof(struct swrap_addr);
|
||||
return accept(sock, (struct sockaddr*)addr, (addr) ? &addr_size : NULL);
|
||||
#endif
|
||||
}
|
||||
|
||||
//address functions
|
||||
SWDEF int swrapAddress (int sock, struct swrap_addr* addr) {
|
||||
//writes the address the given socket is bound to into given address pointer, useful when automatically assigning port
|
||||
//returns 0 on success, non-zero on failure
|
||||
#ifdef _WIN32
|
||||
int addr_size = sizeof(struct swrap_addr);
|
||||
#else
|
||||
socklen_t addr_size = sizeof(struct swrap_addr);
|
||||
#endif
|
||||
return getsockname(sock, (struct sockaddr*)addr, &addr_size);
|
||||
}
|
||||
SWDEF int swrapAddressInfo (struct swrap_addr* addr, char* host, size_t host_size, char* serv, size_t serv_size) {
|
||||
//writes the host/address and service/port of given address into given buffers (pointer + size), either buffer may be NULL
|
||||
//returns 0 on success, non-zero on failure
|
||||
return getnameinfo((struct sockaddr*)addr, sizeof(struct swrap_addr), host, host_size, serv, serv_size, 0);
|
||||
}
|
||||
|
||||
//send/receive functions
|
||||
SWDEF int swrapSend (int sock, const char* data, size_t data_size) {
|
||||
//uses the given socket (either SWRAP_CONNECT or returned by swrapAccept) to send given data (pointer + size)
|
||||
//at most INT_MAX bytes of data will be sent, data sizes greater than that are clamped to INT_MAX
|
||||
//returns how much data was actually sent (may be less than data size), or -1 on failure
|
||||
return send(sock, data, (data_size > INT_MAX) ? INT_MAX : data_size, 0);
|
||||
}
|
||||
SWDEF int swrapReceive (int sock, char* data, size_t data_size) {
|
||||
//receives data using given socket (either SWRAP_CONNECT or returned by swrapAccept) into given buffer (pointer + size)
|
||||
//at most INT_MAX bytes of data will be received, buffer sizes greater than INT_MAX have no additional benefit
|
||||
//returns the number of bytes received, 0 on orderly shutdown, or -1 on failure (e.g. no data to receive)
|
||||
return recv(sock, data, (data_size > INT_MAX) ? INT_MAX : data_size, 0);
|
||||
}
|
||||
SWDEF int swrapSendTo (int sock, struct swrap_addr* addr, const char* data, size_t data_size) {
|
||||
//uses the given socket to send given data (pointer + size) to the given swrap_addr (e.g. from swrapReceiveFrom)
|
||||
//at most INT_MAX bytes of data will be sent, data sizes greater than that are clamped to INT_MAX
|
||||
//returns how much data was actually sent (may be less than data size), or -1 on failure
|
||||
return sendto(sock, data, (data_size > INT_MAX) ? INT_MAX : data_size, 0, (struct sockaddr*)addr, sizeof(struct swrap_addr));
|
||||
}
|
||||
SWDEF int swrapReceiveFrom (int sock, struct swrap_addr* addr, char* data, size_t data_size) {
|
||||
//receives data using given socket into given buffer (pointer + size), optionally returning sender's address
|
||||
//at most INT_MAX bytes of data will be received, buffer sizes greater than INT_MAX have no additional benefit
|
||||
//returns the number of bytes received, 0 on orderly shutdown, or -1 on failure (e.g. no data to receive)
|
||||
#ifdef _WIN32
|
||||
int addr_size = sizeof(struct swrap_addr);
|
||||
#else
|
||||
socklen_t addr_size = sizeof(struct swrap_addr);
|
||||
#endif
|
||||
return recvfrom(sock, data, (data_size > INT_MAX) ? INT_MAX : data_size, 0, (struct sockaddr*)addr, &addr_size);
|
||||
}
|
||||
|
||||
//select functions
|
||||
SWDEF int swrapSelect (int sock, double timeout) {
|
||||
//waits either until given socket has new data to receive or given time (in seconds) has passed
|
||||
//if given socket is -1 an empty select will be performed, which is just a sub-second sleep
|
||||
//returns 1 if new data is available, 0 if timeout was reached, and -1 on error
|
||||
fd_set set; struct timeval time;
|
||||
//fd set
|
||||
FD_ZERO(&set);
|
||||
if (sock > -1) FD_SET(sock, &set);
|
||||
//timeout
|
||||
time.tv_sec = timeout;
|
||||
time.tv_usec = (timeout - time.tv_sec)*1000000.0;
|
||||
//return
|
||||
return select(sock+1, &set, NULL, NULL, &time);
|
||||
}
|
||||
SWDEF int swrapMultiSelect (int* socks, size_t socks_size, double timeout) {
|
||||
//waits either until a socket in given list has new data to receive or given time (in seconds) has passed
|
||||
//if the given list length is 0 an empty select will be performed, which is just a sub-second sleep
|
||||
//returns 1 or more if new data is available, 0 if timeout was reached, and -1 on error
|
||||
fd_set set; struct timeval time; int sock_max = -1;
|
||||
//fd set
|
||||
FD_ZERO(&set);
|
||||
for (size_t i = 0; i < socks_size; i++) {
|
||||
if (socks[i] > sock_max) sock_max = socks[i];
|
||||
if (socks[i] > -1) FD_SET(socks[i], &set);
|
||||
}
|
||||
//timeout
|
||||
time.tv_sec = timeout;
|
||||
time.tv_usec = (timeout - time.tv_sec)*1000000.0;
|
||||
//return
|
||||
return select(sock_max+1, &set, NULL, NULL, &time);
|
||||
}
|
||||
|
||||
#endif //SWRAP_IMPLEMENTATION
|
||||
#endif //SWRAP_H
|
||||
|
|
16116
engine/split/3rd_tfd.h
16116
engine/split/3rd_tfd.h
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -1,82 +1,82 @@
|
|||
// 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,
|
||||
SWARM_DISTANCE_QUADRATIC,
|
||||
SWARM_DISTANCE_INVERSE_QUADRATIC
|
||||
} SWARM_DISTANCE;
|
||||
|
||||
#define boid(...) C_CAST(boid_t, __VA_ARGS__)
|
||||
|
||||
typedef struct boid_t {
|
||||
vec3 position;
|
||||
vec3 velocity;
|
||||
vec3 acceleration;
|
||||
vec3 prev_position;
|
||||
} boid_t;
|
||||
|
||||
typedef struct swarm_t {
|
||||
array(boid_t) boids;
|
||||
|
||||
float perception_radius; // determines the vision radius of each boid. Only boids within this distance influence each other.
|
||||
|
||||
float separation_weight; // how much boids repel each other
|
||||
SWARM_DISTANCE separation_type;
|
||||
|
||||
float alignment_weight; // how much boids want go in the same direction
|
||||
float cohesion_weight; // how much boids want to be in the center of the swarm
|
||||
|
||||
float steering_weight;
|
||||
array(vec3) steering_targets;
|
||||
SWARM_DISTANCE steering_target_type;
|
||||
|
||||
float blindspot_angledeg;
|
||||
float max_acceleration; // how fast each boid can change its direction
|
||||
float max_velocity; // how fast each boid can move
|
||||
|
||||
// private:
|
||||
map(vec3*, array(boid_t*)) voxel_cache_;
|
||||
float blindspot_angledeg_compare_value_;
|
||||
} swarm_t;
|
||||
|
||||
API swarm_t swarm();
|
||||
API void swarm_update(swarm_t *self, float delta); // acc,vel,pos
|
||||
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);
|
||||
// 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,
|
||||
SWARM_DISTANCE_QUADRATIC,
|
||||
SWARM_DISTANCE_INVERSE_QUADRATIC
|
||||
} SWARM_DISTANCE;
|
||||
|
||||
#define boid(...) C_CAST(boid_t, __VA_ARGS__)
|
||||
|
||||
typedef struct boid_t {
|
||||
vec3 position;
|
||||
vec3 velocity;
|
||||
vec3 acceleration;
|
||||
vec3 prev_position;
|
||||
} boid_t;
|
||||
|
||||
typedef struct swarm_t {
|
||||
array(boid_t) boids;
|
||||
|
||||
float perception_radius; // determines the vision radius of each boid. Only boids within this distance influence each other.
|
||||
|
||||
float separation_weight; // how much boids repel each other
|
||||
SWARM_DISTANCE separation_type;
|
||||
|
||||
float alignment_weight; // how much boids want go in the same direction
|
||||
float cohesion_weight; // how much boids want to be in the center of the swarm
|
||||
|
||||
float steering_weight;
|
||||
array(vec3) steering_targets;
|
||||
SWARM_DISTANCE steering_target_type;
|
||||
|
||||
float blindspot_angledeg;
|
||||
float max_acceleration; // how fast each boid can change its direction
|
||||
float max_velocity; // how fast each boid can move
|
||||
|
||||
// private:
|
||||
map(vec3*, array(boid_t*)) voxel_cache_;
|
||||
float blindspot_angledeg_compare_value_;
|
||||
} swarm_t;
|
||||
|
||||
API swarm_t swarm();
|
||||
API void swarm_update(swarm_t *self, float delta); // acc,vel,pos
|
||||
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);
|
||||
|
|
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue