| #include <stdio.h> | |
| #include <stdlib.h> | |
| #include <string.h> | |
| #include <math.h> | |
| #include <GLFW/glfw3.h> | |
| #include "nanosvg.h" | |
| #include "tesselator.h" | |
| void* stdAlloc(void* userData, unsigned int size) | |
| { | |
| int* allocated = ( int*)userData; | |
| TESS_NOTUSED(userData); | |
| *allocated += (int)size; | |
| return malloc(size); | |
| } | |
| void stdFree(void* userData, void* ptr) | |
| { | |
| TESS_NOTUSED(userData); | |
| free(ptr); | |
| } | |
| struct MemPool | |
| { | |
| unsigned char* buf; | |
| unsigned int cap; | |
| unsigned int size; | |
| }; | |
| void* poolAlloc( void* userData, unsigned int size ) | |
| { | |
| struct MemPool* pool = (struct MemPool*)userData; | |
| size = (size+0x7) & ~0x7; | |
| if (pool->size + size < pool->cap) | |
| { | |
| unsigned char* ptr = pool->buf + pool->size; | |
| pool->size += size; | |
| return ptr; | |
| } | |
| printf("out of mem: %d < %d!\n", pool->size + size, pool->cap); | |
| return 0; | |
| } | |
| void poolFree( void* userData, void* ptr ) | |
| { | |
| // empty | |
| TESS_NOTUSED(userData); | |
| TESS_NOTUSED(ptr); | |
| } | |
| // Undefine this to see non-interactive heap allocator version. | |
| #define USE_POOL 1 | |
| int run = 1; | |
| int cdt = 0; | |
| static void key(GLFWwindow* window, int key, int scancode, int action, int mods) | |
| { | |
| TESS_NOTUSED(scancode); | |
| TESS_NOTUSED(mods); | |
| if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS) | |
| glfwSetWindowShouldClose(window, GL_TRUE); | |
| if (key == GLFW_KEY_SPACE && action == GLFW_PRESS) | |
| run = !run; | |
| if (key == GLFW_KEY_C && action == GLFW_PRESS) | |
| cdt = !cdt; | |
| } | |
| int main(int argc, char *argv[]) | |
| { | |
| GLFWwindow* window; | |
| const GLFWvidmode* mode; | |
| int width,height,i,j; | |
| struct SVGPath* bg; | |
| struct SVGPath* fg; | |
| struct SVGPath* it; | |
| float bounds[4],view[4],cx,cy,w,offx,offy; | |
| float t = 0.0f, pt = 0.0f; | |
| TESSalloc ma; | |
| TESStesselator* tess = 0; | |
| const int nvp = 3; | |
| unsigned char* vflags = 0; | |
| #ifdef USE_POOL | |
| struct MemPool pool; | |
| unsigned char mem[1024*1024]; | |
| int nvflags = 0; | |
| #else | |
| int allocated = 0; | |
| double t0 = 0, t1 = 0; | |
| #endif | |
| TESS_NOTUSED(argc); | |
| TESS_NOTUSED(argv); | |
| if (!glfwInit()) { | |
| printf("Failed to init GLFW."); | |
| return -1; | |
| } | |
| printf("loading...\n"); | |
| // Load assets | |
| bg = svgParseFromFile("../Bin/bg.svg"); | |
| if (!bg) return -1; | |
| fg = svgParseFromFile("../Bin/fg.svg"); | |
| if (!fg) return -1; | |
| printf("go...\n"); | |
| // Flip y | |
| for (it = bg; it != NULL; it = it->next) | |
| for (i = 0; i < it->npts; ++i) | |
| it->pts[i*2+1] = -it->pts[i*2+1]; | |
| for (it = fg; it != NULL; it = it->next) | |
| for (i = 0; i < it->npts; ++i) | |
| it->pts[i*2+1] = -it->pts[i*2+1]; | |
| // Find FG bounds and center. | |
| bounds[0] = bounds[2] = fg->pts[0]; | |
| bounds[1] = bounds[3] = fg->pts[1]; | |
| for (it = fg; it != NULL; it = it->next) | |
| { | |
| for (i = 0; i < it->npts; ++i) | |
| { | |
| const float x = it->pts[i*2]; | |
| const float y = it->pts[i*2+1]; | |
| if (x < bounds[0]) bounds[0] = x; | |
| if (y < bounds[1]) bounds[1] = y; | |
| if (x > bounds[2]) bounds[2] = x; | |
| if (y > bounds[3]) bounds[3] = y; | |
| } | |
| } | |
| cx = (bounds[0]+bounds[2])/2; | |
| cy = (bounds[1]+bounds[3])/2; | |
| for (it = fg; it != NULL; it = it->next) | |
| { | |
| for (i = 0; i < it->npts; ++i) | |
| { | |
| it->pts[i*2] -= cx; | |
| it->pts[i*2+1] -= cy; | |
| } | |
| } | |
| // Find BG bounds. | |
| bounds[0] = bounds[2] = bg->pts[0]; | |
| bounds[1] = bounds[3] = bg->pts[1]; | |
| for (it = bg; it != NULL; it = it->next) | |
| { | |
| for (i = 0; i < it->npts; ++i) | |
| { | |
| const float x = it->pts[i*2]; | |
| const float y = it->pts[i*2+1]; | |
| if (x < bounds[0]) bounds[0] = x; | |
| if (y < bounds[1]) bounds[1] = y; | |
| if (x > bounds[2]) bounds[2] = x; | |
| if (y > bounds[3]) bounds[3] = y; | |
| } | |
| } | |
| #ifdef USE_POOL | |
| pool.size = 0; | |
| pool.cap = sizeof(mem); | |
| pool.buf = mem; | |
| memset(&ma, 0, sizeof(ma)); | |
| ma.memalloc = poolAlloc; | |
| ma.memfree = poolFree; | |
| ma.userData = (void*)&pool; | |
| ma.extraVertices = 256; // realloc not provided, allow 256 extra vertices. | |
| #else | |
| t0 = glfwGetTime(); | |
| memset(&ma, 0, sizeof(ma)); | |
| ma.memalloc = stdAlloc; | |
| ma.memfree = stdFree; | |
| ma.userData = (void*)&allocated; | |
| ma.extraVertices = 256; // realloc not provided, allow 256 extra vertices. | |
| tess = tessNewTess(&ma); | |
| if (!tess) | |
| return -1; | |
| tessSetOption(tess, TESS_CONSTRAINED_DELAUNAY_TRIANGULATION, 1); | |
| // Offset the foreground shape to center of the bg. | |
| offx = (bounds[2]+bounds[0])/2; | |
| offy = (bounds[3]+bounds[1])/2; | |
| for (it = fg; it != NULL; it = it->next) | |
| { | |
| for (i = 0; i < it->npts; ++i) | |
| { | |
| it->pts[i*2] += offx; | |
| it->pts[i*2+1] += offy; | |
| } | |
| } | |
| // Add contours. | |
| for (it = bg; it != NULL; it = it->next) | |
| tessAddContour(tess, 2, it->pts, sizeof(float)*2, it->npts); | |
| for (it = fg; it != NULL; it = it->next) | |
| tessAddContour(tess, 2, it->pts, sizeof(float)*2, it->npts); | |
| if (!tessTesselate(tess, TESS_WINDING_POSITIVE, TESS_POLYGONS, nvp, 2, 0)) | |
| return -1; | |
| t1 = glfwGetTime(); | |
| printf("Time: %.3f ms\n", (t1 - t0) * 1000.0f); | |
| printf("Memory used: %.1f kB\n", allocated/1024.0f); | |
| #endif | |
| mode = glfwGetVideoMode(glfwGetPrimaryMonitor()); | |
| width = mode->width - 40; | |
| height = mode->height - 80; | |
| window = glfwCreateWindow(width, height, "Libtess2 Demo", NULL, NULL); | |
| if (!window) { | |
| glfwTerminate(); | |
| return -1; | |
| } | |
| glfwSetKeyCallback(window, key); | |
| glfwMakeContextCurrent(window); | |
| // Adjust bounds so that we get nice view of the bg. | |
| cx = (bounds[0]+bounds[2])/2; | |
| cy = (bounds[3]+bounds[1])/2; | |
| w = (bounds[2]-bounds[0])/2; | |
| view[0] = cx - w*1.2f; | |
| view[2] = cx + w*1.2f; | |
| view[1] = cy - w*1.2f*(float)height/(float)width; | |
| view[3] = cy + w*1.2f*(float)height/(float)width; | |
| glfwSetTime(0); | |
| while (!glfwWindowShouldClose(window)) | |
| { | |
| int winWidth, winHeight; | |
| int fbWidth, fbHeight; | |
| float pxr, ct; | |
| glfwGetWindowSize(window, &winWidth, &winHeight); | |
| glfwGetFramebufferSize(window, &fbWidth, &fbHeight); | |
| // Calculate pixel ration for hi-dpi devices. | |
| pxr = (float)fbWidth / (float)winWidth; | |
| ct = (float)glfwGetTime(); | |
| if (run) t += ct - pt; | |
| pt = ct; | |
| // Update and render | |
| glViewport(0, 0, fbWidth, fbHeight); | |
| glClearColor(0.3f, 0.3f, 0.32f, 1.0f); | |
| glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT); | |
| glEnable(GL_BLEND); | |
| glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); | |
| glDisable(GL_TEXTURE_2D); | |
| glMatrixMode(GL_PROJECTION); | |
| glLoadIdentity(); | |
| glOrtho(view[0],view[2],view[1],view[3],-1,1); | |
| glMatrixMode(GL_MODELVIEW); | |
| glLoadIdentity(); | |
| glDisable(GL_DEPTH_TEST); | |
| glEnable(GL_BLEND); | |
| glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA); | |
| #ifdef USE_POOL | |
| pool.size = 0; // reset pool | |
| tess = tessNewTess(&ma); | |
| if (tess) | |
| { | |
| tessSetOption(tess, TESS_CONSTRAINED_DELAUNAY_TRIANGULATION, cdt); | |
| offx = (view[2]+view[0])/2 + sinf(t) * (view[2]-view[0])/2; | |
| offy = (view[3]+view[1])/2 + cosf(t*3.13f) * (view[3]-view[1])/6; | |
| for (it = fg; it != NULL; it = it->next) | |
| { | |
| for (i = 0; i < it->npts; ++i) | |
| { | |
| it->pts[i*2] += offx; | |
| it->pts[i*2+1] += offy; | |
| } | |
| } | |
| for (it = bg; it != NULL; it = it->next) | |
| tessAddContour(tess, 2, it->pts, sizeof(float)*2, it->npts); | |
| for (it = fg; it != NULL; it = it->next) | |
| tessAddContour(tess, 2, it->pts, sizeof(float)*2, it->npts); | |
| for (it = fg; it != NULL; it = it->next) | |
| { | |
| for (i = 0; i < it->npts; ++i) | |
| { | |
| it->pts[i*2] -= offx; | |
| it->pts[i*2+1] -= offy; | |
| } | |
| } | |
| // First combine contours and then triangulate, this removes unnecessary inner vertices. | |
| if (tessTesselate(tess, TESS_WINDING_POSITIVE, TESS_BOUNDARY_CONTOURS, 0, 0, 0)) | |
| { | |
| const float* verts = tessGetVertices(tess); | |
| const int* vinds = tessGetVertexIndices(tess); | |
| const int nverts = tessGetVertexCount(tess); | |
| const int* elems = tessGetElements(tess); | |
| const int nelems = tessGetElementCount(tess); | |
| if (nverts > nvflags) | |
| { | |
| if (vflags) | |
| free(vflags); | |
| nvflags = nverts; | |
| vflags = (unsigned char*)malloc(sizeof(unsigned char)*nvflags); | |
| } | |
| if (vflags) | |
| { | |
| // Vertex indices describe the order the indices were added and can be used | |
| // to map the tesselator output to input. Vertices marked as TESS_UNDEF | |
| // are the ones that were created at the intersection of segments. | |
| // That is, if vflags is set it means that the vertex comes from intersegment. | |
| for (i = 0; i < nverts; ++i) | |
| vflags[i] = vinds[i] == TESS_UNDEF ? 1 : 0; | |
| } | |
| for (i = 0; i < nelems; ++i) | |
| { | |
| int b = elems[i*2]; | |
| int n = elems[i*2+1]; | |
| tessAddContour(tess, 2, &verts[b*2], sizeof(float)*2, n); | |
| } | |
| if (!tessTesselate(tess, TESS_WINDING_POSITIVE, TESS_POLYGONS, nvp, 2, 0)) | |
| tess = 0; | |
| } | |
| else | |
| tess = 0; | |
| } | |
| #endif | |
| // Draw tesselated pieces. | |
| if (tess) | |
| { | |
| const float* verts = tessGetVertices(tess); | |
| const int* vinds = tessGetVertexIndices(tess); | |
| const int* elems = tessGetElements(tess); | |
| const int nverts = tessGetVertexCount(tess); | |
| const int nelems = tessGetElementCount(tess); | |
| // Draw polygons. | |
| glColor4ub(255,255,255,128); | |
| for (i = 0; i < nelems; ++i) | |
| { | |
| const int* p = &elems[i*nvp]; | |
| glBegin(GL_TRIANGLE_FAN); | |
| for (j = 0; j < nvp && p[j] != TESS_UNDEF; ++j) | |
| glVertex2f(verts[p[j]*2], verts[p[j]*2+1]); | |
| glEnd(); | |
| } | |
| glLineWidth(1.0f * pxr); | |
| glPointSize(3.0f * pxr); | |
| glColor4ub(0,0,0,16); | |
| for (i = 0; i < nelems; ++i) | |
| { | |
| const int* p = &elems[i*nvp]; | |
| glBegin(GL_LINE_LOOP); | |
| for (j = 0; j < nvp && p[j] != TESS_UNDEF; ++j) | |
| glVertex2f(verts[p[j]*2], verts[p[j]*2+1]); | |
| glEnd(); | |
| } | |
| glColor4ub(0,0,0,128); | |
| glBegin(GL_POINTS); | |
| for (i = 0; i < nverts; ++i) | |
| { | |
| if (vflags && vflags[vinds[i]]) | |
| glColor4ub(255,0,0,192); | |
| else | |
| glColor4ub(0,0,0,128); | |
| glVertex2f(verts[i*2], verts[i*2+1]); | |
| } | |
| glEnd(); | |
| glPointSize(1.0f); | |
| } | |
| glEnable(GL_DEPTH_TEST); | |
| glfwSwapBuffers(window); | |
| glfwPollEvents(); | |
| } | |
| if (tess) tessDeleteTess(tess); | |
| if (vflags) | |
| free(vflags); | |
| svgDelete(bg); | |
| svgDelete(fg); | |
| glfwTerminate(); | |
| return 0; | |
| } |