9c9368dbbb1b1b95f5f6466deeddc855d64d39ed
[sdk] / butterbur / src / butterbur.ec
1 import "ecere"
2
3 #define glTypeUnsignedShort     0x1403
4 #define glTypeFloat             0x1406
5
6 enum JoinType { miter, round, bevel };
7 enum CapType { butt, round, square };
8
9 define joinType = JoinType::bevel;
10 define capType = CapType::round;
11
12 class ButterburTest : Window
13 {
14    displayDriver = "OpenGL";
15    caption = $"Butterbur's Humble Beginnings";
16    background = formColor;
17    borderStyle = sizable;
18    hasMaximize = true;
19    hasMinimize = true;
20    hasClose = true;
21    clientSize = { 632, 438 };
22
23    BBScene scene { };
24
25    BBRectangle rect { scene, lineColor = { 128, tomato }, fillColor = { 128, skyBlue }, box = { 30, 30, 340, 190 }, cap = capType, join = joinType, lineWidth = 10 };
26    BBCircle circle  { scene, lineColor = { 128, green }, fillColor = { 170, tomato }, center = { 390, 280 }, cap = capType, join = joinType, radius = 100, lineWidth = 8 };
27    BBEllipse ell  { scene, lineColor = { 128, yellow }, fillColor = { 100, black }, center = { 250, 310 }, k = 1.3, cap = capType, join = joinType, radius = 100, lineWidth = 4 };
28
29    BBPath triangle
30    {
31       scene, lineColor = { 128, blue }, fillColor = { 100, lime }, lineWidth = 20;
32       //closed = true;
33       join = miter; //joinType;
34       cap = capType;
35       nodes.copySrc = [
36          // Pointf { 150, 80 },
37          Pointf { 210, 80 },
38          Pointf { 50, 220 },
39          Pointf { 240, 220 }
40       ];
41    };
42
43    BBPath test
44    {
45       scene, lineColor = red, lineWidth = 1;
46       join = miter;
47       closed = true;
48       cap = butt;
49       nodes.copySrc = [
50          Pointf { 210, 80 },
51          Pointf { 50, 220 },
52          Pointf { 240, 220 }
53       ];
54    };
55    BBPath triangle2
56    {
57       scene, lineColor = purple, fillColor = { 100, orange }, lineWidth = 9;
58       closed = true;
59       cap = capType;
60       join = joinType;
61       nodes.copySrc = [
62          // Pointf { 150, 80 },
63          Pointf { 510, 180 },
64          Pointf { 150, 320 },
65          Pointf { 340, 320 }
66       ];
67    };
68
69    BBPath dot
70    {
71       scene, lineColor = black, lineWidth = 20;
72       cap = square;
73       nodes.copySrc = [ Pointf { 300, 300 } ];
74    };
75
76    void OnRedraw(Surface surface)
77    {
78       display.antiAlias = true;
79       scene.render();
80    }
81 }
82
83 ButterburTest bbTest {};
84
85 class BBScene
86 {
87    List<BBObject> objects { };
88
89    void render()
90    {
91       for(o : objects)
92       {
93          if(o.needUpdate) { o.update(); o.needUpdate = false; }
94          o.render();
95       }
96    }
97 }
98
99 class BBObject
100 {
101    BBScene parent;
102
103    bool needUpdate;
104
105    needUpdate = true;
106
107    virtual void update();
108    virtual void render();
109
110    public property BBScene parent
111    {
112       set
113       {
114          if(parent)
115             parent.objects.TakeOut(this);
116          parent = value;
117          value.objects.Add(this);
118       }
119       get { return parent; }
120    }
121 }
122
123 class BBPath : BBObject
124 {
125    Array<Pointf> nodes { };
126
127    GLAB fillVBO { };
128    GLAB lineVBO { };
129    GLEAB fillIndices { };
130    GLEAB lineIndices { };
131
132    ColorAlpha lineColor;
133    ColorAlpha fillColor;
134    float lineWidth;
135    JoinType join;
136    CapType cap;
137    bool closed;
138    bool noJoin;
139    int lineCount;
140    lineColor = black;
141
142    property ColorAlpha lineColor
143    {
144       set { lineColor = value; }
145       get { return lineColor; }
146    };
147
148    property ColorAlpha fillColor
149    {
150       set { fillColor = value; }
151       get { return fillColor; }
152    };
153
154    property float lineWidth
155    {
156       set { lineWidth = value; }
157       get { return lineWidth; }
158    };
159
160    void update()
161    {
162       Pointf * points;
163       uint16 * ix;
164       uint16 i;
165       uint tc = nodes.count;
166       uint ixCount;
167       uint vboCount;
168
169       if(lineWidth > 1 && (tc > 1 || cap != butt))
170       {
171          int rCount = (!noJoin && join == round) ? 7 : (!noJoin && join == bevel) ? 2 : 1;
172          int capCount = (cap == round) ? 7 : 1;
173          uint16 startIX = 0;
174          uint d = 0;
175
176          vboCount = closed ? (tc * (rCount+1)) : (2*(capCount+1) + ((tc > 2) ? (tc-2) * (rCount+1) : 0));
177          points = new Pointf[vboCount];
178
179          ixCount = closed ? (tc * rCount*2 + closed*2) :
180             (2*(2*capCount) + ((tc > 2) ? (tc-2) * (2*rCount) : 0));
181          ix = new uint16[ixCount];
182
183          for(i = 0; i < tc + (tc == 1); i++)
184          {
185             bool end = false;
186             if(i == tc) { i = 0; end = true; }
187             {
188                bool isCap = false;
189                Pointf p = nodes[i];
190                Pointf before = i > 0 ? nodes[i-1] : (closed ? nodes[tc-1] : (tc > 1 ? nodes[1] : nodes[0]));
191                Pointf after  = i < tc-1 ? nodes[i+1] : (closed ? nodes[0] : (tc > 1 ? nodes[i-1] : nodes[0]));
192                float ldx = p.x - before.x, ldy = p.y - before.y;
193                float rdx = after.x - p.x, rdy = after.y - p.y;
194                double at1 = atan2(ldy, ldx);
195                double at2 = atan2(rdy, rdx);
196                float c, s;
197                double diffAngle = at2 - at1;
198                bool simpleMean = true;
199                int n;
200
201                if(!closed && (i == 0 || i == tc-1)) isCap = true;
202
203                if(Abs(diffAngle) >= Pi)
204                {
205                   simpleMean = false;
206                   if(diffAngle < 0)
207                      diffAngle += 2*Pi;
208                   else
209                      diffAngle = diffAngle - 2*Pi;
210                }
211
212                if(isCap)
213                {
214                   // Caps
215                   switch(cap)
216                   {
217                      case round:
218                      {
219                         double r = lineWidth;
220                         int t;
221                         int add = 0;
222                         double angle = (i == 0 && !end) ? at1 : at2;
223                         if(end) angle += Pi;
224                         if(i || end)
225                         {
226                            double ang = angle - Pi/2 + Pi;
227                            c = (float)(cos(ang) * r/2), s = (float)(sin(ang) * r/2);
228                            points[startIX] = { p.x + c, p.y + s };
229                            add = 1;
230                         }
231                         for(t = 0; t < capCount; t++)
232                         {
233                            double ang = angle + (i ? -Pi/2 : Pi/2) - t * Pi/(capCount-1);
234                            c = (float)(cos(ang) * r/2), s = (float)(sin(ang) * r/2);
235                            points[startIX + add + t] = { p.x + c, p.y + s };
236                         }
237                         if(!i && !end)
238                         {
239                            double ang = angle + Pi/2 + Pi;
240                            c = (float)(cos(ang) * r/2), s = (float)(sin(ang) * r/2);
241                            points[startIX + 1 + capCount-1] = { p.x + c, p.y + s };
242                         }
243                         break;
244                      }
245                      case butt:
246                      {
247                         double r = lineWidth;
248                         double angle = (i == 0 && !end) ? at1 : at2;
249                         double ang = angle - Pi/2;
250                         c = (float)(cos(ang) * r/2), s = (float)(sin(ang) * r/2);
251                         points[startIX] = { p.x - c, p.y - s};
252                         points[startIX+1] = { p.x + c, p.y + s };
253                         break;
254                      }
255                      case square:
256                      {
257                         double r = lineWidth * sqrt(2);
258                         double angle = (i == 0 && !end) ? at1 : at2;
259                         double ang = (i || end) ? (angle - Pi/4) + Pi : (angle + Pi/4);
260                         c = (float)(cos(ang) * r/2), s = (float)(sin(ang) * r/2);
261                         points[startIX] = { p.x + c, p.y + s};
262
263                         ang = (i || end) ? (angle + Pi/4) + Pi : (angle - Pi/4);
264                         c = (float)(cos(ang) * r/2), s = (float)(sin(ang) * r/2);
265                         points[startIX+1] = { p.x + c, p.y + s };
266                         break;
267                      }
268                   }
269                }
270                else
271                {
272                   double r = lineWidth / cos(diffAngle/2);
273                   double angle;
274                   bool diffSigns = Sgn(at1) != Sgn(at2);
275                   if(simpleMean)
276                      angle = (double)(at1 + at2) / 2;
277                   else if(diffSigns && ldy > 0 && rdy < 0 && !ldx && !rdx)
278                      angle = Pi;
279                   else if(diffSigns && ldy < 0 && rdy > 0 && !ldx && !rdx)
280                      angle = 0;
281                   else
282                      angle = (double)(at1 + at2) / 2 + Pi;
283
284                   angle += Pi/2;
285
286                   c = (float)(cos(angle) * r/2), s = (float)(sin(angle) * r/2);
287
288                   points[startIX] = { p.x - c, p.y - s };
289                   if(rCount > 1)
290                   {
291                      int t;
292
293                      p = nodes[i];
294                      r = lineWidth;
295                      for(t = 0; t < rCount; t++)
296                      {
297                         double ang = at2 - ((rCount-1-t) * diffAngle / (rCount-1)) + Pi/2;
298                         c = (float)(cos(ang) * r/2), s = (float)(sin(ang) * r/2);
299                         points[startIX+1+t] = { p.x + c, p.y + s };
300                      }
301                   }
302                   else
303                      points[startIX+1] = { p.x + c, p.y + s };
304                }
305                for(n = 0; n < (isCap ? capCount : rCount); n++)
306                {
307                   ix[d++] = startIX;
308                   ix[d++] = (uint16)(startIX+n+1);
309                }
310                startIX += (uint16)(isCap ? capCount : rCount) + 1;
311                if(end) break;
312             }
313          }
314          if(closed)
315          {
316             ix[d++] = 0;
317             ix[d++] = 1;
318          }
319       }
320       else
321       {
322          vboCount = tc;
323          points = nodes.array;
324          ixCount = tc + closed;
325          ix = new uint16[ixCount];
326
327          for(i = 0; i < tc; i++)
328             ix[i] = i;
329          if(closed)
330             ix[i] = 0;
331       }
332       lineVBO.upload(vboCount*sizeof(Pointf), points);
333       lineIndices.upload(ixCount * sizeof(uint16), ix);
334       lineCount = ixCount;
335
336       if(points != nodes.array)
337          delete points;
338
339       if(closed)
340       {
341          uint16 i;
342          delete ix;
343          ix = new uint16[tc + closed];
344
345          for(i = 0; i < tc; i++)
346             ix[i] = i;
347          ix[i] = 0;
348
349          fillVBO.upload(tc*sizeof(Pointf), nodes.array);
350          fillIndices.upload(tc * sizeof(uint16), ix);
351       }
352
353       delete ix;
354    }
355
356    void render()
357    {
358       // Fill
359       if(closed)
360       {
361          glimtkColor4f(fillColor.color.r/255.0f, fillColor.color.g/255.0f, fillColor.color.b/255.0f, fillColor.a/255.0f);
362          fillVBO.use(vertex, 2, glTypeFloat, 0, null);
363          fillIndices.draw(GLIMTKMode::triangleFan, nodes.count, glTypeUnsignedShort, null);
364       }
365
366       // Line
367       glimtkColor4f(lineColor.color.r/255.0f, lineColor.color.g/255.0f, lineColor.color.b/255.0f, lineColor.a/255.0f);
368       lineVBO.use(vertex, 2, glTypeFloat, 0, null);
369
370       lineIndices.draw(lineWidth > 1 ? GLIMTKMode::triangleStrip : GLIMTKMode::lineStrip, lineCount, glTypeUnsignedShort, null);
371
372       glimtkColor4f(1, 0, 0, 1);
373       // lineIndices.draw(GLIMTKMode::lineStrip, lineCount, glTypeUnsignedShort, null);
374    }
375 }
376
377 class BBRectangle : BBPath
378 {
379    closed = true;
380
381    Box box;
382    property Box box
383    {
384       set { box = value; needUpdate = true; }
385       get { value = box; }
386    }
387
388    void update()
389    {
390       nodes.size = 4;
391       nodes[0] = { box.left, box.top };
392       nodes[1] = { box.left, box.bottom };
393       nodes[2] = { box.right, box.bottom };
394       nodes[3] = { box.right, box.top };
395
396       BBPath::update();
397    }
398 }
399
400 class BBCircle : BBPath
401 {
402    closed = true;
403    noJoin = true;
404
405    Pointf center;
406    float radius;
407
408    radius = 10;
409
410    property Pointf center
411    {
412       set { center = value; needUpdate = true; }
413       get { value = center; }
414    }
415
416    property float radius
417    {
418       set { radius = value; needUpdate = true; }
419       get { return radius; }
420    }
421
422    void update()
423    {
424       uint count = radius < 3 ? 6 : radius < 20 ? 12 : radius < 50 ? 30 : 100;
425       int i;
426
427       nodes.size = count;
428       for(i = 0; i < count; i++)
429       {
430          double a = i * 2*Pi / count;
431          nodes[i] = { center.x + (float)cos(a) * radius, center.y + (float)sin(a) * radius };
432       }
433
434       BBPath::update();
435    }
436 }
437
438 class BBEllipse : BBPath
439 {
440    closed = true;
441    noJoin = true;
442
443    Pointf center;
444    float radius;
445    float k;
446
447    radius = 10;
448    k = 1;
449
450    property Pointf center
451    {
452       set { center = value; needUpdate = true; }
453       get { value = center; }
454    }
455
456    property float radius
457    {
458       set { radius = value; needUpdate = true; }
459       get { return radius; }
460    }
461
462    void update()
463    {
464       uint count = radius < 3 ? 6 : radius < 20 ? 12 : radius < 50 ? 30 : 100;
465       int i;
466
467       nodes.size = count;
468       for(i = 0; i < count; i++)
469       {
470          double a = i * 2*Pi / (count+1);
471          nodes[i] = { center.x + (float)cos(a) * radius * k, center.y + (float)sin(a) * radius };
472       }
473
474       BBPath::update();
475    }
476 }