#include #include #include #include #include #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; }