*/
+#include <cmath>
#include <exception>
#include <algorithm>
using std::min;
using std::max;
-Curve::Curve (Group* parent)
- : Item (parent)
- , PolyItem (parent)
+Curve::Curve (Canvas* c)
+ : PolyItem (c)
+ , n_samples (0)
+ , points_per_segment (16)
+ , curve_fill (None)
{
+}
+
+Curve::Curve (Item* parent)
+ : PolyItem (parent)
+ , n_samples (0)
+ , points_per_segment (16)
+ , curve_fill (None)
+{
+}
+/** When rendering the curve, we will always draw a fixed number of straight
+ * line segments to span the x-axis extent of the curve. More segments:
+ * smoother visual rendering. Less rendering: closer to a visibily poly-line
+ * render.
+ */
+void
+Curve::set_points_per_segment (uint32_t n)
+{
+ /* this only changes our appearance rather than the bounding box, so we
+ just need to schedule a redraw rather than notify the parent of any
+ changes
+ */
+ points_per_segment = n;
+ interpolate ();
+ redraw ();
}
void
{
PolyItem::compute_bounding_box ();
- if (_bounding_box) {
-
- bool have1 = false;
- bool have2 = false;
-
- Rect bbox1;
- Rect bbox2;
-
- for (Points::const_iterator i = first_control_points.begin(); i != first_control_points.end(); ++i) {
- if (have1) {
- bbox1.x0 = min (bbox1.x0, i->x);
- bbox1.y0 = min (bbox1.y0, i->y);
- bbox1.x1 = max (bbox1.x1, i->x);
- bbox1.y1 = max (bbox1.y1, i->y);
- } else {
- bbox1.x0 = bbox1.x1 = i->x;
- bbox1.y0 = bbox1.y1 = i->y;
- have1 = true;
- }
- }
-
- for (Points::const_iterator i = second_control_points.begin(); i != second_control_points.end(); ++i) {
- if (have2) {
- bbox2.x0 = min (bbox2.x0, i->x);
- bbox2.y0 = min (bbox2.y0, i->y);
- bbox2.x1 = max (bbox2.x1, i->x);
- bbox2.y1 = max (bbox2.y1, i->y);
- } else {
- bbox2.x0 = bbox2.x1 = i->x;
- bbox2.y0 = bbox2.y1 = i->y;
- have2 = true;
- }
- }
-
- Rect u = bbox1.extend (bbox2);
- _bounding_box = u.extend (_bounding_box.get());
- }
-
- _bounding_box_dirty = false;
+ /* possibly add extents of any point indicators here if we ever do that */
}
void
Curve::set (Points const& p)
{
PolyItem::set (p);
-
- first_control_points.clear ();
- second_control_points.clear ();
-
- compute_control_points (_points, first_control_points, second_control_points);
+ interpolate ();
}
void
-Curve::render (Rect const & area, Cairo::RefPtr<Cairo::Context> context) const
+Curve::interpolate ()
{
- if (_outline) {
- setup_outline_context (context);
- render_path (area, context);
- context->stroke ();
- }
+ samples.clear ();
+ InterpolatedCurve::interpolate (_points, points_per_segment, CatmullRomCentripetal, false, samples);
+ n_samples = samples.size();
}
-void
-Curve::render_path (Rect const & area, Cairo::RefPtr<Cairo::Context> context) const
-{
- PolyItem::render_curve (area, context, first_control_points, second_control_points);
-}
-
-void
-Curve::compute_control_points (Points const& knots,
- Points& firstControlPoints,
- Points& secondControlPoints)
+void
+Curve::render (Rect const & area, Cairo::RefPtr<Cairo::Context> context) const
{
- Points::size_type n = knots.size() - 1;
-
- if (n < 1) {
+ if (!_outline || _points.size() < 2 || !_bounding_box) {
return;
}
-
- if (n == 1) {
- /* Special case: Bezier curve should be a straight line. */
-
- Duple d;
-
- d.x = (2.0 * knots[0].x + knots[1].x) / 3;
- d.y = (2.0 * knots[0].y + knots[1].y) / 3;
- firstControlPoints.push_back (d);
-
- d.x = 2.0 * firstControlPoints[0].x - knots[0].x;
- d.y = 2.0 * firstControlPoints[0].y - knots[0].y;
- secondControlPoints.push_back (d);
-
- return;
- }
-
- // Calculate first Bezier control points
- // Right hand side vector
-
- std::vector<double> rhs;
-
- rhs.assign (n, 0);
-
- // Set right hand side X values
-
- for (Points::size_type i = 1; i < n - 1; ++i) {
- rhs[i] = 4 * knots[i].x + 2 * knots[i + 1].x;
- }
- rhs[0] = knots[0].x + 2 * knots[1].x;
- rhs[n - 1] = (8 * knots[n - 1].x + knots[n].x) / 2.0;
-
- // Get first control points X-values
- double* x = solve (rhs);
-
- // Set right hand side Y values
- for (Points::size_type i = 1; i < n - 1; ++i) {
- rhs[i] = 4 * knots[i].y + 2 * knots[i + 1].y;
- }
- rhs[0] = knots[0].y + 2 * knots[1].y;
- rhs[n - 1] = (8 * knots[n - 1].y + knots[n].y) / 2.0;
-
- // Get first control points Y-values
- double* y = solve (rhs);
-
- for (Points::size_type i = 0; i < n; ++i) {
-
- firstControlPoints.push_back (Duple (x[i], y[i]));
-
- if (i < n - 1) {
- secondControlPoints.push_back (Duple (2 * knots [i + 1].x - x[i + 1],
- 2 * knots[i + 1].y - y[i + 1]));
- } else {
- secondControlPoints.push_back (Duple ((knots [n].x + x[n - 1]) / 2,
- (knots[n].y + y[n - 1]) / 2));
+
+ Rect self = item_to_window (_bounding_box);
+ Rect d = self.intersection (area);
+ assert (d);
+ Rect draw = d;
+
+ /* Our approach is to always draw n_segments across our total size.
+ *
+ * This is very inefficient if we are asked to only draw a small
+ * section of the curve. For now we rely on cairo clipping to help
+ * with this.
+ */
+
+
+ setup_outline_context (context);
+
+ if (_points.size() == 2) {
+
+ /* straight line */
+
+ Duple window_space;
+
+ window_space = item_to_window (_points.front());
+ context->move_to (window_space.x, window_space.y);
+ window_space = item_to_window (_points.back());
+ context->line_to (window_space.x, window_space.y);
+
+
+ switch (curve_fill) {
+ case None:
+ context->stroke();
+ break;
+ case Inside:
+ context->stroke_preserve ();
+ window_space = item_to_window (Duple(_points.back().x, draw.height()));
+ context->line_to (window_space.x, window_space.y);
+ window_space = item_to_window (Duple(_points.front().x, draw.height()));
+ context->line_to (window_space.x, window_space.y);
+ context->close_path();
+ setup_fill_context(context);
+ context->fill ();
+ break;
+ case Outside:
+ context->stroke_preserve ();
+ window_space = item_to_window (Duple(_points.back().x, 0.0));
+ context->line_to (window_space.x, window_space.y);
+ window_space = item_to_window (Duple(_points.front().x, 0.0));
+ context->line_to (window_space.x, window_space.y);
+ context->close_path();
+ setup_fill_context(context);
+ context->fill ();
+ break;
}
- }
-
- delete [] x;
- delete [] y;
-}
-/** Solves a tridiagonal system for one of coordinates (x or y)
- * of first Bezier control points.
- */
+ } else {
-double*
-Curve::solve (std::vector<double> const & rhs)
-{
- std::vector<double>::size_type n = rhs.size();
- double* x = new double[n]; // Solution vector.
- double* tmp = new double[n]; // Temp workspace.
-
- double b = 2.0;
-
- x[0] = rhs[0] / b;
-
- for (std::vector<double>::size_type i = 1; i < n; i++) {
- // Decomposition and forward substitution.
- tmp[i] = 1 / b;
- b = (i < n - 1 ? 4.0 : 3.5) - tmp[i];
- x[i] = (rhs[i] - x[i - 1]) / b;
- }
-
- for (std::vector<double>::size_type i = 1; i < n; i++) {
- // Backsubstitution
- x[n - i - 1] -= tmp[n - i] * x[n - i];
+ /* curve of at least 3 points */
+
+ /* x-axis limits of the curve, in window space coordinates */
+
+ Duple w1 = item_to_window (Duple (_points.front().x, 0.0));
+ Duple w2 = item_to_window (Duple (_points.back().x, 0.0));
+
+ /* clamp actual draw to area bound by points, rather than our bounding box which is slightly different */
+
+ context->save ();
+ context->rectangle (draw.x0, draw.y0, draw.width(), draw.height());
+ context->clip ();
+
+ /* expand drawing area by several pixels on each side to avoid cairo stroking effects at the boundary.
+ they will still occur, but cairo's clipping will hide them.
+ */
+
+ draw = draw.expand (4.0);
+
+ /* now clip it to the actual points in the curve */
+
+ if (draw.x0 < w1.x) {
+ draw.x0 = w1.x;
+ }
+
+ if (draw.x1 >= w2.x) {
+ draw.x1 = w2.x;
+ }
+
+ /* find left and right-most sample */
+ Duple window_space;
+ Points::size_type left = 0;
+ Points::size_type right = n_samples;
+
+ for (Points::size_type idx = 0; idx < n_samples - 1; ++idx) {
+ left = idx;
+ window_space = item_to_window (Duple (samples[idx].x, 0.0));
+ if (window_space.x >= draw.x0) break;
+ }
+ for (Points::size_type idx = n_samples; idx > left + 1; --idx) {
+ window_space = item_to_window (Duple (samples[idx].x, 0.0));
+ if (window_space.x <= draw.x1) break;
+ right = idx;
+ }
+
+ /* draw line between samples */
+ window_space = item_to_window (Duple (samples[left].x, samples[left].y));
+ context->move_to (window_space.x, window_space.y);
+ for (uint32_t idx = left + 1; idx < right; ++idx) {
+ window_space = item_to_window (Duple (samples[idx].x, samples[idx].y));
+ context->line_to (window_space.x, window_space.y);
+ }
+
+ switch (curve_fill) {
+ case None:
+ context->stroke();
+ break;
+ case Inside:
+ context->stroke_preserve ();
+ window_space = item_to_window (Duple (samples[right-1].x, draw.height()));
+ context->line_to (window_space.x, window_space.y);
+ window_space = item_to_window (Duple (samples[left].x, draw.height()));
+ context->line_to (window_space.x, window_space.y);
+ context->close_path();
+ setup_fill_context(context);
+ context->fill ();
+ break;
+ case Outside:
+ context->stroke_preserve ();
+ window_space = item_to_window (Duple (samples[right-1].x, 0.0));
+ context->line_to (window_space.x, window_space.y);
+ window_space = item_to_window (Duple (samples[left].x, 0.0));
+ context->line_to (window_space.x, window_space.y);
+ context->close_path();
+ setup_fill_context(context);
+ context->fill ();
+ break;
+ }
+ context->restore ();
}
- delete [] tmp;
-
- return x;
+#if 0
+ /* add points */
+ setup_outline_context (context);
+ for (Points::const_iterator p = _points.begin(); p != _points.end(); ++p) {
+ Duple window_space (item_to_window (*p));
+ context->arc (window_space.x, window_space.y, 5.0, 0.0, 2 * M_PI);
+ context->stroke ();
+ }
+#endif
}
bool
Curve::covers (Duple const & pc) const
{
- Duple point = canvas_to_item (pc);
+ Duple point = window_to_item (pc);
- /* XXX Hellaciously expensive ... */
+ /* O(N) N = number of points, and not accurate */
for (Points::const_iterator p = _points.begin(); p != _points.end(); ++p) {