Subversion Repositories DIN Is Noise

Rev

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;
}