Rev 2302 |
Blame |
Compare with Previous |
Last modification |
View Log
| RSS feed
/*
* multi_curve.cc
* DIN Is Noise is copyright (c) 2006-2025 Jagannathan Sampath
* DIN Is Noise is released under GNU Public License 2.0
* For more information, please visit https://dinisnoise.org/
*/
#include "multi_curve.h"
#include "vector2d.h"
#include "container.h"
#include "random.h"
#include "log.h"
#include <iostream>
extern string user_data_dir;
static const char eol = '\n';
multi_curve::multi_curve (const string& filename) {
load (filename);
}
multi_curve::multi_curve () {
clear ();
}
void multi_curve::clear (int all) {
if (all) {
name = "nameless";
set_color (1, 1, 1);
}
vertices.clear ();
num_vertices = 0;
last_vertex = -1;
left_tangents.clear ();
right_tangents.clear ();
curv.clear ();
limit = 0.001f;
shapeform = 0;
}
//
// must call following 3 funcs together with add_vertex called first
// to correctly add curve
//
void multi_curve::add_vertex (float x, float y, int p) {
last_vertex = num_vertices++;
vertices.push_back (pointinfo<float> (x, y, p));
if (num_vertices > 1) {
curv.push_back (curve());
ncurvs = curv.size ();
}
}
void multi_curve::add_left_tangent (float x, float y, int p) {
left_tangents.push_back (pointinfo<float> (x, y, p));
}
void multi_curve::add_right_tangent (float x, float y, int p) {
right_tangents.push_back (pointinfo<float> (x, y, p));
}
void multi_curve::get_vertex (int i, float& x, float& y) {
pointinfo<float>& v = vertices[i];
x = v.x;
y = v.y;
}
int multi_curve::set_vertex (int i, float x, float y) {
pointinfo<float>& v = vertices[i];
if ((v.x != x) || (v.y != y)) {
pointinfo<float>& lt = left_tangents[i];
if (lt.pin == 0) {
float dlx, dly;
dlx = lt.x - v.x;
dly = lt.y - v.y;
lt.x = x + dlx; lt.y = y + dly;
}
pointinfo<float>& rt = right_tangents[i];
if (rt.pin == 0) {
float drx, dry;
drx = rt.x - v.x; dry = rt.y - v.y;
rt.x = x + drx; rt.y = y + dry;
}
v.x = x;
v.y = y;
if (i < ncurvs) curv[i].eval = 1;
int i_1 = i - 1; if (i_1 > -1) curv[i_1].eval = 1;
return 1;
} else return 0;
}
int multi_curve::set_left_tangent (int i, float x, float y) {
point<float>& lt = left_tangents [i];
if ((lt.x != x) || (lt.y != y)) {
lt.x = x;
lt.y = y;
int j = i - 1;
if (j > -1) curv[j].eval = 1;
return 1;
}
return 0;
}
int multi_curve::set_right_tangent (int i, float x, float y) {
point<float>& rt = right_tangents[i];
if ((rt.x != x) || (rt.y != y)) {
rt.x = x;
rt.y = y;
if (i < ncurvs) curv[i].eval = 1;
return 1;
}
return 0;
}
void multi_curve::get_left_tangent (int i, float& x, float& y) {
point<float>& lt = left_tangents [i];
x = lt.x;
y = lt.y;
}
void multi_curve::get_right_tangent (int i, float& x, float& y) {
point<float>& rt = right_tangents[i];
x = rt.x;
y = rt.y;
}
int multi_curve::insert (float x, float y, float tx, float ty) {
point<float>& vl = vertices[last_vertex];
if (x > vl.x) { // append
add_vertex (x, y);
add_left_tangent (x, y);
add_right_tangent (x, y);
evaluate ();
return 1;
}
// insert
//
points_array::iterator \
vter = ++vertices.begin (), \
lter = ++left_tangents.begin (), \
rter = ++right_tangents.begin ();
vector<curve>::iterator cter = curv.begin ();
for (int i = 0, k = last_vertex; i < k; ++i, ++vter, ++lter, ++rter, ++cter) {
point<float>& vi = vertices[i];
point<float>& vj = vertices[i + 1];
if ((x >= vi.x) && (x <= vj.x)) {
point<float> v (x, y);
vertices.insert (vter, v);
last_vertex = num_vertices++;
left_tangents.insert (lter, v);
right_tangents.insert (rter, v);
(*cter).eval = 1;
curv.insert (cter, curve());
++ncurvs;
return 1;
}
}
return 0;
}
int multi_curve::remove (int i) {
// remove ith vertex and its tangents
if (num_vertices < 3) return 0;
erase_id (vertices, i);
erase_id (left_tangents, i);
erase_id (right_tangents, i);
if (i == ncurvs) {
int j = i - 1;
erase_id (curv, j);
} else {
erase_id (curv, i);
if (--i > -1) curv[i].eval = 1; else curv[0].eval = 1;
}
--num_vertices;
--ncurvs;
last_vertex = num_vertices - 1;
return 1;
}
void multi_curve::set_limit (float d) {
limit = d;
require_eval ();
}
void multi_curve::require_eval () {
for (int i = 0; i < ncurvs; ++i) curv[i].eval = 1;
}
void multi_curve::set_color () {
static const float base = 0.1f;
static rnd<float> rd (base, 1.0f - base);
r = rd (); g = rd (); b = rd ();
calc_tangent_color ();
}
void multi_curve::set_color (float rr, float gg, float bb) {
r = rr;
g = gg;
b = bb;
calc_tangent_color ();
}
void multi_curve::calc_tangent_color () {
static const float factor = 0.5;
rt = factor * r;
gt = factor * g;
bt = factor * b;
}
void multi_curve::evaluate () {
int nevals = 0;
for (int i = 0; i < ncurvs; ++i) {
curve& crv = curv[i];
if (crv.eval) {
int j = i + 1;
point<float>& v0 = vertices[i];
point<float>& v1 = vertices[j];
point<float>& rt0 = right_tangents[i];
point<float>& lt1 = left_tangents[j];
crv.vertex (0, v0.x, v0.y);
crv.vertex (1, v1.x, v1.y);
crv.tangent (0, rt0.x, rt0.y);
crv.tangent (1, lt1.x, lt1.y);
crv.set_limit (limit);
crv.evaluate (shapeform);
++nevals;
}
}
if (nevals) {
if (shapeform) {
float total_length = 0.0f;
float now_length = 0.0f;
for (int i = 0; i < ncurvs; ++i) total_length += curv[i].length;
for (int i = 0; i < ncurvs; ++i) curv[i].normalise_length (now_length, total_length);
check_shapeform ();
}
}
}
void multi_curve::check_shapeform () {
for (int i = 0, j = 1, k = ncurvs - 1; i < k; ++i, ++j) {
curve& crvi = curv[i];
curve& crvj = curv[j];
vector<crvpt>& dptsi = crvi.dpts;
vector<crvpt>& dptsj = crvj.dpts;
int last = dptsi.size () - 1;
crvpt& dl = dptsi[last];
crvpt& d0 = dptsj[0];
d0.x = dl.x;
d0.y = dl.y;
}
}
void multi_curve::calc_bbox (box<float>& b) {
if (ncurvs) {
int num_points = curv[0].vpts.size ();
if (num_points) {
const vector<crvpt>& vpts = curv[0].vpts;
b.left = b.right = vpts[0].x;
b.bottom = b.top = vpts[0].y;
for (int i = 0; i < ncurvs; ++i) {
const vector <crvpt>& vpts = curv[i].vpts;
num_points = vpts.size ();
for (int j = 0; j < num_points; ++j) {
const crvpt& p = vpts[j];
if (p.x < b.left) b.left = p.x; else if (p.x > b.right) b.right = p.x;
if (p.y < b.bottom) b.bottom = p.y; else if (p.y > b.top) b.top = p.y;
}
}
}
}
b.calc ();
}
void multi_curve::load (const string& filename) {
ifstream file ((user_data_dir + filename).c_str (), ios::in);
if (!file) {
dlog << "!!! failed to load curve from: " << filename << " !!!" << endl;
return;
}
load (file);
dlog << "+++ loaded curve from: " << filename << " +++" << endl;
}
void multi_curve::load (ifstream& file) {
clear ();
string ignore;
file >> ignore >> name;
int nvertices;
file >> ignore >> nvertices;
for (int i = 0; i < nvertices; ++i) {
float x, y;
int p;
file >> ignore >> x >> y >> p;
add_vertex (x, y, p);
file >> ignore >> x >> y >> p;
add_left_tangent (x, y, p);
file >> ignore >> x >> y >> p;
add_right_tangent (x, y, p);
}
file >> ignore >> limit;
float ir, ig, ib; file >> ignore >> ir >> ig >> ib;
set_color (ir, ig, ib);
file >> ignore >> shapeform;
setcentroid (shapeform);
point<float>& lt0 = left_tangents[0];
point<float>& v0 = vertices[0];
int last = nvertices - 1;
point<float>& rtl = right_tangents[last];
point<float>& vl = vertices[last];
lt0.x = v0.x; lt0.y = v0.y;
rtl.x = vl.x; rtl.y = vl.y;
evaluate ();
}
void multi_curve::save (const string& filename) {
ofstream file ((user_data_dir + filename).c_str (), ios::out);
if (!file) {
dlog << "!!! failed writing curve to: " << filename << endl;
return;
}
save (file);
dlog << "+++ saved curve into: " << filename << " +++" << endl;
}
void multi_curve::save (ofstream& file) {
string ignore;
if (name == "") name = "nameless";
file << "name " << name << eol;
file << "num_vertices " << num_vertices << eol;
for (int i = 0; i < num_vertices; ++i) {
pointinfo<float>& v = vertices[i];
pointinfo<float>& lt = left_tangents[i];
pointinfo<float>& rt = right_tangents[i];
file << "v" << i << spc << v.x << spc << v.y << spc << v.pin << eol;
file << "lt" << i << spc << lt.x << spc << lt.y << spc << lt.pin << eol;
file << "rt" << i << spc << rt.x << spc << rt.y << spc << rt.pin << eol;
}
file << "limit " << limit << eol;
file << "color " << r << spc << g << spc << b << eol;
file << "shapeform " << shapeform << eol;
}
void create_polyline (multi_curve& crv, const points_array& pts) {
int npts = pts.size ();
if (npts < 2) return;
crv.clear (0);
for (int i = 0; i < npts; ++i) {
float xi = pts[i].x, yi = pts[i].y;
crv.add_vertex (xi, yi);
crv.add_left_tangent (xi, yi);
crv.add_right_tangent (xi, yi);
}
crv.evaluate ();
}
void convert2_polyline (multi_curve& crv) {
for (int i = 0, j = crv.num_vertices; i < j; ++i) {
point<float>& v = crv.vertices[i];
crv.set_left_tangent (i, v.x, v.y);
crv.set_right_tangent (i, v.x, v.y);
}
crv.evaluate ();
}
void convert2_catmull_rom (multi_curve& crv, float tangent_size) {
int npts = crv.num_vertices;
if (npts < 2) return;
int last = npts - 1;
point<float>& p0 = crv.vertices[0];
point<float>& p1 = crv.vertices[1];
point<float>& pl = crv.vertices[last];
point<float>& pl1 = crv.vertices[last-1];
// set tangents for 1st vertex
float dx, dy; direction (dx, dy, p0.x, p0.y, p1.x, p1.y);
float tx = tangent_size * dx, ty = tangent_size * dy;
crv.set_left_tangent (0, p0.x - tx, p0.y - ty);
crv.set_right_tangent (0, p0.x + tx, p0.y + ty);
// set tangents for last vertex
direction (dx, dy, pl.x, pl.y, pl1.x, pl1.y);
tx = tangent_size * dx; ty = tangent_size * dy;
crv.set_left_tangent (last, pl.x + tx, pl.y + ty);
crv.set_right_tangent (last, pl.x - tx, pl.y - ty);
// set left, right tangent for inbetween vertices
for (int i = 1; i < last; ++i) {
int l = i - 1, r = i + 1;
point<float>& pi = crv.vertices[i];
point<float>& pl = crv.vertices[l];
point<float>& pr = crv.vertices[r];
direction (dx, dy, pl.x, pl.y, pr.x, pr.y);
crv.set_left_tangent (i, pi.x - tangent_size * dx, pi.y - tangent_size * dy);
crv.set_right_tangent (i, pi.x + tangent_size * dx, pi.y + tangent_size * dy);
}
crv.evaluate ();
}
multi_curve* check_list (multi_curve** lst, int n, const string& name) {
for (int m = 0; m < n; ++m) if (lst[m]->name == name) return lst[m];
return 0;
}
vector<crvpt>& multi_curve::get_profile_points (int i) {
if (shapeform) return curv[i].dpts; else return curv[i].vpts;
}
void multi_curve::setcentroid (int shapeform) {
static const float CX [] = {0.0f, 0.5f};
cen.x = CX [shapeform];
cen.y = 0.0f;
}
void multi_curve::set_shapeform (int what) {
shapeform = what;
setcentroid (shapeform);
require_eval ();
evaluate ();
}
void multi_curve::rotate (float angle) {
for (int i = 1; i < num_vertices; ++i) {
pointinfo<float>& lt = left_tangents[i];
if (lt.pin == 0) lt.rotate (cen, angle);
}
for (int i = 0; i < last_vertex; ++i) {
pointinfo<float>& rt = right_tangents[i];
if (rt.pin == 0) rt.rotate (cen, angle);
}
for (int i = 0; i < num_vertices; ++i) {
pointinfo<float>& vi = vertices[i];
if (vi.pin == 0) vi.rotate (cen, angle);
}
point<float>& v0 = vertices[0];
point<float>& lt0 = left_tangents[0];
lt0 = v0;
point<float>& vl = vertices[last_vertex];
point<float>& rtl = right_tangents[last_vertex];
rtl = vl;
require_eval ();
evaluate ();
}
void multi_curve::scale (float sx, float sy) {
for (int i = 0; i < num_vertices; ++i) {
point<float>& vi = vertices[i];
point<float>& lt = left_tangents[i];
point<float>& rt = right_tangents[i];
vi.scale (cen, sx, sy);
lt.scale (cen, sx, sy);
rt.scale (cen, sx, sy);
}
require_eval ();
evaluate ();
}
void multi_curve::get_xy (double d, float& x, float& y) {
for (int i = 0; i < ncurvs; ++i) {
curve& c = curv[i];
vector<crvpt>& vpts = c.vpts;
vector<crvpt>& dpts = c.dpts;
for (int m = 0, n = dpts.size () - 1; m < n; ++m) {
crvpt& dm = dpts[m];
crvpt& dn = dpts[m+1];
crvpt& vm = vpts[m];
crvpt& vn = vpts[m+1];
if (d >= dm.x && d <= dn.x) {
float amt = (d - dm.x) / (dn.x - dm.x);
x = vm.x + amt * (vn.x - vm.x);
y = vm.y + amt * (vn.y - vm.y);
return;
}
}
}
}
int multi_curve::get_total_points () {
int total = 0;
for (int i = 0; i < ncurvs; ++i) {
vector<crvpt>& vpts = curv[i].vpts;
int ni = vpts.size () - 1;
total = total + ni;
}
++total;
return total;
}
int multi_curve::get_total_points (vector<int>& crvs) {
int j = ncurvs, j_1 = j - 1;
crvs.reserve (j);
int total = 0;
for (int i = 0; i < j; ++i) {
vector<crvpt>& vpts = curv[i].vpts;
int ni = vpts.size () - 1;
crvs[i] = ni;
total = total + ni;
}
++crvs[j_1];
++total;
return total;
}
int multi_curve::get_profile_points (vector<crvpt>& profile) {
vector<int> crvs;
int n = get_total_points (crvs);
profile.reserve (n);
profile.clear ();
for (int i = 0, k = 0; i < ncurvs; ++i) {
vector<crvpt>& vpts = curv[i].vpts;
for (int p = 0; p < crvs[i]; ++p) profile[k++] = vpts[p];
}
return n;
}
double multi_curve::get_tangent_mag (int i, points_array& pa) {
point<float>& v = vertices[i];
point<float>& t = pa[i];
return magnitude (v.x, v.y, t.x, t.y);
}
double multi_curve::get_left_tangent_mag (int i) {
return get_tangent_mag (i, left_tangents);
}
double multi_curve::get_right_tangent_mag (int i) {
return get_tangent_mag (i, right_tangents);
}
void multi_curve::move (float dx, float dy) {
for (int i = 0; i < num_vertices; ++i) {
pointinfo<float>& p = vertices [i];
set_vertex (i, p.x + dx, p.y + dy);
/*if (p.carry == 0) {
pointinfo<float>& l = left_tangents [i];
if (l.pin == 0) set_left_tangent (i, l.x + dx, l.y + dy);
pointinfo<float>& r = right_tangents [i];
if (r.pin == 0) set_right_tangent (i, r.x + dx, r.y + dy);
//}*/
}
require_eval ();
evaluate ();
}
extern void make_good_name (string& name);
void multi_curve::set_name (const string& _name) {
name = _name;
make_good_name (name);
}
void scale (point<float>& p, point<float>& c, float sx, float sy) {
float px = sx * (p.x - c.x), py = sy * (p.y - c.y);
p.x = c.x + px;
p.y = c.y + py;
}