3 #define glTypeUnsignedShort 0x1403
4 #define glTypeFloat 0x1406
6 enum JoinType { miter, round, bevel };
7 enum CapType { butt, round, square };
9 define joinType = JoinType::miter;
10 define capType = CapType::round;
12 static double pointsArea(Array<Pointf> points)
18 for(i = 0; i < points.count - 1; i++)
19 area += points[i].x * points[i+1].y - points[i+1].x * points[i].y;
20 area += points[i].x * points[0].y - points[0].x * points[i].y;
25 class ButterburTest : Window
27 displayDriver = "OpenGL";
28 caption = $"Butterbur's Humble Beginnings";
29 borderStyle = sizable;
33 clientSize = { 632, 438 };
40 box = { 450, 50, 550, 150 },
41 lineColor = { 230, green }, fillColor = { 126, magenta }, cap = capType, join = round, lineWidth = 10
46 box = { 30, 30, 340, 190 }, rx = 20, ry = 20,
47 lineColor = { 230, red }, fillColor = { 126, black }, cap = capType, join = joinType, lineWidth = 4
49 BBCircle circle { scene, lineColor = { 128, green }, fillColor = { 170, tomato }, center = { 390, 280 }, cap = capType, join = joinType, radius = 100, lineWidth = 8 };
50 BBEllipse ell { scene, lineColor = { 128, yellow }, fillColor = { 100, black }, center = { 250, 310 }, k = 1.3, cap = capType, join = joinType, radius = 100, lineWidth = 4 };
54 scene, lineColor = { 128, blue }, fillColor = { 100, lime }, lineWidth = 20;
56 join = bevel; //miter; //joinType;
59 // Pointf { 150, 80 },
68 scene, lineColor = red, lineWidth = 1;
80 scene, lineColor = { 128, purple }, fillColor = { 100, orange }, lineWidth = 9;
83 join = round; //joinType;
85 Pointf { 510, 180 }, Pointf { 150, 320 }, Pointf { 340, 320 }
86 // Pointf { 340, 320 }, Pointf { 150, 320 }, Pointf { 510, 180 }
92 scene, lineColor = black, lineWidth = 20;
94 nodes.copySrc = [ Pointf { 300, 300 } ];
99 scene, lineColor = { 128, black }, fillColor = { 128, orange }, lineWidth = 18;
101 needTesselation = true;
121 BBPath concaveOutline
123 scene, lineColor = blue, lineWidth = 1;
125 needTesselation = true;
137 void OnRedraw(Surface surface)
139 display.antiAlias = true;
144 ButterburTest bbTest {};
148 List<BBObject> objects { };
154 if(o.needUpdate) { o.update(); o.needUpdate = false; }
168 virtual void update();
169 virtual void render();
171 public property BBScene parent
176 parent.objects.TakeOut(this);
178 value.objects.Add(this);
180 get { return parent; }
184 class BBPath : BBObject
186 Array<Pointf> nodes { };
188 GLEAB fillIndices { };
189 GLEAB lineIndices { };
191 ColorAlpha lineColor;
192 ColorAlpha fillColor;
198 bool needTesselation;
199 int lineCount, fillCount;
209 property ColorAlpha lineColor
211 set { lineColor = value; }
212 get { return lineColor; }
215 property ColorAlpha fillColor
217 set { fillColor = value; }
218 get { return fillColor; }
221 property float lineWidth
223 set { lineWidth = value; }
224 get { return lineWidth; }
231 uint16 * ixFill = null;
233 uint tc = nodes.count;
237 if(lineWidth > 1 && (tc > 1 || cap != butt))
239 int rCount = (!noJoin && join == round) ? 7 : (!noJoin && join == bevel) ? 2 : 1;
240 int capCount = (cap == round) ? 7 : 1;
243 bool flip = pointsArea(nodes) > 0;
245 vboCount = closed ? (tc * (rCount+1)) : (2*(capCount+1) + ((tc > 2) ? (tc-2) * (rCount+1) : 0));
246 points = new Pointf[vboCount];
248 ixCount = closed ? (tc * rCount*2 + closed*2) :
249 (2*(2*capCount) + ((tc > 2) ? (tc-2) * (2*rCount) : 0));
250 ix = new uint16[ixCount];
251 ixFill = new uint16[tc];
253 for(i = 0; i < tc + (tc == 1); i++)
257 if(i == tc) { i = 0; end = true; }
259 #define DOFLIP(x) (flip ? (uint16)((tc-1)-(x)) : (x))
265 Pointf p = nodes[ni];
266 Pointf before = i > 0 ? nodes[DOFLIP(i-1)] : (closed ? nodes[DOFLIP(tc-1)] : (tc > 1 ? nodes[DOFLIP(1)] : nodes[DOFLIP(0)]));
267 Pointf after = i < tc-1 ? nodes[DOFLIP(i+1)] : (closed ? nodes[DOFLIP(0)] : (tc > 1 ? nodes[DOFLIP(i-1)] : nodes[DOFLIP(0)]));
268 float ldx = p.x - before.x, ldy = p.y - before.y;
269 float rdx = after.x - p.x, rdy = after.y - p.y;
270 bool thisFlip = false;
271 double at1 = atan2(ldy, ldx);
272 double at2 = atan2(rdy, rdx);
274 double diffAngle = at2 - at1;
275 bool simpleMean = true;
278 if(!closed && (i == 0 || i == tc-1)) isCap = true;
280 if(Abs(diffAngle) >= Pi)
289 if(Sgn(diffAngle) > 0)
291 // Inside/outside changed (e.g. zig zag patterns)
292 at1 = atan2(-ldy, -ldx);
293 at2 = atan2(-rdy, -rdx);
294 diffAngle = at2 - at1;
297 if(Abs(diffAngle) >= Pi)
315 double r = lineWidth;
318 double angle = (i == 0 && !end) ? at1 : at2;
322 double ang = angle - Pi/2 + Pi;
323 c = (float)(cos(ang) * r/2), s = (float)(sin(ang) * r/2);
324 points[startIX] = { p.x + c, p.y + s };
327 for(t = 0; t < capCount; t++)
329 double ang = angle + (i ? -Pi/2 : Pi/2) - t * Pi/(capCount-1);
330 c = (float)(cos(ang) * r/2), s = (float)(sin(ang) * r/2);
331 points[startIX + add + t] = { p.x + c, p.y + s };
335 double ang = angle + Pi/2 + Pi;
336 c = (float)(cos(ang) * r/2), s = (float)(sin(ang) * r/2);
337 points[startIX + 1 + capCount-1] = { p.x + c, p.y + s };
343 double r = lineWidth;
344 double angle = (i == 0 && !end) ? at1 : at2;
345 double ang = angle - Pi/2;
346 c = (float)(cos(ang) * r/2), s = (float)(sin(ang) * r/2);
347 points[startIX] = { p.x - c, p.y - s};
348 points[startIX+1] = { p.x + c, p.y + s };
353 double r = lineWidth * sqrt(2);
354 double angle = (i == 0 && !end) ? at1 : at2;
355 double ang = (i || end) ? (angle - Pi/4) + Pi : (angle + Pi/4);
356 c = (float)(cos(ang) * r/2), s = (float)(sin(ang) * r/2);
357 points[startIX] = { p.x + c, p.y + s};
359 ang = (i || end) ? (angle + Pi/4) + Pi : (angle - Pi/4);
360 c = (float)(cos(ang) * r/2), s = (float)(sin(ang) * r/2);
361 points[startIX+1] = { p.x + c, p.y + s };
368 double r = lineWidth / cos(diffAngle/2);
370 bool diffSigns = Sgn(at1) != Sgn(at2);
372 angle = (double)(at1 + at2) / 2;
373 else if(diffSigns && ldy > 0 && rdy < 0 && !ldx && !rdx)
375 else if(diffSigns && ldy < 0 && rdy > 0 && !ldx && !rdx)
378 angle = (double)(at1 + at2) / 2 + Pi;
382 c = (float)(cos(angle) * r/2), s = (float)(sin(angle) * r/2);
384 points[startIX] = { p.x - c, p.y - s };
392 for(t = 0; t < rCount; t++)
394 double ang = at2 - ((rCount-1-t) * diffAngle / (rCount-1)) + Pi/2;
395 c = (float)(cos(ang) * r/2), s = (float)(sin(ang) * r/2);
396 points[startIX+1+t] = { p.x + c, p.y + s };
400 points[startIX+1] = { p.x + c, p.y + s };
404 r = lineWidth*1.1; // TODO: Handle this properly... 1.1 works around not adding an extra vertex
407 c = (float)(cos(angle) * r/2), s = (float)(sin(angle) * r/2);
408 points[startIX+1] = { p.x - c, p.y - s };
411 for(n = 0; n < (isCap ? capCount : rCount); n++)
415 ix[d++] = (uint16)(startIX+n+1);
421 ix[d++] = (uint16)(startIX+n+1);
424 startIX += (uint16)(isCap ? capCount : rCount) + 1;
438 points = nodes.array;
439 ixCount = tc + closed;
440 ix = new uint16[ixCount];
443 for(i = 0; i < tc; i++)
448 vbo.upload(vboCount*sizeof(Pointf), points);
450 lineIndices.upload(ixCount * sizeof(uint16), ix);
453 if(points != nodes.array)
458 fillIndices.upload(tc * sizeof(uint16), ixFill);
469 vbo.use(vertex, 2, glTypeFloat, 0, null);
474 glimtkColor4f(fillColor.color.r/255.0f, fillColor.color.g/255.0f, fillColor.color.b/255.0f, fillColor.a/255.0f);
475 fillIndices.draw(GLIMTKMode::triangleFan, fillCount, glTypeUnsignedShort, null);
481 glimtkColor4f(lineColor.color.r/255.0f, lineColor.color.g/255.0f, lineColor.color.b/255.0f, lineColor.a/255.0f);
482 lineIndices.draw(lineWidth > 1 ? GLIMTKMode::triangleStrip : GLIMTKMode::lineStrip, lineCount, glTypeUnsignedShort, null);
484 glimtkColor4f(1, 0, 0, 1);
485 // lineIndices.draw(GLIMTKMode::lineStrip, lineCount, glTypeUnsignedShort, null);
490 class BBRectangle : BBPath
499 set { box = value; needUpdate = true; }
504 set { rx = value; needUpdate = true; }
509 set { ry = value; needUpdate = true; }
519 nodes[0] = { box.left, box.top };
520 nodes[1] = { box.left, box.bottom };
521 nodes[2] = { box.right, box.bottom };
522 nodes[3] = { box.right, box.top };
527 float rx = Min(this.rx, box.right - box.left);
528 float ry = Min(this.ry, box.bottom - box.top);
534 for(i = 0; i < 4; i++)
541 case 0: p = { box.left, box.top }; angle = 0; break;
542 case 1: p = { box.left, box.bottom }; angle = 270; break;
543 case 2: p = { box.right, box.bottom }; angle = 180; break;
544 case 3: p = { box.right, box.top }; angle = 90; break;
546 o = { (float)(p.x + cos(angle) * rx - sin(angle) * rx), (float)(p.y + sin(angle) * ry + cos(angle) * ry) };
548 for(t = 0; t < res; t++)
552 (float)(o.x - rx * cos(angle + (res-1-t) * Degrees { 90 } / (res-1))),
553 (float)(o.y - ry * sin(angle + (res-1-t) * Degrees { 90 } / (res-1)))
564 class BBCircle : BBPath
574 property Pointf center
576 set { center = value; needUpdate = true; }
577 get { value = center; }
580 property float radius
582 set { radius = value; needUpdate = true; }
583 get { return radius; }
588 uint count = radius < 3 ? 6 : radius < 20 ? 12 : radius < 50 ? 30 : 100;
592 for(i = 0; i < count; i++)
594 double a = i * 2*Pi / count;
595 nodes[i] = { center.x + (float)cos(a) * radius, center.y + (float)sin(a) * radius };
603 class BBEllipse : BBPath
615 property Pointf center
617 set { center = value; needUpdate = true; }
618 get { value = center; }
621 property float radius
623 set { radius = value; needUpdate = true; }
624 get { return radius; }
629 uint count = radius < 3 ? 6 : radius < 20 ? 12 : radius < 50 ? 30 : 100;
633 for(i = 0; i < count; i++)
635 double a = i * 2*Pi / (count+1);
636 nodes[i] = { center.x + (float)cos(a) * radius * k, center.y + (float)sin(a) * radius };