Nervous programming
Page 1 of 1 [ 5 posts ]
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
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;
}
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.
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
Page 1 of 1 [ 5 posts ]
| Similar Topics | |
|---|---|
| AI is taking out the joy out of programming. |
Yesterday, 10:18 am |
