mirror of https://github.com/buggins/dlangui.git
basic bezier curves with sample
This commit is contained in:
parent
1720960923
commit
94161b4924
|
@ -0,0 +1,14 @@
|
|||
.dub
|
||||
docs.json
|
||||
__dummy.html
|
||||
docs/
|
||||
bezier.so
|
||||
bezier.dylib
|
||||
bezier.dll
|
||||
bezier.a
|
||||
bezier.lib
|
||||
bezier-test-*
|
||||
*.exe
|
||||
*.o
|
||||
*.obj
|
||||
*.lst
|
Binary file not shown.
After Width: | Height: | Size: 54 KiB |
|
@ -0,0 +1,32 @@
|
|||
{
|
||||
"name": "bezier",
|
||||
"description": "dlangui bezier curves samples",
|
||||
"license": "Boost",
|
||||
|
||||
"targetPath": "bin",
|
||||
"targetType": "executable",
|
||||
"targetName": "bezier",
|
||||
|
||||
"sourceFiles-windows-x86-dmd": ["$PACKAGE_DIR/../../src/win_app.def"],
|
||||
|
||||
"dependencies": {
|
||||
"dlangui": {"path": "../../"}
|
||||
},
|
||||
"configurations" : [
|
||||
{
|
||||
"name" : "default"
|
||||
},
|
||||
{
|
||||
"name" : "sdl",
|
||||
"subConfigurations" : {
|
||||
"dlangui" : "sdl"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name" : "x11",
|
||||
"subConfigurations" : {
|
||||
"dlangui" : "x11"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,230 @@
|
|||
module app;
|
||||
|
||||
static assert(ENABLE_OPENGL, "All bezier samples in this module using
|
||||
floating point drawing functions which is not supported in minimal config");
|
||||
|
||||
import dlangui;
|
||||
|
||||
import std.algorithm.comparison;
|
||||
|
||||
mixin APP_ENTRY_POINT;
|
||||
|
||||
// helper for scaling relative to average 96dpi FullHD, IDK but maybe a bad idea after all
|
||||
T scaledByDPI(T)(T val) {
|
||||
return val *= (SCREEN_DPI()/cast(T)96);
|
||||
}
|
||||
|
||||
/// Entry point for dlangui based application
|
||||
extern (C) int UIAppMain(string[] args) {
|
||||
// portrait "mode" window
|
||||
Window window = Platform.instance.createWindow("Bezier curves", null, WindowFlag.Resizable, 480.scaledByDPI, 600.scaledByDPI);
|
||||
window.mainWidget = new BezierSamples();
|
||||
window.show();
|
||||
return Platform.instance.enterMessageLoop();
|
||||
}
|
||||
|
||||
class BezierSamples : VerticalLayout {
|
||||
|
||||
this() {
|
||||
this(null);
|
||||
}
|
||||
this(string id) {
|
||||
super(id);
|
||||
addChild(new CubicTraceSample);
|
||||
addChild(new FlattenCubicSample);
|
||||
addChild(new ColoredCubicTraceSample);
|
||||
addChild(new FlattenCubicGuidesSample);
|
||||
addChild(new FlattenQuadraticSample);
|
||||
}
|
||||
|
||||
override bool animating() { return true; }
|
||||
}
|
||||
|
||||
|
||||
abstract class SampleCanvas : CanvasWidget {
|
||||
|
||||
dstring _sampleName = "Bezier sample";
|
||||
static immutable vec2[] _controlPointsDefaultRatios = [vec2(0,0.2), vec2(0.2,0.2), vec2(0.8,0.8), vec2(1,0.8)];
|
||||
static immutable vec2[] _controlPointsQuadratic = [vec2(0.2,0.8), vec2(0.7,0.4), vec2(0.3,0.2)];
|
||||
|
||||
this() {
|
||||
fillHorizontal();
|
||||
auto p = 5.scaledByDPI;
|
||||
margins(Rect(p, p, p, p));
|
||||
p = 15.scaledByDPI;
|
||||
padding(Rect(p, p, p, p));
|
||||
minHeight = 250.scaledByDPI;
|
||||
}
|
||||
|
||||
dstring sampleName() { return _sampleName; }
|
||||
|
||||
override protected void measuredContent(int parentWidth, int parentHeight, int contentWidth, int contentHeight) {
|
||||
_measuredWidth = max(minHeight, contentWidth);
|
||||
_measuredHeight = minHeight;
|
||||
}
|
||||
|
||||
protected void drawText(DrawBuf buf, Rect rc, dstring text) {
|
||||
FontRef font = font();
|
||||
Point sz = font.textSize(text);
|
||||
applyAlign(rc, sz, Align.HCenter, Align.Bottom );
|
||||
font.drawText(buf, rc.left, rc.top, text, textColor, 4, 0, textFlags);
|
||||
}
|
||||
|
||||
protected void calcRectSize(const vec2[] controlPoints, vec2[] result) {
|
||||
assert(result.length >= controlPoints.length);
|
||||
auto r = _pos;
|
||||
applyMargins(r);
|
||||
applyPadding(r);
|
||||
vec2 pos = vec2(r.left, r.top);
|
||||
vec2 size = vec2(r.width, r.height);
|
||||
result[] = controlPoints[]; // copy points
|
||||
result[0] = result[0].mul(size) + pos;
|
||||
result[1] = result[1].mul(size) + pos;
|
||||
result[2] = result[2].mul(size) + pos;
|
||||
if(controlPoints.length > 3)
|
||||
result[3] = result[3].mul(size) + pos;
|
||||
}
|
||||
|
||||
// override to draw
|
||||
override void doDraw(DrawBuf buf, Rect rc) {
|
||||
}
|
||||
}
|
||||
|
||||
class CubicTraceSample : SampleCanvas {
|
||||
this() {
|
||||
_sampleName = "Cubic bezier curve drawn with ellipses (slow, high overdraw)";
|
||||
}
|
||||
override void doDraw(DrawBuf buf, Rect rc) {
|
||||
vec2[4] points;
|
||||
calcRectSize(_controlPointsDefaultRatios, points);
|
||||
auto len = (points[0]-points[3]).magnitude;
|
||||
auto interval = 1f/len;
|
||||
auto step = interval;
|
||||
// evaluate normal bezier curve and trace with circles
|
||||
foreach ( i ; 0..len ) {
|
||||
auto b = bezierCubic(points, interval);
|
||||
interval+=step;
|
||||
buf.drawEllipseF(b.x, b.y, 3, 3, 0, Color.black, Color.black);
|
||||
}
|
||||
drawText(buf,rc, sampleName());
|
||||
}
|
||||
}
|
||||
|
||||
class FlattenCubicSample : SampleCanvas {
|
||||
this() {
|
||||
_sampleName = "Flattened cubic bezier curve drawn with lines (fast)";
|
||||
}
|
||||
override void doDraw(DrawBuf buf, Rect rc) {
|
||||
vec2[4] points;
|
||||
calcRectSize(_controlPointsDefaultRatios, points);
|
||||
enum segments = 10;
|
||||
auto lines = flattenBezierCubic(points, segments);
|
||||
buf.polyLineF(lines, 3f.scaledByDPI, Color.black);
|
||||
drawText(buf,rc, sampleName());
|
||||
}
|
||||
}
|
||||
|
||||
class ColoredCubicTraceSample : SampleCanvas {
|
||||
this() {
|
||||
_sampleName = "Simple colored cubic bezier curve drawn with ellipsises";
|
||||
}
|
||||
override void doDraw(DrawBuf buf, Rect rc) {
|
||||
vec2[4] points;
|
||||
calcRectSize(_controlPointsDefaultRatios, points);
|
||||
auto len = (points[0]-points[3]).magnitude;
|
||||
auto interval = 1/len;
|
||||
auto step = interval;
|
||||
// evaluate normal bezier curve and trace with circles
|
||||
foreach ( i ; 0..len ) {
|
||||
auto b = bezierCubic(points, interval);
|
||||
interval+=step;
|
||||
buf.drawEllipseF(b.x, b.y, 3, 3, 0, COLOR_TRANSPARENT, lerpColor01(Color.blue, Color.red, interval));
|
||||
}
|
||||
drawText(buf,rc, sampleName());
|
||||
}
|
||||
|
||||
// clamp to [0,1] and lerp color
|
||||
static uint lerpColor01(uint a, uint b, float ratio) {
|
||||
ratio = clamp(ratio, 0, 1);
|
||||
return blendARGB(b, a, cast(uint)(ratio * 255));
|
||||
}
|
||||
}
|
||||
|
||||
class FlattenCubicGuidesSample : SampleCanvas {
|
||||
this() {
|
||||
_sampleName = "Flattened cubic bezier curve with direction and normal vectors";
|
||||
}
|
||||
override void doDraw(DrawBuf buf, Rect rc) {
|
||||
vec2[4] points;
|
||||
calcRectSize(_controlPointsDefaultRatios, points);
|
||||
enum segmentsCount = 10;
|
||||
auto lines = flattenBezierCubic(points, segmentsCount);
|
||||
drawCubicControlsGuides(buf, points);
|
||||
buf.polyLineF(lines, 3f.scaledByDPI, Color.black);
|
||||
drawCubicSegmentsNormDir(buf, points, segmentsCount, lines, true, true);
|
||||
drawText(buf,rc, sampleName());
|
||||
}
|
||||
}
|
||||
|
||||
class FlattenQuadraticSample : SampleCanvas {
|
||||
this() {
|
||||
_sampleName = "Flattened quadratic bezier curve";
|
||||
}
|
||||
override void doDraw(DrawBuf buf, Rect rc) {
|
||||
vec2[3] points;
|
||||
calcRectSize(_controlPointsQuadratic, points);
|
||||
|
||||
// guide lines
|
||||
buf.drawLineF(points[0], points[1], 1, Color.dark_gray);
|
||||
buf.drawLineF(points[1], points[2], 1, Color.dark_gray);
|
||||
|
||||
|
||||
enum segmentCount = 10;
|
||||
auto lines = flattenBezierQuadratic(points, segmentCount);
|
||||
buf.polyLineF(lines, 3f.scaledByDPI, Color.black);
|
||||
|
||||
// end points
|
||||
buf.drawEllipseF(points[0].x, points[0].y, 5,5, 1, Color.antique_white, Color.cyan);
|
||||
buf.drawEllipseF(points[2].x, points[2].y, 5,5, 1, Color.antique_white, Color.cyan);
|
||||
buf.drawEllipseF(points[1].x, points[1].y, 3,3, 0, Color.antique_white, Color.cyan);
|
||||
|
||||
// draw the direction & normal vectors
|
||||
auto segStep = 1f/segmentCount;
|
||||
foreach(i; 0..segmentCount) {
|
||||
auto dir = bezierQuadraticDirection(points, i*segStep);
|
||||
auto norm = dir.rotated90ccw;
|
||||
auto point = lines[i];
|
||||
buf.drawLineF(point, point + (dir * 15f), 3.scaledByDPI, Color.yellow );
|
||||
buf.drawLineF(point, point + (norm * 15f), 3.scaledByDPI, Color.red );
|
||||
}
|
||||
|
||||
drawText(buf,rc, sampleName());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
void drawCubicControlsGuides(DrawBuf buf, vec2[] controls) {
|
||||
buf.drawLineF(controls[0], controls[1], 1, Color.black);
|
||||
buf.drawLineF(controls[2], controls[3], 1, Color.black);
|
||||
buf.drawEllipseF(controls[0].x, controls[0].y, 5,5, 1, Color.antique_white, Color.cyan);
|
||||
buf.drawEllipseF(controls[3].x, controls[3].y, 5,5, 1, Color.antique_white, Color.cyan);
|
||||
buf.drawEllipseF(controls[1].x, controls[1].y, 3,3, 0, Color.antique_white, Color.cyan);
|
||||
buf.drawEllipseF(controls[2].x, controls[2].y, 3,3, 0, Color.antique_white, Color.cyan);
|
||||
}
|
||||
|
||||
void drawCubicSegmentsNormDir(DrawBuf buf, vec2[] controls, int segmentCount, vec2[] segments, bool direction, bool normals) {
|
||||
if ( !direction && !normals && segments.length < 2)
|
||||
return;
|
||||
// draw the direction & normal vectors
|
||||
auto segStep = 1f/segmentCount;
|
||||
foreach(i; 0..segmentCount) {
|
||||
auto dir = bezierCubicDirection(controls, i*segStep);
|
||||
auto norm = dir.rotated90ccw;
|
||||
auto point = segments[i];
|
||||
if ( direction )
|
||||
buf.drawLineF(point, point + (dir * 15f), 3.scaledByDPI, Color.yellow );
|
||||
if ( normals )
|
||||
buf.drawLineF(point, point + (norm * 15f), 3.scaledByDPI, Color.red );
|
||||
}
|
||||
}
|
|
@ -1740,3 +1740,143 @@ vec3 triangleNormal(float[3] p1, float[3] p2, float[3] p3) {
|
|||
|
||||
/// Alias for 2d float point
|
||||
alias PointF = vec2;
|
||||
|
||||
|
||||
|
||||
|
||||
// this form can be used within shaders
|
||||
/// cubic bezier curve
|
||||
PointF bezierCubic(const PointF[] cp, float t) pure @nogc @safe
|
||||
in { assert(cp.length > 3); } do
|
||||
{
|
||||
// control points
|
||||
auto p0 = cp[0];
|
||||
auto p1 = cp[1];
|
||||
auto p2 = cp[2];
|
||||
auto p3 = cp[3];
|
||||
|
||||
float u1 = (1.0 - t);
|
||||
float u2 = t * t;
|
||||
// the polynomials
|
||||
float b3 = u2 * t;
|
||||
float b2 = 3.0 * u2 * u1;
|
||||
float b1 = 3.0 * t * u1 * u1;
|
||||
float b0 = u1 * u1 * u1;
|
||||
// cubic bezier interpolation
|
||||
PointF p = p0 * b0 + p1 * b1 + p2 * b2 + p3 * b3;
|
||||
return p;
|
||||
}
|
||||
|
||||
/// quadratic bezier curve (not tested)
|
||||
PointF bezierQuadratic(const PointF[] cp, float t) pure @nogc @safe
|
||||
in { assert(cp.length > 2); } do
|
||||
{
|
||||
auto p0 = cp[0];
|
||||
auto p1 = cp[1];
|
||||
auto p2 = cp[2];
|
||||
|
||||
float u1 = (1.0 - t);
|
||||
float u2 = u1 * u1;
|
||||
|
||||
float b2 = t * t;
|
||||
float b1 = 2.0 * u1 * t;
|
||||
float b0 = u2;
|
||||
|
||||
PointF p = p0 * b0 + p1 * b1 + p2 * b2;
|
||||
return p;
|
||||
}
|
||||
|
||||
/// cubic bezier (first) derivative
|
||||
PointF bezierCubicDerivative(const PointF[] cp, float t) pure @nogc @safe
|
||||
in { assert(cp.length > 3); } do
|
||||
{
|
||||
auto p0 = cp[0];
|
||||
auto p1 = cp[1];
|
||||
auto p2 = cp[2];
|
||||
auto p3 = cp[3];
|
||||
|
||||
float u1 = (1.0 - t);
|
||||
float u2 = t * t;
|
||||
float u3 = 6*(u1)*t;
|
||||
float d0 = 3 * u1 * u1;
|
||||
// -3*P0*(1-t)^2 + P1*(3*(1-t)^2 - 6*(1-t)*t) + P2*(6*(1-t)*t - 3*t^2) + 3*P3*t^2
|
||||
PointF d = p0*(-d0) + p1*(d0 - u3) + p2*(u3 - 3*u2) + (p3*3)*u2;
|
||||
return d;
|
||||
}
|
||||
|
||||
/// quadratic bezier (first) derivative
|
||||
PointF bezierQuadraticDerivative(const PointF[] cp, float t) pure @nogc @safe
|
||||
in { assert(cp.length > 2); } do
|
||||
{
|
||||
auto p0 = cp[0];
|
||||
auto p1 = cp[1];
|
||||
auto p2 = cp[2];
|
||||
|
||||
float u1 = (1.0 - t);
|
||||
// -2*(1-t)*(p1-p0) + 2*t*(p2-p1);
|
||||
PointF d = (p0-p1)*-2*u1 + (p2-p1)*2*t;
|
||||
return d;
|
||||
}
|
||||
|
||||
// can't be pure due to normalize & vec2 ctor
|
||||
/// evaluates cubic bezier direction(tangent) at point t
|
||||
PointF bezierCubicDirection(const PointF[] cp, float t) {
|
||||
auto d = bezierCubicDerivative(cp,t);
|
||||
d.normalize();
|
||||
return PointF(tan(d.x), tan(d.y));
|
||||
}
|
||||
|
||||
/// evaluates quadratic bezier direction(tangent) at point t
|
||||
PointF bezierQuadraticDirection(const PointF[] cp, float t) {
|
||||
auto d = bezierQuadraticDerivative(cp,t);
|
||||
d.normalize();
|
||||
return PointF(tan(d.x), tan(d.y));
|
||||
}
|
||||
|
||||
/// templated version of bezier flatten curve function, allocates temporary buffer
|
||||
PointF[] flattenBezier(alias BezierFunc)(const PointF[] cp, int segmentCountInclusive)
|
||||
if (is(typeof(BezierFunc)==function))
|
||||
{
|
||||
if (segmentCountInclusive < 2)
|
||||
return PointF[].init;
|
||||
PointF[] coords = new PointF[segmentCountInclusive+1];
|
||||
flattenBezier!BezierFunc(cp, segmentCountInclusive, coords);
|
||||
return coords;
|
||||
}
|
||||
|
||||
/// flatten bezier curve function, writes to provided buffer instead of allocation
|
||||
void flattenBezier(alias BezierFunc)(const PointF[] cp, int segmentCountInclusive, PointF[] outSegments)
|
||||
if (is(typeof(BezierFunc)==function))
|
||||
{
|
||||
if (segmentCountInclusive < 2)
|
||||
return;
|
||||
float step = 1f/segmentCountInclusive;
|
||||
outSegments[0] = BezierFunc(cp, 0);
|
||||
foreach(i; 1..segmentCountInclusive) {
|
||||
outSegments[i] = BezierFunc(cp, i*step);
|
||||
}
|
||||
outSegments[segmentCountInclusive] = BezierFunc(cp, 1f);
|
||||
}
|
||||
|
||||
|
||||
/// flattens cubic bezier curve, returns PointF[segmentCount+1] array or empty array if <1 segments
|
||||
PointF[] flattenBezierCubic(const PointF[] cp, int segmentCount) {
|
||||
return flattenBezier!bezierCubic(cp,segmentCount);
|
||||
}
|
||||
|
||||
/// flattens quadratic bezier curve, returns PointF[segmentCount+1] array or empty array if <1 segments
|
||||
PointF[] flattenBezierQuadratic(const PointF[] cp, int segmentCount) {
|
||||
return flattenBezier!bezierQuadratic(cp, segmentCount);
|
||||
}
|
||||
|
||||
/// calculates normal vector at point t using direction
|
||||
PointF bezierCubicNormal(const PointF[] cp, float t) {
|
||||
auto d = bezierCubicDirection(cp, t);
|
||||
return d.rotated90ccw;
|
||||
}
|
||||
|
||||
/// calculates normal vector at point t using direction
|
||||
PointF bezierQuadraticNormal(const PointF[] cp, float t) {
|
||||
auto d = bezierQuadraticDerivative(cp, t);
|
||||
return d.rotated90ccw;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue