quchen / generative-art Goto Github PK
View Code? Open in Web Editor NEWI wanted to make a nicer sticker for Munihac, then things got out of hand.
License: BSD 3-Clause "New" or "Revised" License
I wanted to make a nicer sticker for Munihac, then things got out of hand.
License: BSD 3-Clause "New" or "Revised" License
The reason I checked in so many pictures is that they are readily available online. However, thanks to Github Actions, they are now built and deployed to Github Pages automatically 🎉 (link)
We should thus think about removing all the auto-generted pictures from the repo and have CI/local builds handle everything.
The Caseljeau article handles more corner cases than the current implementation in Geometry.Bezier
, e.g. non-differentiable bends.
The missing parts are:
C++ code from Antigrain (source), in case the archive page goes offline:
//----------------------------------------------------------------------------
// Anti-Grain Geometry (AGG) - Version 2.5
// A high quality rendering engine for C++
// Copyright (C) 2002-2006 Maxim Shemanarev
// Contact: [email protected]
// [email protected]
// http://antigrain.com
//
// AGG is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// AGG is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with AGG; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
// MA 02110-1301, USA.
//----------------------------------------------------------------------------
#include <math.h>
#include "agg_curves.h"
#include "agg_math.h"
namespace agg
{
//------------------------------------------------------------------------
const double curve_distance_epsilon = 1e-30;
const double curve_collinearity_epsilon = 1e-30;
const double curve_angle_tolerance_epsilon = 0.01;
enum curve_recursion_limit_e { curve_recursion_limit = 32 };
//------------------------------------------------------------------------
void curve3_inc::approximation_scale(double s)
{
m_scale = s;
}
//------------------------------------------------------------------------
double curve3_inc::approximation_scale() const
{
return m_scale;
}
//------------------------------------------------------------------------
void curve3_inc::init(double x1, double y1,
double x2, double y2,
double x3, double y3)
{
m_start_x = x1;
m_start_y = y1;
m_end_x = x3;
m_end_y = y3;
double dx1 = x2 - x1;
double dy1 = y2 - y1;
double dx2 = x3 - x2;
double dy2 = y3 - y2;
double len = sqrt(dx1 * dx1 + dy1 * dy1) + sqrt(dx2 * dx2 + dy2 * dy2);
m_num_steps = uround(len * 0.25 * m_scale);
if(m_num_steps < 4)
{
m_num_steps = 4;
}
double subdivide_step = 1.0 / m_num_steps;
double subdivide_step2 = subdivide_step * subdivide_step;
double tmpx = (x1 - x2 * 2.0 + x3) * subdivide_step2;
double tmpy = (y1 - y2 * 2.0 + y3) * subdivide_step2;
m_saved_fx = m_fx = x1;
m_saved_fy = m_fy = y1;
m_saved_dfx = m_dfx = tmpx + (x2 - x1) * (2.0 * subdivide_step);
m_saved_dfy = m_dfy = tmpy + (y2 - y1) * (2.0 * subdivide_step);
m_ddfx = tmpx * 2.0;
m_ddfy = tmpy * 2.0;
m_step = m_num_steps;
}
//------------------------------------------------------------------------
void curve3_inc::rewind(unsigned)
{
if(m_num_steps == 0)
{
m_step = -1;
return;
}
m_step = m_num_steps;
m_fx = m_saved_fx;
m_fy = m_saved_fy;
m_dfx = m_saved_dfx;
m_dfy = m_saved_dfy;
}
//------------------------------------------------------------------------
unsigned curve3_inc::vertex(double* x, double* y)
{
if(m_step < 0) return path_cmd_stop;
if(m_step == m_num_steps)
{
*x = m_start_x;
*y = m_start_y;
--m_step;
return path_cmd_move_to;
}
if(m_step == 0)
{
*x = m_end_x;
*y = m_end_y;
--m_step;
return path_cmd_line_to;
}
m_fx += m_dfx;
m_fy += m_dfy;
m_dfx += m_ddfx;
m_dfy += m_ddfy;
*x = m_fx;
*y = m_fy;
--m_step;
return path_cmd_line_to;
}
//------------------------------------------------------------------------
void curve3_div::init(double x1, double y1,
double x2, double y2,
double x3, double y3)
{
m_points.remove_all();
m_distance_tolerance_square = 0.5 / m_approximation_scale;
m_distance_tolerance_square *= m_distance_tolerance_square;
bezier(x1, y1, x2, y2, x3, y3);
m_count = 0;
}
//------------------------------------------------------------------------
void curve3_div::recursive_bezier(double x1, double y1,
double x2, double y2,
double x3, double y3,
unsigned level)
{
if(level > curve_recursion_limit)
{
return;
}
// Calculate all the mid-points of the line segments
//----------------------
double x12 = (x1 + x2) / 2;
double y12 = (y1 + y2) / 2;
double x23 = (x2 + x3) / 2;
double y23 = (y2 + y3) / 2;
double x123 = (x12 + x23) / 2;
double y123 = (y12 + y23) / 2;
double dx = x3-x1;
double dy = y3-y1;
double d = fabs(((x2 - x3) * dy - (y2 - y3) * dx));
double da;
if(d > curve_collinearity_epsilon)
{
// Regular case
//-----------------
if(d * d <= m_distance_tolerance_square * (dx*dx + dy*dy))
{
// If the curvature doesn't exceed the distance_tolerance value
// we tend to finish subdivisions.
//----------------------
if(m_angle_tolerance < curve_angle_tolerance_epsilon)
{
m_points.add(point_d(x123, y123));
return;
}
// Angle & Cusp Condition
//----------------------
da = fabs(atan2(y3 - y2, x3 - x2) - atan2(y2 - y1, x2 - x1));
if(da >= pi) da = 2*pi - da;
if(da < m_angle_tolerance)
{
// Finally we can stop the recursion
//----------------------
m_points.add(point_d(x123, y123));
return;
}
}
}
else
{
// Collinear case
//------------------
da = dx*dx + dy*dy;
if(da == 0)
{
d = calc_sq_distance(x1, y1, x2, y2);
}
else
{
d = ((x2 - x1)*dx + (y2 - y1)*dy) / da;
if(d > 0 && d < 1)
{
// Simple collinear case, 1---2---3
// We can leave just two endpoints
return;
}
if(d <= 0) d = calc_sq_distance(x2, y2, x1, y1);
else if(d >= 1) d = calc_sq_distance(x2, y2, x3, y3);
else d = calc_sq_distance(x2, y2, x1 + d*dx, y1 + d*dy);
}
if(d < m_distance_tolerance_square)
{
m_points.add(point_d(x2, y2));
return;
}
}
// Continue subdivision
//----------------------
recursive_bezier(x1, y1, x12, y12, x123, y123, level + 1);
recursive_bezier(x123, y123, x23, y23, x3, y3, level + 1);
}
//------------------------------------------------------------------------
void curve3_div::bezier(double x1, double y1,
double x2, double y2,
double x3, double y3)
{
m_points.add(point_d(x1, y1));
recursive_bezier(x1, y1, x2, y2, x3, y3, 0);
m_points.add(point_d(x3, y3));
}
//------------------------------------------------------------------------
void curve4_inc::approximation_scale(double s)
{
m_scale = s;
}
//------------------------------------------------------------------------
double curve4_inc::approximation_scale() const
{
return m_scale;
}
//------------------------------------------------------------------------
static double MSC60_fix_ICE(double v) { return v; }
//------------------------------------------------------------------------
void curve4_inc::init(double x1, double y1,
double x2, double y2,
double x3, double y3,
double x4, double y4)
{
m_start_x = x1;
m_start_y = y1;
m_end_x = x4;
m_end_y = y4;
double dx1 = x2 - x1;
double dy1 = y2 - y1;
double dx2 = x3 - x2;
double dy2 = y3 - y2;
double dx3 = x4 - x3;
double dy3 = y4 - y3;
double len = (sqrt(dx1 * dx1 + dy1 * dy1) +
sqrt(dx2 * dx2 + dy2 * dy2) +
sqrt(dx3 * dx3 + dy3 * dy3)) * 0.25 * m_scale;
#if defined(_MSC_VER) && _MSC_VER <= 1200
m_num_steps = uround(MSC60_fix_ICE(len));
#else
m_num_steps = uround(len);
#endif
if(m_num_steps < 4)
{
m_num_steps = 4;
}
double subdivide_step = 1.0 / m_num_steps;
double subdivide_step2 = subdivide_step * subdivide_step;
double subdivide_step3 = subdivide_step * subdivide_step * subdivide_step;
double pre1 = 3.0 * subdivide_step;
double pre2 = 3.0 * subdivide_step2;
double pre4 = 6.0 * subdivide_step2;
double pre5 = 6.0 * subdivide_step3;
double tmp1x = x1 - x2 * 2.0 + x3;
double tmp1y = y1 - y2 * 2.0 + y3;
double tmp2x = (x2 - x3) * 3.0 - x1 + x4;
double tmp2y = (y2 - y3) * 3.0 - y1 + y4;
m_saved_fx = m_fx = x1;
m_saved_fy = m_fy = y1;
m_saved_dfx = m_dfx = (x2 - x1) * pre1 + tmp1x * pre2 + tmp2x * subdivide_step3;
m_saved_dfy = m_dfy = (y2 - y1) * pre1 + tmp1y * pre2 + tmp2y * subdivide_step3;
m_saved_ddfx = m_ddfx = tmp1x * pre4 + tmp2x * pre5;
m_saved_ddfy = m_ddfy = tmp1y * pre4 + tmp2y * pre5;
m_dddfx = tmp2x * pre5;
m_dddfy = tmp2y * pre5;
m_step = m_num_steps;
}
//------------------------------------------------------------------------
void curve4_inc::rewind(unsigned)
{
if(m_num_steps == 0)
{
m_step = -1;
return;
}
m_step = m_num_steps;
m_fx = m_saved_fx;
m_fy = m_saved_fy;
m_dfx = m_saved_dfx;
m_dfy = m_saved_dfy;
m_ddfx = m_saved_ddfx;
m_ddfy = m_saved_ddfy;
}
//------------------------------------------------------------------------
unsigned curve4_inc::vertex(double* x, double* y)
{
if(m_step < 0) return path_cmd_stop;
if(m_step == m_num_steps)
{
*x = m_start_x;
*y = m_start_y;
--m_step;
return path_cmd_move_to;
}
if(m_step == 0)
{
*x = m_end_x;
*y = m_end_y;
--m_step;
return path_cmd_line_to;
}
m_fx += m_dfx;
m_fy += m_dfy;
m_dfx += m_ddfx;
m_dfy += m_ddfy;
m_ddfx += m_dddfx;
m_ddfy += m_dddfy;
*x = m_fx;
*y = m_fy;
--m_step;
return path_cmd_line_to;
}
//------------------------------------------------------------------------
void curve4_div::init(double x1, double y1,
double x2, double y2,
double x3, double y3,
double x4, double y4)
{
m_points.remove_all();
m_distance_tolerance_square = 0.5 / m_approximation_scale;
m_distance_tolerance_square *= m_distance_tolerance_square;
bezier(x1, y1, x2, y2, x3, y3, x4, y4);
m_count = 0;
}
//------------------------------------------------------------------------
void curve4_div::recursive_bezier(double x1, double y1,
double x2, double y2,
double x3, double y3,
double x4, double y4,
unsigned level)
{
if(level > curve_recursion_limit)
{
return;
}
// Calculate all the mid-points of the line segments
//----------------------
double x12 = (x1 + x2) / 2;
double y12 = (y1 + y2) / 2;
double x23 = (x2 + x3) / 2;
double y23 = (y2 + y3) / 2;
double x34 = (x3 + x4) / 2;
double y34 = (y3 + y4) / 2;
double x123 = (x12 + x23) / 2;
double y123 = (y12 + y23) / 2;
double x234 = (x23 + x34) / 2;
double y234 = (y23 + y34) / 2;
double x1234 = (x123 + x234) / 2;
double y1234 = (y123 + y234) / 2;
// Try to approximate the full cubic curve by a single straight line
//------------------
double dx = x4-x1;
double dy = y4-y1;
double d2 = fabs(((x2 - x4) * dy - (y2 - y4) * dx));
double d3 = fabs(((x3 - x4) * dy - (y3 - y4) * dx));
double da1, da2, k;
switch((int(d2 > curve_collinearity_epsilon) << 1) +
int(d3 > curve_collinearity_epsilon))
{
case 0:
// All collinear OR p1==p4
//----------------------
k = dx*dx + dy*dy;
if(k == 0)
{
d2 = calc_sq_distance(x1, y1, x2, y2);
d3 = calc_sq_distance(x4, y4, x3, y3);
}
else
{
k = 1 / k;
da1 = x2 - x1;
da2 = y2 - y1;
d2 = k * (da1*dx + da2*dy);
da1 = x3 - x1;
da2 = y3 - y1;
d3 = k * (da1*dx + da2*dy);
if(d2 > 0 && d2 < 1 && d3 > 0 && d3 < 1)
{
// Simple collinear case, 1---2---3---4
// We can leave just two endpoints
return;
}
if(d2 <= 0) d2 = calc_sq_distance(x2, y2, x1, y1);
else if(d2 >= 1) d2 = calc_sq_distance(x2, y2, x4, y4);
else d2 = calc_sq_distance(x2, y2, x1 + d2*dx, y1 + d2*dy);
if(d3 <= 0) d3 = calc_sq_distance(x3, y3, x1, y1);
else if(d3 >= 1) d3 = calc_sq_distance(x3, y3, x4, y4);
else d3 = calc_sq_distance(x3, y3, x1 + d3*dx, y1 + d3*dy);
}
if(d2 > d3)
{
if(d2 < m_distance_tolerance_square)
{
m_points.add(point_d(x2, y2));
return;
}
}
else
{
if(d3 < m_distance_tolerance_square)
{
m_points.add(point_d(x3, y3));
return;
}
}
break;
case 1:
// p1,p2,p4 are collinear, p3 is significant
//----------------------
if(d3 * d3 <= m_distance_tolerance_square * (dx*dx + dy*dy))
{
if(m_angle_tolerance < curve_angle_tolerance_epsilon)
{
m_points.add(point_d(x23, y23));
return;
}
// Angle Condition
//----------------------
da1 = fabs(atan2(y4 - y3, x4 - x3) - atan2(y3 - y2, x3 - x2));
if(da1 >= pi) da1 = 2*pi - da1;
if(da1 < m_angle_tolerance)
{
m_points.add(point_d(x2, y2));
m_points.add(point_d(x3, y3));
return;
}
if(m_cusp_limit != 0.0)
{
if(da1 > m_cusp_limit)
{
m_points.add(point_d(x3, y3));
return;
}
}
}
break;
case 2:
// p1,p3,p4 are collinear, p2 is significant
//----------------------
if(d2 * d2 <= m_distance_tolerance_square * (dx*dx + dy*dy))
{
if(m_angle_tolerance < curve_angle_tolerance_epsilon)
{
m_points.add(point_d(x23, y23));
return;
}
// Angle Condition
//----------------------
da1 = fabs(atan2(y3 - y2, x3 - x2) - atan2(y2 - y1, x2 - x1));
if(da1 >= pi) da1 = 2*pi - da1;
if(da1 < m_angle_tolerance)
{
m_points.add(point_d(x2, y2));
m_points.add(point_d(x3, y3));
return;
}
if(m_cusp_limit != 0.0)
{
if(da1 > m_cusp_limit)
{
m_points.add(point_d(x2, y2));
return;
}
}
}
break;
case 3:
// Regular case
//-----------------
if((d2 + d3)*(d2 + d3) <= m_distance_tolerance_square * (dx*dx + dy*dy))
{
// If the curvature doesn't exceed the distance_tolerance value
// we tend to finish subdivisions.
//----------------------
if(m_angle_tolerance < curve_angle_tolerance_epsilon)
{
m_points.add(point_d(x23, y23));
return;
}
// Angle & Cusp Condition
//----------------------
k = atan2(y3 - y2, x3 - x2);
da1 = fabs(k - atan2(y2 - y1, x2 - x1));
da2 = fabs(atan2(y4 - y3, x4 - x3) - k);
if(da1 >= pi) da1 = 2*pi - da1;
if(da2 >= pi) da2 = 2*pi - da2;
if(da1 + da2 < m_angle_tolerance)
{
// Finally we can stop the recursion
//----------------------
m_points.add(point_d(x23, y23));
return;
}
if(m_cusp_limit != 0.0)
{
if(da1 > m_cusp_limit)
{
m_points.add(point_d(x2, y2));
return;
}
if(da2 > m_cusp_limit)
{
m_points.add(point_d(x3, y3));
return;
}
}
}
break;
}
// Continue subdivision
//----------------------
recursive_bezier(x1, y1, x12, y12, x123, y123, x1234, y1234, level + 1);
recursive_bezier(x1234, y1234, x234, y234, x34, y34, x4, y4, level + 1);
}
//------------------------------------------------------------------------
void curve4_div::bezier(double x1, double y1,
double x2, double y2,
double x3, double y3,
double x4, double y4)
{
m_points.add(point_d(x1, y1));
recursive_bezier(x1, y1, x2, y2, x3, y3, x4, y4, 0);
m_points.add(point_d(x4, y4));
}
}
The sentence
Outdated, wrong: "Simply using bezierSmoothen will yield a sharp bend at the line’s origin"
is wrong and outdated. Remove it.
Since a quadratic Bezier curve can be expressed as a cubic one, we can simply parse quadratic curves (SVG: qQtT) to cubic ones. (The lib currently crashes with an error message for qQtT.)
c1d4739 broke the tests for the arc bounding boxes of the pen plotter.
Being polymorphic over Sequential yields ambiguities etc. The price of this is a bit high compared to just writing e.g. Plotting Vector
and Plotting [a]
by hand.
Minimize pen hovering globally.
Posts by Oleg about optimization might be a good entry point:
Doctest output can be made nicer by using Doctest’s ...
feature, so that instead of
>>> S.fromList (reverse [1..10])
S.fromList [1,2,3,4,5,6,7,8,9,10]
we can omit some noise and write
>>> S.fromList (reverse [1..10])
... [1,2,3,4,5,6,7,8,9,10]
These just clutter up Haddock. If we need the internals we should modify the lib instead and expose what we need explicitly, or do a fork on a branch for some dirty hacking.
Standardize all functions and documentation to standard math coordinates
Cairo has zero at the top-left, and the documentation often reflects that. For the most part, I simply winged it, and fiddled with the signs long enough so the picture is pretty. I never checked whether clockwise is actually clockwise for example, and even worse: now that we have both a Cairo and a penplotting backend, claims that change with coordinate handedness are wrong for one of the backends.
I think it’s therefore a good idea to check the functions that rotate things for whether their documentation is right, and fixing any sign errors (and un-fixing those sign errors so output of previous pictures does not change).
No reusability and clutters, hack your way around it in a branch if you need it.
In 40861e0:
*** Failed! Falsified (after 2 tests):
NonZeroVec2 (Vec2 (-1.0) 0.0)
NonZeroVec2 (Vec2 0.653308615076723 0.9706462362409549)
LT /= GT
Use --quickcheck-replay=167254 to reproduce.
Use -p '/pseudoAngle/' to rerun this test only.```
Drop your pictures here! Just drag+drop your image into a comment and hit send.
Paint GCode via Cairo, to save a copy+paste trip to e.g. NCViewer.
sketch :: GCode -> Render ()
Naively removing --no-haddock-deps
rebuilds all packages (~7500 lines of log output for the Haddock step). Caching is crucial here.
This doesn’t belong in the lib, because it has no reusability. It’s an artwork and should be treated as such.
With parameters such as variable feedrate, pen hovering height, Z support
The GCode generator is wholly untested. Wat
Show how to plot something basic, and render the preview image in the module header.
Does this work? It should.
SVG’s M directive allows multiple XY pairs as parameters, which corresponds to implicit L elements (spec)
M x y a b c d
= M x y L a b L c d
Much like multiple coordinates after M in #11, the other SVG commands allow multiple arguments, such as L 1 2 3 4
or C 1 2 3 4 5 6 1 2 3 4 5 6
to define multiple segments of a curve without repeating the instruction.
Using scale
, we can shrink polygons by a factor. For getting nice patterns out of e.g. a Delaunay triangulation, it would be cool to shrink all edges of the triangle by a number of pixels, instead of a factor of its distance from some point.
The naive way of painting a circle is by always starting them e.g. on the very left. This requires some unnecessary pen hovering, and for some pens creates a visible »pen down« dot.
Instead, start the circle with minimal pen hovering, depending on the current pen position.
Probably because the first move comes from infinity, because we use it as a nonsense starting value.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.