Butterbur: Tesselation support for filling closed paths
[sdk] / butterbur / src / tesselation.ec
diff --git a/butterbur/src/tesselation.ec b/butterbur/src/tesselation.ec
new file mode 100644 (file)
index 0000000..e48c301
--- /dev/null
@@ -0,0 +1,268 @@
+#define property _property
+#include <glutess.h>
+#undef property
+
+import "ecere"
+
+#if defined(__WIN32__)
+#define TESS_CALLBACK_FUNCTION_PROTOTYPE void (__stdcall*)(void)
+#else
+#define TESS_CALLBACK_FUNCTION_PROTOTYPE void *
+#define CALLBACK
+#endif
+
+void * eC_malloc(unsigned int size) { return new byte[size]; }
+void * eC_realloc(void * pointer, unsigned int size) { return renew pointer byte[size]; }
+void eC_free(void * pointer) { delete pointer; }
+
+Mutex tessMutex { };
+
+static Array<Vector3D> vertices { };
+
+static GLUtesselator * butterburTesselator = null;
+
+static double ccw(Pointf a, Pointf b, Pointf c)
+{
+   return (((double)b.x - (double)a.x) * ((double)c.y - (double)a.y) - ((double)c.x - (double)a.x) * ((double)b.y - (double)a.y));
+}
+
+static bool selfIntersects(Array<Pointf> points)
+{
+   int i, j;
+   for(i = 0; i < points.count; i++)
+   {
+      Pointf * ap = &points[i], * aq = &points[(i < points.count-1) ? i + 1 : 0];
+      for(j = 0; j < points.count; j++)
+      {
+         Pointf * bp = &points[j], * bq = &points[(j < points.count-1) ? j + 1 : 0];
+         if(ap != bp && ap != bq && aq != bp && aq != bq)
+         {
+            double a = ccw(ap, aq, bp);
+            double b = ccw(ap, aq, bq);
+            double c = ccw(bp, bq, ap);
+            double d = ccw(bp, bq, aq);
+
+            #define roundoffDouble  0.00000000001
+
+            if(Abs(a) < roundoffDouble) a = 0;
+            if(Abs(b) < roundoffDouble) b = 0;
+            if(Abs(c) < roundoffDouble) c = 0;
+            if(Abs(d) < roundoffDouble) d = 0;
+
+            if(Sgn(a) * Sgn(b) < 0 && Sgn(c) * Sgn(d) < 0)
+               return true;
+         }
+      }
+   }
+   return false;
+}
+
+struct TessPrim
+{
+   GLIMTKMode type;
+   uint count;
+   uint32 * indices;
+   void OnFree()
+   {
+      delete indices;
+   }
+};
+
+struct TessPrimData
+{
+   GLIMTKMode which;
+
+   Array<CombineVertex> combineVertices;
+   Array<TessPrim> primitives;
+   Array<uint> triIndices;
+   Array<uint> tmpIndices;
+   Array<Pointf> output;
+};
+
+class CombineVertex : struct
+{
+   Pointf position;
+   int id;
+};
+
+static void CALLBACK tessPrimBegin(GLenum which, TessPrimData tesselatorData)
+{
+   tesselatorData.which = which == GL_TRIANGLES ? triangles : which == GL_TRIANGLE_STRIP ? triangleStrip : triangleFan;
+   if(which != GL_TRIANGLES)
+   {
+      tesselatorData.tmpIndices.size = 0;
+      if(tesselatorData.primitives.size + 1 > tesselatorData.primitives.minAllocSize)
+         tesselatorData.primitives.minAllocSize += tesselatorData.primitives.minAllocSize / 2;
+   }
+}
+
+static void CALLBACK tessPrimVertex(Pointf * point, TessPrimData tesselatorData)
+{
+   uint index = 0;
+   uint * indices;
+
+   if(tesselatorData.which == triangles)
+   {
+      if(tesselatorData.triIndices.size + 1 > tesselatorData.triIndices.minAllocSize)
+         tesselatorData.triIndices.minAllocSize += tesselatorData.triIndices.minAllocSize / 2;
+      indices = tesselatorData.triIndices.array;
+   }
+   else
+   {
+      if(tesselatorData.tmpIndices.size + 1 > tesselatorData.tmpIndices.minAllocSize)
+         tesselatorData.tmpIndices.minAllocSize += tesselatorData.tmpIndices.minAllocSize / 2;
+      indices = tesselatorData.tmpIndices.array;
+   }
+
+   if(point >= &tesselatorData.output[0] && point < &tesselatorData.output[tesselatorData.output.count])
+      index = point - &tesselatorData.output[0];
+   else
+   {
+      CombineVertex vertex = (CombineVertex)point;
+      index = tesselatorData.output.count + vertex.id;
+   }
+
+   if(tesselatorData.which == triangles)
+   {
+      uint ix = tesselatorData.triIndices.size;
+      tesselatorData.triIndices.size++;
+      indices[ix] = index;
+   }
+   else
+   {
+      uint ix = tesselatorData.tmpIndices.size;
+      tesselatorData.tmpIndices.size++;
+      indices[ix] = index;
+   }
+}
+
+static void CALLBACK tessPrimEnd(TessPrimData tesselatorData)
+{
+   if(tesselatorData.which != triangles)
+   {
+      TessPrim * prim;
+      uint ix = tesselatorData.primitives.size;
+      tesselatorData.primitives.size++;
+      prim = &tesselatorData.primitives[ix];
+      prim->type = tesselatorData.which;
+      prim->count = tesselatorData.tmpIndices.count;
+      prim->indices = new uint32[prim->count];
+      memcpy(prim->indices, &tesselatorData.tmpIndices[0], sizeof(uint) * prim->count);
+   }
+}
+
+static void CALLBACK tessPrimCombine(GLdouble coords[3], Pointf *d[4],
+                                     GLfloat w[4], Pointf **dataOut,
+                                                                                 TessPrimData tesselatorData)
+{
+   CombineVertex vertex
+   {
+      position = { (float)coords[0], (float)coords[1] };
+      id = tesselatorData.combineVertices.count;
+   };
+   *dataOut = &vertex.position;
+   tesselatorData.combineVertices.Add(vertex);
+}
+
+void tesselatePolygon(Array<Pointf> area, Array<Pointf> * outputPtr, Array<TessPrim> * primitivesPtr)
+{
+       int i, d;
+   Pointf * destPoints;
+   int totalCount = area.count;
+   int start = 0;
+   Array<Pointf> output { };
+   Array<TessPrim> primitives { minAllocSize = 16 };
+   Array<CombineVertex> combineVertices { };
+
+   static TessPrimData tesselatorData;
+
+   tessMutex.Wait();
+
+   output.size = totalCount;
+   destPoints = &output[0];
+
+   tesselatorData.primitives = primitives;
+   tesselatorData.combineVertices = combineVertices;
+   tesselatorData.tmpIndices = { minAllocSize = 16 };
+   tesselatorData.triIndices = { minAllocSize = 16 };
+   tesselatorData.output = output;
+
+   vertices.size = totalCount;
+
+   {
+      int n = area.count;
+      d = 0;
+      for(i = 0; i < n; i++, d++)
+      {
+         destPoints[d] = area[i];
+         vertices[d] = { area[i].x, area[i].y };
+      }
+   }
+
+   if(!butterburTesselator)
+   {
+      butterburTesselator = gluNewTess();
+
+      gluTessCallback(butterburTesselator, GLU_TESS_BEGIN_DATA,  (TESS_CALLBACK_FUNCTION_PROTOTYPE)(void *)tessPrimBegin);
+      gluTessCallback(butterburTesselator, GLU_TESS_END_DATA,    (TESS_CALLBACK_FUNCTION_PROTOTYPE)(void *)tessPrimEnd);
+      gluTessCallback(butterburTesselator, GLU_TESS_VERTEX_DATA,  (TESS_CALLBACK_FUNCTION_PROTOTYPE)(void *)tessPrimVertex);
+      gluTessCallback(butterburTesselator, GLU_TESS_COMBINE_DATA, (TESS_CALLBACK_FUNCTION_PROTOTYPE)(void *)tessPrimCombine);
+
+      gluTessProperty(butterburTesselator, GLU_TESS_BOUNDARY_ONLY, GL_FALSE);
+      gluTessProperty(butterburTesselator, GLU_TESS_WINDING_RULE, GLU_TESS_WINDING_POSITIVE);
+   }
+
+   gluTessBeginPolygon(butterburTesselator, &tesselatorData);
+
+   start = 0;
+   {
+      int n = area.count;
+      if(n && !selfIntersects(area))
+      {
+         gluTessBeginContour(butterburTesselator);
+         d = start;
+         for(i = 0; i < n; i++, d++)
+            gluTessVertex(butterburTesselator, (double *)&vertices[d], &destPoints[d]);
+         gluTessEndContour(butterburTesselator);
+      }
+      start += n;
+   }
+
+   gluTessEndPolygon(butterburTesselator);
+
+   if(combineVertices.count)
+   {
+      int c;
+      int count = output.count;
+
+      output.size = count + combineVertices.count;
+      for(c = 0; c < combineVertices.count; c++)
+      {
+         CombineVertex vertex = combineVertices[c];
+         output[count++] = vertex.position;
+         delete vertex;
+      }
+   }
+   delete combineVertices;
+
+   if(tesselatorData.triIndices && tesselatorData.triIndices.count)
+   {
+      uint ix = primitives.size;
+      TessPrim * prim;
+      primitives.size++;
+      prim = &primitives[ix];
+      prim->type = triangles;
+      prim->count = tesselatorData.triIndices.count;
+      prim->indices = new uint[prim->count];
+      memcpy(prim->indices, &tesselatorData.triIndices[0], sizeof(uint) * prim->count);
+   }
+
+   primitives.minAllocSize = 0;
+   delete tesselatorData.triIndices;
+   delete tesselatorData.tmpIndices;
+
+   *outputPtr = output;
+   *primitivesPtr = primitives;
+
+   tessMutex.Release();
+}