# viewer **Repository Path**: wujilingfeng/viewer ## Basic Information - **Project Name**: viewer - **Description**: 可视化库 - **Primary Language**: C - **License**: MIT - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 3 - **Forks**: 2 - **Created**: 2021-04-14 - **Last Updated**: 2025-09-28 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README #### Viewer 可视化库,该库的目标是给一种用户可以抽象描述可视化事物而无视工程实现的工具库。比如一个数学专业的人员只需要学习c语言的成本就可以实现自己想要的的可视化任务,而无需耗费大量的成本在各种可视化工程框架中。该库目前也支持UI框架,它同样有上述的特点。 该库的框架分为抽象描述部分和工程实现部分,抽象描述部分建立在c语言上,目前工程解释器部分是基于opengl实现的。抽象描述部分只依赖c语言,不依赖任何工程部分。 ​ 欢迎基于其他工程框架写的解释器,这样可以方便的将此库移植到其他平台,比如WebGL。 #### Dedenpencies glfw:用于opengl的窗口系统。(CMAKE -DCMAKE_C_FLAGS=-fPIC -DGLFW_BUILD_WAYLAND=ON) glad: opengl的实现库。 freetype: 字体渲染库(2.10)(.configure --with-pic) cstructures(>=1.2.1): c语言的数据结构库 lbmath(>=1.2): 基础数学库 在debian系统你需要先安装 ```bash sudo apt install xorg-dev libglu1-mesa-dev build-essential libgl1-mesa-dev libharfbuzz-dev libbz2-dev ``` #### Compile ##### compile library only glfw cstructures freetype glad lbmath文件放在thirdpart中 for windows vs ```bash xmake f --configure=y --demo=n xmake project -k vs ``` for windows mingw ```bash xmake f -p mingw --configure=y --demo=n xmake ``` for windows cl ```bash xmake f --configure=y --demo=n xmake ``` for linux ```bash xmake f --configure=y --demo=n xmake ``` ##### complier library and demo 将freetype glfw cstructures编译好的lib库放到lib文件 针对各种平台只需将上面`--demo=n`替换为`--demo=y`,再加入`--demo_path=`选项。 for linux ```bash xmake f --configure=y --demo=y --demo_path="Demo" xmake ``` for windows cl (如果想要渲染中文,windows下默认编码是gbk,所以要把windows的系统编码调为UTF-8编码) ```bash xmake f --configure=y --demo=y --demo_path="Demo" xmake ``` ##### Install ```bash xmake install --admin ``` #### Tutorial * 设计标准一个世界对应一个窗口。 ##### 抽象层 * 库的概念 用户利用c语言描述“世界”,然后调用解释器解释。 每个世界对应一个窗口绘制,多个世界可以用世界管理器(Viewer_World_Manager)来管理。 一个世界有多个“物种”,每个物种有多个“个体”。 用户可以自己在"世界"注册新的”物种“,然后就可以创建该“物种”的个体了,当然你需要在解释器中增加对其的解释。比如你可以注册自己的”灯光物种“,然后在解释器中添加自己的解释代码。(正如上面介绍的,创建物种仅仅依赖c语言,而解释器部分则自己决定) 目前该库默认注册的”物种“有: * 物种 | 物种名 | 对应的结构体名 | 对应的事物 | | ------------ | ------------------- | ------------------------------- | | Points | Viewer_Points | 点集 | | Edges | Viewer_Edges | 线段集合 | | Faces | Viewer_Faces | 任意多边形集合 | | Intera | Viewer_Intera | 交互器(包含各种鼠标 ,键盘,拖拽,时间,拾取等回调函数 ) | | Camera | Viewer_Camera | 相机 | | Texture | Viewer_Texture | 纹理 | | UI_Mesh | Viewer_UI_Mesh | UI的网格 | | Cursor_Shape | Viewer_Cursor_Shape | 鼠标形状 | | Texts | Viewer_Texts | 文字 | 上面所有结构体在Viewer_World_Manager文件夹的头文件内,里面几乎所有变量望文生义。 * 世界管理器(Viewer_World_Manager) | 成员 | 含义 | | ----------------- | ----------------------- | | container | 储存所有的世界("Viewer_World") | | create_world | 创建并返回世界 | | get_world_from_id | 按索引查找并返回世界 | | get_world_num | 获取世界个数 | | prop | 储存用户变量 | * 世界(Viewer_World) 以下结构体Viewer_World的内部成员变量解释 | 成员 | 含义 | | ------------------------- | ------------------------------------------------------------------------------------------------------ | | name | 世界名字 | | id | 世界的id | | background_color | 世界的背景色 | | find_species | 查找物种,并按顺序返回当前物种的id。如果返回NULL,表示字符串不合适。id=-1表示没有此物种。例如find_speces(&vw,"Points,Faces")会返回链表,这个链表按顺序储存id变量 | | registe | 注册物种,如果返回NULL,表示字符串不合适。按顺序返回物种id | | create_something | 创建个体,按顺序返回物种 | | g_info | 储存了当前所有交互信息,比如鼠标和键盘状态,拖拽的文件路径,拾取信息 | | viewer_world_find_registe | 寻找并注册物种 | | 还有其他变量全部望文生义,你可以查看源码。 | | * 全局信息(Interactor_GlobalInfo) ```c typedef struct Interactor_GlobalInfo{ float run_time;//运行时间 float *mouse_coord;//鼠标在屏幕的坐标 int *resolution;//窗口的分辨率 int mouse_button;//鼠标按键 int mouse_action;//鼠标动作 int key;//键盘按键 int key_action;//键盘动作 int key_mods; int mouse_mods; unsigned char *readpixelcolor;//当前鼠标读取的屏幕颜色 Interactor_PickInfo ipi;//当前鼠标拾取的事物 float select_area[4]//当前框选拾取范围(以窗口左下角为原点,以1为最大xy值) Node* select_somethings;//储存框选的事物 float screenshot_area[4];//对非UI事物进行截图的区域,同select_area unsigned char* screenshot_image;//截图后的数据 int drop_count;//拖人窗口的文件个数 char** paths;//托入窗口每个文件的路径 void* user_states; //用户定义的状态 void *prop;//用户储存的变量 void* window; }Interactor_GlobalInfo; ``` 你随时可以修改或者访问上述所有变量。有些变量主要是用来访问的,比如前10个变量和select_somethings,ipi,有些主要是用来修改的,比如select_area,screeenshot_area..,有些是既可以修改又可以访问的,比如prop,user_states * 矩阵 此库使用odai_math中的矩阵,并利用宏实现了c++的模板功能,假如你要获得适配double类型的LB_Matrix,你需要在头文件加入LB_MATRIX_FUNC_DECLARE(double),那么就会展开相关的函数声明,然后再在源文件中加入一句LB_MATRIX_FUNC(double),那么就会展开相关函数的定义。之后对矩阵调用lb_matrix_init_double()初始化,就可以使用了。 | 内部函数 | 意义 | | ----------------------------------------------- | ----------------------------------------------------- | | void (*transpose)( LB_Matrix \*) | 对矩阵进行转置 | | LB_Matrix\*(mult)(LB_Matrix\*A,LB_Matrix\* B) | 返回AB,返回的结果不用时,调用lb_matrix_free进行内存销毁 | | LB_Matrix \*(\*inverse)(LB_Matrix \*); | 返回$A^-$,当A退化时,返回NULL,当结果矩阵不使用时,调用lb_matrix_free进行内存销毁 | | void (\*identity)(LB_Matrix \*); | 初始化当前矩阵为单位阵 | | void (\*zero)(LB_Matrix\*) | 初始化当前矩阵为0矩阵 | | void \*(\*det)(LB_Matrix \*); | 返回当前矩阵的行列式,值是void\*(根据实际情况转化其为相应的数值) | | void (\*copy_data)(LB_Matrix\*A, LB_Matrix\*B); | 将B的值赋给A | | void \* data | 矩阵的元素值,是一个储存16数值的数组 | | void (\*print_self)(LB_Matrix \*); | 打印矩阵数值 | ##### 解释器层 * 本库的默认解释器 本库的默认解释器是基于opengl,该解释器主要通过创建shader来运行。实际上该库也是创建了几个默认shader来对上述"世界"进行解释,以下代码展示如何添加自己的shader program ```c #define Viewer_oisp Viewer_Opengl_Interpreter_Shader_Program Viewer_Opengl_Interpreter voi;//创建解释器 Viewer_Opengl_Interpreter_initd(&voi,&vw);/*vw是传入的"世界",该初始化函数会默认创建几个shader program*/ Viewer_oisp *voisp=voi->create_shader_program(voi,pv,pf,load,render);/*插入创建了一个shader program,其中pv,pf分别是顶点着色器和片元着色器的路径,load是加载资源的函数指针,render是渲染函数的指针*/ ``` load函数举例 ```c static void load_data(Viewer_Opengl_Interpreter_Shader_Program*voisp) { if(voisp->program==0) { _Shader_(voisp->shaders); voisp->program=_Program_(voisp->shaders); } //glUseProgram(voisp->program); float vertices2[] = { -0.6f, -0.6f, 0.6f, 0.6f, -0.6f, 0.6f, -0.6f, 0.6f, 0.6f, 0.6f, 0.6f, 0.6f, -0.6f, -0.6f, 0.8f, 0.6f, -0.6f, 0.8f, -0.6f, 0.6f, 0.8f, 0.6f, 0.6f, 0.8f, }; unsigned int indices[] = { 0, 1, 2, 1, 2, 3, 4,5,6,5,6,7, 0,1,4,1,4,5, 2,3,6,3,6,7, 0,2,4,2,4,6, 1,3,4,3,4,7 }; voisp->VAO=(GLuint*)malloc(sizeof(GLuint)); voisp->Buffers=(GLuint**)malloc(sizeof(GLuint*)); voisp->Buffers[0]=(GLuint*)malloc(sizeof(GLuint)*2); GLuint*VAO=voisp->VAO; GLuint*VBO=voisp->Buffers[0]; glGenVertexArrays(1, VAO); glGenBuffers(2, VBO); glBindVertexArray(*VAO); glBindBuffer(GL_ARRAY_BUFFER, VBO[0]); glBufferData(GL_ARRAY_BUFFER, sizeof(vertices2), vertices2, GL_STATIC_DRAW); glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0); glEnableVertexAttribArray(0); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, VBO[1]); glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW); } ``` render函数举例 ```c static void render(Viewer_Opengl_Interpreter_Shader_Program* voisp) { //glViewport(0,0,500,500); glEnable(GL_DEPTH_TEST); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glUseProgram(voisp->program); // create transformations LB_Matrix model,temp_m; lb_matrix_init_float(&model,4,4);lb_matrix_init_float(&temp_m,4,4); float *data=(float*)(temp_m.data); float tempx=0.9*((float)glfwGetTime()),tempy=0.65*((float)glfwGetTime()); data[0*4+0]=cos(tempx);data[0*4+1]=-sin(tempx)*sin(tempy); data[0*4+2]=sin(tempx)*cos(tempy); data[1*4+1]=cos(tempy); data[1*4+2]=sin(tempy); data[2*4+0]=-sin(tempx); data[2*4+1]=-cos(tempx)*sin(tempy); data[2*4+2]=cos(tempx)*cos(tempy); model.mult(&model,&temp_m); ((float*)model.data)[2*4+3]=2.0; LB_Matrix *p=Projection(M_PI/3.0f,800.0/600.0,0.5f,200.0f); glUniformMatrix4fv(glGetUniformLocation(voisp->program, "Proj"), 1, GL_TRUE, (float*)(p->data)); glUniformMatrix4fv(glGetUniformLocation(voisp->program,"Object_Matrix"),1,GL_TRUE,(float*)(model.data)); lb_matrix_free(p); free(model.data);free(temp_m.data); glBindVertexArray(voisp->VAO[0]); glDrawArrays(GL_TRIANGLES, 0, 36); } ``` pv举例 ```glsl #version 330 core layout (location = 0) in vec3 aPos; uniform mat4 Proj; uniform mat4 Object_Matrix; void main() { gl_Position = Proj*Object_Matrix*vec4(aPos, 1.0); } ``` pf举例 ```glsl #version 330 core out vec4 FragColor; uniform vec4 ourColor; void main() { FragColor = ourColor; } ``` ##### let's start 所有的"物种"都是Something,所以所有”物种“结构体都属于Viewer_Something结构体。这个结构体储存了所有物种必要的属性。 首先需要创建一个世界,建议用世界管理器进行创建世界的操作。 ```c Viewer_World_Manager *vwm=(Viewer_World_Manager*)malloc(sizeof(Viewer_World_Manager)); viewer_World_manager_init(vwm);//创建管理器并初始化 Viewer_World *vw=vwm->create_world(vwm,"First World");//创建世界,和世界的名字 ``` 如果这时你对它进行解释,你会获得一个只有背景的窗口,如下: ```c Viewer_Opengl_Interpreter *voi=(Viewer_Opengl_Interpreter*)malloc(sizeof(Viewer_Opengl_Interpreter));//创建解释器 Viewer_Opengl_Interpreter_initn(voi,vwm);//初始化解释器,传入世界管理器vwm voi->interpreter(voi);//解释 ``` 你可以修改vw的background_color[4]变量,它将影响背景颜色。 接下来我们想要在”世界“vw里描述一些东西,比如点集。 但是在描述点集前,你需要先描述一个相机(人眼),因为只有相机(人眼)才能成像。最后的画面均是来自此相机(人眼)的画面。 (你可以创建多个相机,并设置其中一个可用) ```c char camera[]="Camera"; Node* n=vw->create_something(vw,camera); Viewer_Something*vs=(Viewer_Something*)(n->value); //上述三行代码几乎是定式的 Viewer_Camera*vc=(Viewer_Camera*)(vs->evolution); vc->is_using=1;//设置这个camera正在使用 LB_Matrix *p=Projection(M_PI/3.0f, (float)(vw->g_info->resolution[0])/(float)(vw->g_info->resolution[1]),0.5f,200.0f);//设置camera的透视矩阵 p->copy_data(vc->Proj,p); lb_matrix_free(p); free_node(n); ``` 现在描述点集: ```c Node*n=vw->create_something(vw,"Points"); Viewer_Something* vs=(Viewer_Something*)(n->value); Viewer_Points* vp=(Viewer_Something*)(vs->evolution); vp->pointsize=20.0; vp->Data_rows=3; vp->Data=(float*)malloc(sizeof(float)*3*vp->Data_rows); vp->Data[0]=0.5;vp->Data[1]=0.6;vp->Data[2]=0.2; vp->Data[3]=0.1;vp->Data[4]=1.1;vp->Data[5]=0.1; vp->Data[6]=1.0;vp->Data[7]=1.0;vp->Data[8]=1.0; float colorp[]={1,0,0,1}; vp->set_color(vp,colorp); free_node(node_reverse(n)); ``` 这样你将领取一些结构体,你修改vp结构体内的变量也就是描述点集的过程。 以上代码代码几乎是定式的。比如再描述一些线段: ```c n=vw->create_something(vw,"Edges"); vs=(Viewer_Something*)(n->value); Viewer_Edges* ve=(Viewer_Edges*)(vs->evolution); //上述三行代码和camra的创建几乎一样 /******** 修改ve变量即是描述边集 **********/ free_node(node_reverse(n)); ``` 如果你想要一次创建多个"个体",比如一次创建上述的Points和Edges,你可以 ```c Node* n=vw->create_something(vw,"Points,Edges"); Node* nit=n; Viewer_Something* vs=(Viewer_Something*)(nit->value); Viewer_Points * vp=(Viewer_Points*)(vs->evolution); nit=(Node*)(nit->Next); vs=(Viewer_Something*)(nit->value); Viewer_Edges * ve=(Viewer_Edges*)(vs->evolution); ``` * 如何描述多边形集合 在Viewer_Faces结构体内有这样几个变量Data_rows,Data_index_rows,Data,Data_index. Data_rows表示这些多边形有多少个点,Data_index表示有多少个多边形。 Data的数组长度应该Data_rows*3,因为每个点有x,y,z三个分量,这些点的顺序决定了点的索引:0,1,2..... Data_index也是数组(长度视具体描述而定),它表示这些索引表示的多边形。它将按顺序读取数组,首先读取的数假设为x,那么x表示接下来的多边形有x个点,后面的x个数表示这些点的索引,这样就描述了一个多边形,然后再向后读取一个数y,那么接下来的多边形有y个点......... 以下是一个例子 ```c char faces[]="Faces"; n=vw->create_something(vw,faces); vs=(Viewer_Something*)(n->value); // vs->disappear=1;你可选择让这个个体"消失" Viewer_Faces* vf=(Viewer_Faces*)(vs->evolution);//熟悉的三行代码 free_node(n); vf->Data_rows=5; vf->Data_index_rows=2; vf->color_rows=vf->Data_rows;//你可以让color_rows=Data_index_rows,试试效果就知道了 vf->random_color(vf); vf->normal_rows=vf->Data_index_rows;//你可以让normal_rows=Data_rows,试试效果就知道了。 float *v=(float*)malloc(sizeof(float)*vf->Data_rows*3); unsigned int *f=(unsigned int*)malloc(sizeof(unsigned int)*9); v[0*3+0]=0;v[0*3+1]=0;v[0*3+2]=0; v[1*3+0]=1; v[1*3+1]=0; v[1*3+2]=0; v[2*3+0]=1;v[2*3+1]=1;v[2*3+2] =0; v[3*3+0]=0;v[3*3+1]=1;v[3*3+2] =0; v[4*3+0]=-0.5;v[4*3+1]= 0.500000;v[4*3+2]= 0; f[0]=4;//表示下一个面有4个点 f[1]=0;f[2]=1;f[3]=2;f[4]=3; f[5]=3;//表示下一个面有3个点 f[6]=0;f[7]=3;f[8]=4; vf->Data=v; vf->Data_index=f; ``` 上述代码描述了一个三角形和四边形,它们共用一条边,试试效果吧。 删除物体的话先把物体清空,然后更新解释器,然后删除物体。 #### demo ![picture](demo.gif "功能展示")