100 int ribbon_strands,
double ribbon_width,
103 using std::numbers::pi;
106 const int steps = 360;
108 double gap = ribbon_width;
109 double stride = ribbon_width + gap;
110 double total_bundle_width = (ribbon_strands * stride) - gap;
112 std::string path_data;
115 auto get_spine_3d = [&](
double t) ->
point_3d
122 double x = r * std::cos(t);
123 double y = r * std::sin(t);
124 double z = config.
torsion * std::sin(angle_k);
129 for (
int s = 0; s < ribbon_strands; ++s)
131 double offset = (s * stride) - (total_bundle_width / 2.0);
133 std::vector<point_2d> edge_left_2d;
134 std::vector<point_2d> edge_right_2d;
136 for (
int i = 0; i <= steps; ++i)
138 double t = (
static_cast<double>(i) / steps) * 2.0 * pi;
139 double epsilon = 0.005;
143 point_3d p1 = get_spine_3d(t + epsilon);
145 auto [p0x, p0y, p0z] = p0;
146 auto [p1x, p1y, p1z] = p1;
153 if (std::abs(std::get<2>(tangent)) > 0.95)
161 double current_roll = config.
roll_speed * t * 2.0;
164 auto [nx, ny, nz] = normal;
170 double c = std::cos(current_roll);
171 double s_val = std::sin(current_roll);
179 auto [svx, svy, svz] = surface_vec;
189 p0x + svx * (offset + ribbon_width),
190 p0y + svy * (offset + ribbon_width),
191 p0z + svz * (offset + ribbon_width)
198 edge_left_2d.emplace_back(origin_x + rx_start *
scale, origin_y + ry_start *
scale);
199 edge_right_2d.emplace_back(origin_x + rx_end *
scale, origin_y + ry_end *
scale);
203 auto [start_x, start_y] = edge_left_2d[0];
204 path_data += std::format(
"M {:.2f} {:.2f} ", start_x, start_y);
206 for (
size_t k = 1; k < edge_left_2d.size(); ++k)
208 auto [lx, ly] = edge_left_2d[k];
209 path_data += std::format(
"L {:.2f} {:.2f} ", lx, ly);
212 auto [last_rx, last_ry] = edge_right_2d.back();
213 path_data += std::format(
"L {:.2f} {:.2f} ", last_rx, last_ry);
215 for (
int k =
static_cast<int>(edge_right_2d.size()) - 2; k >= 0; --k)
217 auto [rx, ry] = edge_right_2d[k];
218 path_data += std::format(
"L {:.2f} {:.2f} ", rx, ry);
224 return std::format(
"<path d=\"{}\" fill=\"black\" stroke=\"none\" />", path_data);
243 int ribbon_strands,
double ribbon_width,
ripple_config config)
245 using std::numbers::pi;
247 const int steps = 180;
248 double gap = ribbon_width;
249 double stride = ribbon_width + gap;
250 double total_bundle_width = (ribbon_strands * stride) - gap;
253 auto get_spine_3d = [&](
double t) ->
point_3d
256 double x = t * (length / 2.0);
259 double amp_mod = 1.0;
260 if (config.
decay > 0.0)
264 double progress = (t + 1.0) / 2.0;
265 amp_mod = std::max(0.0, 1.0 - (config.
decay * progress));
268 double z = config.
amplitude * std::sin(wave_arg) * amp_mod;
272 std::string path_data;
273 for (
int s = 0; s < ribbon_strands; ++s)
275 double offset_val = (s * stride) - (total_bundle_width / 2.0);
276 std::vector<point_2d> edge_left, edge_right;
278 for (
int i = 0; i <= steps; ++i)
280 double t = -1.0 + (2.0 *
static_cast<double>(i) / steps);
281 double epsilon = 0.01;
284 point_3d p1 = get_spine_3d(t + epsilon);
285 auto [p0x, p0y, p0z] = p0;
286 auto [p1x, p1y, p1z] = p1;
293 if (std::get<1>(binormal) < 0)
295 binormal = { -std::get<0>(binormal), -std::get<1>(binormal), -std::get<2>(binormal) };
297 auto [bx, by, bz] = binormal;
299 point_3d p_start_3d = { p0x + bx * offset_val, p0y + by * offset_val, p0z + bz * offset_val };
300 point_3d p_end_3d = { p0x + bx * (offset_val + ribbon_width), p0y + by * (offset_val + ribbon_width), p0z + bz * (offset_val + ribbon_width) };
305 edge_left.emplace_back(origin_x + rx_s, origin_y + ry_s);
306 edge_right.emplace_back(origin_x + rx_e, origin_y + ry_e);
309 auto [sx, sy] = edge_left[0];
310 path_data += std::format(
"M {:.2f} {:.2f} ", sx, sy);
311 for (
size_t k = 1; k < edge_left.size(); ++k)
313 auto [lx, ly] = edge_left[k];
314 path_data += std::format(
"L {:.2f} {:.2f} ", lx, ly);
316 auto [ex, ey] = edge_right.back();
317 path_data += std::format(
"L {:.2f} {:.2f} ", ex, ey);
318 for (
int k =
static_cast<int>(edge_right.size()) - 2; k >= 0; --k)
320 auto [rx, ry] = edge_right[k];
321 path_data += std::format(
"L {:.2f} {:.2f} ", rx, ry);
325 return std::format(
"<path d=\"{}\" fill=\"black\" />", path_data);
std::string make_ripple_ribbon(double origin_x, double origin_y, double length, int ribbon_strands, double ribbon_width, ripple_config config)