Page 1 of 1 [ 5 posts ] 

beneficii
Veteran
Veteran

User avatar

Joined: 10 May 2005
Age: 42
Gender: Female
Posts: 7,245

04 Aug 2014, 8:15 pm

I'm making a program using Bezier curves and offsetting to produce birds-eye view of roads due to a long-standing hobby/obsession. I'm working on the offsetting of curves, which doesn't seem supported in Inkscape very well.


_________________
"You have a responsibility to consider all sides of a problem and a responsibility to make a judgment and a responsibility to care for all involved." --Ian Danskin


Kurgan
Veteran
Veteran

User avatar

Joined: 6 Apr 2012
Age: 37
Gender: Male
Posts: 4,132
Location: Scandinavia

04 Aug 2014, 8:39 pm

Are you using C++?


_________________
“He who controls the spice controls the universe.”


beneficii
Veteran
Veteran

User avatar

Joined: 10 May 2005
Age: 42
Gender: Female
Posts: 7,245

04 Aug 2014, 10:26 pm

Kurgan wrote:
Are you using C++?


Yes.


_________________
"You have a responsibility to consider all sides of a problem and a responsibility to make a judgment and a responsibility to care for all involved." --Ian Danskin


beneficii
Veteran
Veteran

User avatar

Joined: 10 May 2005
Age: 42
Gender: Female
Posts: 7,245

04 Aug 2014, 11:17 pm

So something like this (using wxWidgets 3.0.1) for an offset Bezier curve:

Code:
void bezier_curve::offset(const bezier_curve& other, wxDouble off, wxDouble incr) {
   if (other.is_line()) {
      points[0].m_x = other.points[0].m_x + off * std::cos(other.get_mean_angle(0) + pi / 2);
      points[0].m_y = other.points[0].m_y + off * std::sin(other.get_mean_angle(0) + pi / 2);
      points[3].m_x = other.points[3].m_x + (off * incr) * std::cos(other.get_mean_angle(0) + pi / 2);
      points[3].m_y = other.points[3].m_y + (off * incr) * std::sin(other.get_mean_angle(0) + pi / 2);
      points[1] = points[0];
      points[2] = points[3];
   } else {
      points[0].m_x = other.points[0].m_x + off * std::cos(other.get_mean_angle(0) + pi / 2);
      points[0].m_y = other.points[0].m_y + off * std::sin(other.get_mean_angle(0) + pi / 2);
      points[1].m_x = other.points[1].m_x + (off + incr) * std::cos(other.get_mean_angle(1) + pi / 2);
      points[1].m_y = other.points[1].m_y + (off + incr) * std::sin(other.get_mean_angle(1) + pi / 2);
      points[2].m_x = other.points[2].m_x + (off + 2 * incr) * std::cos(other.get_mean_angle(2) + pi / 2);
      points[2].m_y = other.points[2].m_y + (off + 2 * incr) * std::sin(other.get_mean_angle(2) + pi / 2);
      points[3].m_x = other.points[3].m_x + (off + 3 * incr) * std::cos(other.get_mean_angle(3) + pi / 2);
      points[3].m_y = other.points[3].m_y + (off + 3 * incr) * std::sin(other.get_mean_angle(3) + pi / 2);
   }
}

wxPoint2DDouble get_mean(wxPoint2DDouble a, wxPoint2DDouble b) {
   wxPoint2DDouble ret;
   ret.m_x = (a.m_x + b.m_x) / 2;
   ret.m_y = (a.m_x + b.m_y) / 2;
   return ret;
}

std::pair<bezier_curve, bezier_curve> bezier_curve::split() const {
   std::pair<bezier_curve, bezier_curve> ret;
   ret.first.points[0] = points[0];
   ret.second.points[3] = points[3];

   wxPoint2DDouble point_1_0 = get_mean(points[0], points[1]);
   wxPoint2DDouble point_1_1 = get_mean(points[1], points[2]);
   wxPoint2DDouble point_1_2 = get_mean(points[2], points[3]);

   wxPoint2DDouble point_2_0 = get_mean(point_1_0, point_1_1);
   wxPoint2DDouble point_2_1 = get_mean(point_1_1, point_1_2);

   wxPoint2DDouble point_3_0 = get_mean(point_2_0, point_2_1);

   ret.first.points[1] = point_1_0;
   ret.first.points[2] = point_2_0;
   ret.first.points[3] = point_3_0;

   ret.second.points[0] = point_3_0;
   ret.second.points[1] = point_2_1;
   ret.second.points[2] = point_1_2;

   return ret;
}

bool bezier_curve::needs_split() const {
   wxDouble sum = 0;
   sum += points[0].GetDistance(points[1]);
   sum += points[1].GetDistance(points[2]);
   sum += points[2].GetDistance(points[3]);

   return sum - points[0].GetDistance(points[3]) <= sqrt2;
}

wxDouble bezier_curve::get_mean_angle(int point) const {
   if (is_line()) {
      return std::asin((points[3].m_y - points[0].m_y) / points[0].GetDistance(points[3]));
   }
   switch (point) {
   case 0:
      return std::asin((points[1].m_y - points[0].m_y) / points[0].GetDistance(points[1]));
   case 3:
      return std::asin((points[3].m_y - points[2].m_y) / points[2].GetDistance(points[3]));
   case 1:
   case 2:
      return
         (std::asin((points[point + 1].m_y - points[point].m_y) / points[point].GetDistance(points[point + 1]))
         + std::asin((points[point].m_y - points[point-1].m_y) / points[point-1].GetDistance(points[point])))
         / 2;
   }

   return 0.0;
}

bool bezier_curve::is_line() const {
   return std::abs(points[1].m_y - points[1].m_y) < sigma && std::abs(points[1].m_x - points[1].m_x) < sigma &&
      std::abs(points[3].m_y - points[2].m_y) < sigma && std::abs(points[3].m_x - points[2].m_x) < sigma;
}


Basically, I move the lines of the control polygon perpendicularly away, offsetting them. I also allow for change in the offset along the curve.

To make the offsets really accurate, I would use plenty of splitting.


_________________
"You have a responsibility to consider all sides of a problem and a responsibility to make a judgment and a responsibility to care for all involved." --Ian Danskin


Last edited by beneficii on 05 Aug 2014, 12:08 am, edited 2 times in total.

beneficii
Veteran
Veteran

User avatar

Joined: 10 May 2005
Age: 42
Gender: Female
Posts: 7,245

04 Aug 2014, 11:20 pm

To make incrementing or decrementing an offset smoother, I'll probably need to know the lengths of each of the path segments (line or Bezier curve).

Of course, calculating the length of a cubic Bezier curve is kinda tricky.


_________________
"You have a responsibility to consider all sides of a problem and a responsibility to make a judgment and a responsibility to care for all involved." --Ian Danskin