izzi
SVG SUBSET C++ API
Loading...
Searching...
No Matches
a60-svg-curves-damped-harmonograph.h
Go to the documentation of this file.
1// izzi harmonograph -*- mode: C++ -*-
2
3// Copyright (c) 2026, Benjamin De Kosnik <b.dekosnik@gmail.com>
4
5// This file is part of the alpha60 library. This library is free
6// software; you can redistribute it and/or modify it under the terms
7// of the GNU General Public License as published by the Free Software
8// Foundation; either version 3, or (at your option) any later
9// version.
10
11// This library is distributed in the hope that it will be useful, but
12// WITHOUT ANY WARRANTY; without even the implied warranty of
13// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14// General Public License for more details.
15
16#ifndef a60_SVG_CURVES_DAMPED_HARMONOGRAPH_H
17#define a60_SVG_CURVES_DAMPED_HARMONOGRAPH_H 1
18
19#include <iostream>
20#include <string>
21#include <format>
22#include <cmath>
23#include <numbers>
24#include <tuple>
25#include <vector>
26
27using point_2t = std::tuple<double, double>;
28
29/**
30 * Generates a Damped Harmonograph SVG path.
31 * @param pt Origin tuple {x, y}
32 * @param r Initial Radius (scale)
33 * @param n Frequency ratio (number of waves)
34 * @param d Damping coefficient (0.01 to 0.1)
35 * @param cycles Total rotations (2 * pi * cycles)
36 */
37std::string
38generate_damped_harmonograph(point_2t pt, double r, double n, double d,
39 double cycles = 10.0)
40{
41 auto [ox, oy] = pt;
42
43 auto getPos = [&](double t) -> point_2t {
44 double decay = r * std::exp(-d * t);
45 return {
46 ox + decay * std::sin(t),
47 oy + decay * std::sin(n * t + std::numbers::pi / 2.0)
48 };
49 };
50
51 auto getVelocity = [&](double t) -> point_2t {
52 double e_dt = std::exp(-d * t);
53 // Derivatives accounting for product rule with damping
54 double vx = r * e_dt * (std::cos(t) - d * std::sin(t));
55 double vy = r * e_dt * (n * std::cos(n * t + std::numbers::pi / 2.0) - d * std::sin(n * t + std::numbers::pi / 2.0));
56 return { vx, vy };
57 };
58
59 // Calculate segments: more cycles require more steps to maintain smoothness
60 int steps = static_cast<int>(cycles * 64);
61 double max_t = cycles * 2.0 * std::numbers::pi;
62 double dt = max_t / steps;
63 double kappa = dt / 3.0;
64
65 auto [start_x, start_y] = getPos(0);
66 std::string path = std::format("M {:.2f} {:.2f}", start_x, start_y);
67
68 for (int i = 0; i < steps; ++i)
69 {
70 double t0 = i * dt;
71 double t1 = (i + 1) * dt;
72
73 auto [p0x, p0y] = getPos(t0);
74 auto [v0x, v0y] = getVelocity(t0);
75 auto [p1x, p1y] = getPos(t1);
76 auto [v1x, v1y] = getVelocity(t1);
77
78 // Control points
79 double c1x = p0x + kappa * v0x;
80 double c1y = p0y + kappa * v0y;
81 double c2x = p1x - kappa * v1x;
82 double c2y = p1y - kappa * v1y;
83
84 path += std::format(" C {:.2f} {:.2f}, {:.2f} {:.2f}, {:.2f} {:.2f}",
85 c1x, c1y, c2x, c2y, p1x, p1y);
86 }
87
88 return path;
89}
90
91/**
92 * Generates a Triple-Frequency Damped Harmonograph.
93 * @param n1 Primary Vertical Frequency
94 * @param n2 Secondary Vertical Frequency
95 * @param p2 Phase shift for the second frequency
96 */
97std::string
98generate_triple_harmonograph(point_2t origin, double r, double n1, double n2,
99 double p2, double d, double cycles)
100{
101 auto [ox, oy] = origin;
102
103 auto getPos = [&](double t) -> point_2t {
104 double decay = r * std::exp(-d * t);
105 // X is a single pendulum
106 double x = ox + decay * std::sin(t);
107 // Y is the sum of two pendulums (interference)
108 double y = oy + (decay / 2.0) * (std::sin(n1 * t + std::numbers::pi / 2.0) + std::sin(n2 * t + p2));
109 return {x, y};
110 };
111
112 auto getVelocity = [&](double t) -> point_2t {
113 double e_dt = std::exp(-d * t);
114 double vx = r * e_dt * (std::cos(t) - d * std::sin(t));
115 // Derivative of the sum of two sines
116 double vy = (r / 2.0) * e_dt * (
117 (n1 * std::cos(n1 * t + std::numbers::pi / 2.0) - d * std::sin(n1 * t + std::numbers::pi / 2.0)) +
118 (n2 * std::cos(n2 * t + p2) - d * std::sin(n2 * t + p2))
119 );
120 return {vx, vy};
121 };
122
123 int steps = static_cast<int>(cycles * 120);
124 double dt = (cycles * 2.0 * std::numbers::pi) / steps;
125 double kappa = dt / 3.0;
126
127 auto [sx, sy] = getPos(0);
128 std::string path = std::format("M {:.2f} {:.2f}", sx, sy);
129
130 for (int i = 0; i < steps; ++i) {
131 double t0 = i * dt, t1 = (i + 1) * dt;
132 auto [p0x, p0y] = getPos(t0); auto [v0x, v0y] = getVelocity(t0);
133 auto [p1x, p1y] = getPos(t1); auto [v1x, v1y] = getVelocity(t1);
134
135 path += std::format(" C {:.2f} {:.2f}, {:.2f} {:.2f}, {:.2f} {:.2f}",
136 p0x + kappa * v0x, p0y + kappa * v0y,
137 p1x - kappa * v1x, p1y - kappa * v1y, p1x, p1y);
138 }
139 return path;
140}
141
142
143
144#endif
std::string generate_triple_harmonograph(point_2t origin, double r, double n1, double n2, double p2, double d, double cycles)
std::tuple< double, double > point_2t
std::string generate_damped_harmonograph(point_2t pt, double r, double n, double d, double cycles=10.0)