/*
 * REQUIRES OpenSCAD v2019.01 or later
 */

DEBUG = 0;
scale = 87.1;  // O=48, HO=87.1, N=160

/*
 * Track gauge is measured between the *inside* of the rails,
 * so add the rail head dimension to each gauge to accurately
 * place each rail position.
 *
 * US standard=56.5", n3=36", n30=30"
 *
 * Measurements of Micro Engineering HO track were around 57.75"
 * but adjustments should be made here to match your
 * particular track.
 */
rail = [5.923, 2.526]; // [foot, head] - width in inches
rail_standard_gauge = scalemm(57.75);
rail_narrow_gauge = scalemm(36.5);
point_clearance = scalemm(5);

/***** do not edit these two lines *****/
gauge = rail_standard_gauge + scalemm(rail[1]);
dualgauge = rail_narrow_gauge + scalemm(rail[1]);
/***************************************/

include <turnout_jig_tests.scad>;
track_list = [
  // [0] radius (in mm)
  // [1] angle (in degrees):
  //     negative values curve to the left
  //     use 0 for a straight track
  // [2] straight leader from bottom (in mm)
  // [3] gauge
  // [4] show 3rd rail:
  //     false=hide, true=show

[ //// [0] straight ////
  [ -1, [0, 0, 33, gauge, 1]], // used to test printer

  [  0, [mm(24), -45, 0, gauge, 0], [0, 0, 0, gauge, 0] ],
  [  0, [mm(24),  45, 0, gauge, 0], [0, 0, 0, gauge, 0] ],
],

[ //// [1] curved ////
  [  0, [mm(18), -45, mm(0), gauge, 0],
    [mm(30), -10, mm(0), gauge, 0 ] ],
],

[ //// [2] wyes ////
  [  0, [mm(36), -45, mm(0), gauge, 0],
        [mm(18),  8, mm(0), gauge, 0] ],
  [  0, [mm(36), 45, mm(0), gauge, 0],
        [mm(18),  -8, mm(0), gauge, 0] ],
],

[ //// [3] three-way ////
  [  1, [mm(18), -45, mm(2.5), gauge, 0],
        [mm(0), 0, mm(0), gauge, 0],
        [mm(42), 45, mm(0), gauge, 0] ],
]];
use_track = [0,1]; // set to a track definition from the list!

track_set = (test_sets) ? test_sets : track_list;
tracks = track_sort([
  let(x=use_track.x, y=use_track.y)
  for(i=[0:len(track_set[x][y])-1])
    track_set[x][y][i]
]);

base = [mm(0), 0, mm(0.75), gauge, 1];
//base = [mm(18), 10, mm(0.75), gauge, 1];
//base = tracks[0];

base_extra = mm(0.5); // extra rack before points
frog_extra = mm(1.0); // distance after last frog

slop = 0.2; // extra space for printer limitations
inside_rail_width = 0.5; // set to zero for no rail marks (not visible in preview)
show_all_ties = false;
show_pcb_ties = true;
hidden_tie_depth = 0.2;
hidden_tie_style = 0; // 0=full, 1=outline, 2=ends
show_guardrails = true;
show_helper_guides = true;
show_point_spacing = true;
use_rail_slots = false;

jig_height = 3.6; // mm
track_depth = 0.8; // mm below top of jig
flange_depth = 0.4; // in addition to track_depth
flange_width = 0.8;
guardrail_width = 0.4 + scalemm(rail[0]) + slop;
guardrail_length = mm(0.75);
guardrail_offset = 0;//inside_rail_width;
guardrail_point_angle = 10;
guardrail_point_length = mm(0.25);
text_depth = 0.6;


// 9x7 for modern RR / 8x6 for older lines
// adjusted to match PCB tie size
// height, width, length, spacing (center-to-center)
tie_size = [5.375, 8, 102, 20];
//tie_depth = 2.5; // mm

jig_margin = 8; // mm


/**************************************************/
/********** Do not edit after this point **********/
/**************************************************/

$fn = 16;
track_colors = [
  ["#888","#888"], // default color
  ["#44f","#8bf"],
  ["#4b4","#8f8"],
  ["#f90","#fc0"],
  ["#f00","#f66"],
];

// -1=left rail, 0=hidden, 1=right rail
//dualgauge_common = test_sets[test.x][test.y][0];
dualgauge_common = track_set[use_track.x][use_track.y][0];

gauge2 = gauge / 2;
dualgauge2 = dualgauge / 2;
gauge_offset = (gauge2 - dualgauge2) * dualgauge_common;
//gauge_clearance = scalemm(0.6); // extra space to match flex track

rail_head = scalemm(rail[1]);
rail_head2 = rail_head / 2;
rail_foot = scalemm(rail[0]) + slop;
rail_foot2 = rail_foot / 2;
inside_rail_width2 = inside_rail_width / 2;
dual_center_offset = dualgauge - gauge2;
if (DEBUG) echo("gauge_offset",gauge_offset);
if (DEBUG) echo("dual_center_offset",dual_center_offset);

echo("*** tracks",tracks);
//echo("*** test_sets",test_sets);
track_count = len(tracks) - 1;
outside_tracks = find_left_right();
sameside = same_side(outside_tracks);
min_jig_length = max([for(t=tracks)t.z]);
max_radius = max([for(t=tracks)t.x]);
//echo("max_radius",max_radius);
any_dual = abs(max([for(t=tracks)t[4]]));
//echo("any_dual",any_dual);

//left_visible = true;
left = outside_tracks[0];
left_radius = left[0];
left_angle = left[1];
left_leader = left[2];
left_gauge = left[3];
left_gauge2 = left_gauge / 2;
//echo("left_radius",left_radius);

//right_visible = true;
right = outside_tracks[1];
right_radius = right[0];
right_angle = right[1];
right_leader = right[2];
right_gauge = right[3];
right_gauge2 = right_gauge / 2;


// find frog info for each rail pair
y_cutoff = gauge;
new_frogs = all_frogs();
/*
m = [
  for(f=[[0,1,1,0],[0,2,1,0],[0,1],[0,2],[1,2],[2,1]])
    let(y = search([f],new_frogs))
    if(new_frogs[y.x][1][1]) new_frogs[y.x][1][1]
];
mx = max(m) || 0;
echo("m",m);
echo("mx",mx);
*/
all_frogs = uniq_frogs(new_frogs);

first_frogY  = min([for(f=all_frogs)f.x.y]);
last_frogY  = max([for(f=all_frogs)f.x.y]);
if (DEBUG) echo("new_frogs",new_frogs);
echo("all_frogs",all_frogs);


// *** tie size variables
tie_bottom = jig_height-scalemm(tie_size[0])-track_depth*0.75;
tie_depth  = scalemm(tie_size[0]);
tie_width  = scalemm(tie_size[1]) + slop/2;
tie_length = scalemm(tie_size[2]) + slop;
half_tie = tie_length / 2;
tie_length_inc = tie_width;
tie_length2 = tie_length / 2;
tie_spacing = scalemm(tie_size[3]);
tie_spacing2 = tie_spacing / 2;
tie_rail_diff = (tie_length - gauge) / 2;

// average all frogY positions to get this?
frog_avg = last_frogY;
//frog_avg = mean([for(f=all_frogs)f.x.y]);
tie_offset = (!frog_avg) ? 0
  : frog_avg - (ceil(frog_avg / tie_spacing) * tie_spacing);
//tie_offset = last_frogY - (round(last_frogY / tie_spacing) * tie_spacing);

//echo("tie_spacing",tie_spacing);
//echo("tie_rail_diff",tie_rail_diff);
echo("tie_offset",tie_offset);

// *** set the jig extra lengths and tie positions
base_length = ceil(abs((base_extra + tie_spacing2) / tie_spacing)) * tie_spacing - tie_spacing/2 - tie_offset;
frog_length = ceil(abs((frog_extra + tie_spacing2) / tie_spacing)) * tie_spacing - tie_spacing/2;

// *** determine size of jig
jig_length = (last_frogY) ? last_frogY + frog_length : min_jig_length;
jig_edges = track_edges(jig_length);
jig_width_left = (jig_edges[0])
  ? floor_zero(-jig_edges[0]) + tie_rail_diff + jig_margin*2
  : tie_length2 + jig_margin;
jig_width_right = (jig_edges[1])
  ? floor_zero(jig_edges[1]) + tie_rail_diff + jig_margin*2
  : tie_length2 + jig_margin;
jig_width = jig_width_left + jig_width_right;


echo(str("jig_length: ",jig_length+base_length,"mm / ",in(jig_length+base_length),"in"));
echo (str("jig_width: ",jig_width,"mm / ",in(jig_width),"in"));
if (DEBUG)
{
  echo("gauge (inside rail head)",gauge-rail_head);
  echo("gauge (outside rail feet)", gauge+rail_foot);
  echo ("jig_width_left", jig_width_left);
  echo ("jig_width_right", jig_width_right);
  echo("gauge,dualgauge",gauge,dualgauge);
  echo("gauge_offset",gauge_offset);
  echo("dualgauge_common",dualgauge_common);
  //echo("rail_foot",rail_foot);
  echo("----------");
}



/********************************/
/********** Create jig **********/
/********************************/

// initial track
if (len(tracks) && jig_width)
difference()
{
  union()
  {

    difference()
    {
      // jig
      color("#ddd")
      translate([-jig_width_left, -base_length, 0])
        roundedcube(jig_width_left+jig_width_right, jig_length+base_length, jig_height, 2);

      draw_all_tracks();
    } // end difference()

    // lip inside rails
    //if (! $preview)
    intersection()
    {
      // keep additions inside the jig
      color("#0000")
      translate([-jig_width_left, -base_length, 0])
         cube([jig_width_left+jig_width_right, jig_length+base_length, jig_height]);

      draw_all_tracks(true);
    } // end intersection()

  } // end union()

  //point_spacing(left, right);
  if (show_point_spacing && all_frogs)
  {
    point_space(track_count, -1);
    point_space(0, 1);
    // add points for dualgauge
  }

  // point ties
  point_tie_count = floor((first_frogY - tie_spacing*5.5) /tie_spacing);
  point_tie_gap = min(10, ceil(point_tie_count / 3));
  point_ties = floor((point_tie_count - point_tie_gap/3) / point_tie_gap);
  //echo("point_tie_count",point_tie_count);
  //echo("point_tie_gap",point_tie_gap);
  if (DEBUG) echo("point_ties",point_ties);
  if (point_ties>0 && point_ties<99)
  for(i=[1:point_ties])
  {
    mark_tie_y(tie_spacing*2 + point_tie_gap*i*tie_spacing);
  }

  // locate PCB ties around frogs
  for(frog=all_frogs)
  {
    frogX = frog.x[0];
    frogY = frog.x[1];

    // mark frog crossing
    mark_tie_y(frogY - tie_spacing*3);
    mark_tie_y(frogY - tie_spacing*2.5, ">");
    mark_tie_y(frogY - tie_spacing*2);
    mark_tie_y(frogY);
    mark_tie_y(frogY + tie_spacing*2);
    mark_tie_y(frogY + tie_spacing*2.5, "<");
    mark_tie_y(frogY + tie_spacing*3);

    if ($preview)
    {
      //if (DEBUG) echo("frog",frogX,frogY);
      color(track_colors[(frog.x[0]+frog.x[1])][0])
      //color("red")
      translate([frogX, frogY, 0])
          cylinder(h=jig_height*2, d=rail_foot);
    }

  }

  // remove inside seperator from points
  p_max = straight_cross(rail_foot+inside_rail_width, max_radius);
  for(frog=all_frogs)
  {
    lt = frog[1][0][0];
    ltrack = tracks[lt];
    ldir = plusminus(ltrack[1]);
    lr = ltrack[0] + ltrack[3]/2;
    rt = frog[1][1][0];
    rtrack = tracks[rt];
    rdir = plusminus(rtrack[1]);
    rr = rtrack[0] + rtrack[3]/2;
    pp = (ltrack[1] && rtrack[1]) // double curve
      ? (ltrack[1]<0 && rtrack[1]>0)
        ? curve_cross(rail_foot-ltrack[0],0,ltrack[0], -rail_foot+rtrack[0],0,rtrack[0]) // wye
      : (ltrack[0] > rtrack[0])
        ? curve_cross(rr-rail_foot2-inside_rail_width,0,rtrack[0], rail_foot2+lr,0,ltrack[0])
        : curve_cross(lr-rail_foot2-inside_rail_width,0,ltrack[0], rail_foot2+rr,0,rtrack[0])
      : (! ltrack[1])
        ? straight_cross(rail_foot+inside_rail_width, rtrack[0])
        : straight_cross(rail_foot+inside_rail_width, ltrack[0]);
    p = (pp.y) ? pp.y : pp;
    //echo(str("P",frog[1][0][0],frog[1][1][0]), p);
    //translate([0,,p,0]) cube([4,1,10], center=true);
    //echo("lt,rt",lt,rt);

    if (p)
    {
      la = arc_angle(p-ltrack[2], ltrack[0]) * ldir;
      llen = (ltrack[1]) ? ltrack[2] : p_max;
      ra = arc_angle(p-rtrack[2], rtrack[0]) * rdir;
      rlen = (rtrack[1]) ? rtrack[2] : p_max;

      color(track_colors[lt+1][0])
      clear_track_points(ltrack[0], la, llen, ltrack[3], ltrack[4]);

      color(track_colors[rt+1][0])
      clear_track_points(rtrack[0], ra, rlen, rtrack[3], rtrack[4]);
    }
  }

// make this a module
// use intersection of inside_rail and gaurd rails
//   to create dips showing frog point

  // add guard rails for point rails
  for(frog=all_frogs)
  {
    if (show_guardrails && frog[0][2][2])
    {
      offset = rail_foot + guardrail_offset;
      cross0 = frog[0][2];
      cross1 = frog[0][3];
      t0 = tracks[frog[1][0][0]];
      t1 = tracks[frog[1][1][0]];
      dir0 = plusminus(t0[1]);
      dir1 = plusminus(t1[1]);
      r0 = t0[0];// + dir0*(t0[3]/2 - offset);
      r1 = t1[0];// + dir1*(t1[3]/2 - offset);

      color(track_colors[frog[1][1][0]+1][0])
      translate([cross0[2].x, cross0[2].y, jig_height-track_depth])
      rotate([0, 0, cross0[1]])
      {
        guardrail(guardrail_width, guardrail_length*0.75, jig_height, dir=-1, r=r0);
        translate([-t0[3]+offset*2, 0, 0])
        {
          guardrail(guardrail_width, guardrail_length*0.75, jig_height, dir=1, r=r0);
          rotate([0, 0, 180])
          guardrail(guardrail_width, guardrail_length*0.25, jig_height, dir=-1, r=r0);
        }
      }

      color(track_colors[frog[1][0][0]+1][0])
      translate([cross1[2].x, cross1[2].y, jig_height-track_depth])
      rotate([0, 0, cross1[1]])
      {
        guardrail(guardrail_width, guardrail_length*0.75, jig_height, dir=1, r=r1);
        translate([t0[3]-offset*2, 0, 0])
        {
          guardrail(guardrail_width, guardrail_length*0.75, jig_height, dir=-1, r=r0);
          rotate([0, 0, 180])
          guardrail(guardrail_width, guardrail_length*0.25, jig_height, dir=1, r=r0);
        }
      }
    }
  }

  // ties
  {
    for(i=[0:base_length/tie_spacing]) {
      tie(-half_tie, -(i*tie_spacing) + tie_offset);
    }

    for(i=[1:(jig_length/tie_spacing)+1]) {
      y = (i * tie_spacing) + tie_offset;

      edges = track_edges(y);
      lx = edges[0];
      rx = edges[1];

//      translate([edges[0], y, 0]) cylinder(h=10,d=0.5);
//      translate([edges[1], y, 0]) cylinder(h=10,d=0.5);
//      translate([lx, y, 0]) cylinder(h=10,d=0.5);
//      translate([rx, y, 0]) cylinder(h=10,d=0.5);
//echo("----------");
      //x = tie_width - gauge + lx - (rail_foot*0.75);
      x = lx - tie_length2;

      len = tie_length + (rx-lx);
/*
      len = (sameside<0)
//        ? rx-lx + tie_rail_diff*2 + gauge
        ? tie_length + (rx-lx)
        : tie_length + (rx-lx);
*/
//echo(i,"tie",y,lx,rx, len);
      tie(x, y, len);
    }
  }
  //echo("rail_foot",rail_foot);
  //echo("tie_rail_diff",tie_rail_diff);

  // switch stand
  color("#fd6")
  {
    // tie bar
    len = (track_count>0) ? tie_length*1.5 : tie_length*1.33;
    llen = (len/2 > jig_width_left-3) ? 5 : 0;
    rlen = (len/2 > jig_width_right-3) ? 5 : 0;
    translate([-llen, 0, tie_depth*0.5])
    tie(-len/2, (tie_spacing*1.5)+tie_offset, len+llen+rlen, mark=" ", override=tie_width*1.5);

    // switch stand
    tie(-jig_width_left-1, tie_spacing+tie_offset, jig_width*2, mark=" ");
    tie(-jig_width_left-1, (tie_spacing*2)+tie_offset, jig_width*2, mark=" ");
  }

  // mark exta PCB ties around switch stand
  mark_tie_y(-tie_spacing);
  mark_tie_y( tie_spacing*2);

  // jig identification
  if (track_count>0)
  {
    //side = (jig_width_left > jig_width_right) ? -1 : 1;
    side = plusminus(jig_width_right, jig_width_left);
    x = (side<0) ? -jig_width_left : jig_width_right;

    lbl = [
      for (t=tracks)
      //if (t[1] != 0)
        let(
          y = abs(curveY(t[0],t[1])),
          side = (t[1]==0) ? "═ S" : (t[1]<0) ? "L" : "R",
          offset = (!t[0] || !t[1] || !t[2]) ? ""
            : str(" (+",in(t[2]),"\")"),
          text1 = (t[1]==0) ? "STRAIGHT"
            : str(side, ": ", in(t[0]), "\""), // ◠
          text2 = (t[1]==0 || y>jig_length) ? ""
            : str(" - ",abs(t[1]), "°"),
          text = str(text1,text2,offset)
        )
        text
    ];
    // reverse order when side>0
    labels = (side<0) ? lbl
      : [ for(i=[0:len(lbl)-1]) lbl[len(lbl)-i-1] ];

    l = [ for(i=[0:len(labels)-1]) len(labels[i]) ];
    mx = (side>0) ? 0 : max(l) * 3.5;

    //echo("labels",mx,labels);

    translate([x, tie_offset + tie_spacing*4 + mx, jig_height-text_depth])
    {
      for(i=[0:len(labels)-1])
        translate([(4 + i*7)*-side, 0, 0])
        rotate([0, 0, 90*side])
          bold_text(text=labels[i],depth=text_depth*2);
    }
  } // end jig identification

  // label bottom of jig
  translate([jig_width_right-5.4, jig_length-6, -0.01])
  mirror([1,0,0])
    bold_text(text=str("Gauge: ",in(rail_standard_gauge*scale),"\""), size=4, depth=0.21);
  if (any_dual) {
    translate([jig_width_right-2, jig_length-12, -0.01])
    mirror([1,0,0])
      bold_text(text=str("nGauge: ",in(rail_narrow_gauge*scale),"\""), size=4, depth=0.21);
  }
  translate([jig_width_right-2, -base_extra+3, -0.01])
  mirror([1,0,0])
    bold_text(text=str("Model: [",use_track[0],",",use_track[1],"]"), size=4, depth=0.21);

}


//point_space(0, 1);
//point_space(1, -1);

module point_space(track, s=0)
{
  /*** make starting point at 0-tie_offset  ***/
  /*** to always end in middle of first tie ***/
  side = plusminus(s);
  dist = min(mm(3), first_frogY*0.75);
  clearance = point_clearance + rail_foot2;
  t = tracks[track];
  r0 = t[0];
  a0 = t[1];
  y0 = t[2];
  g = t[3];
  dual = t[4];
  dir = plusminus(a0);
  dadj = (dualgauge_common==0) ? 0
    : (dualgauge_common<0)
      ? (g==gauge) ? 0 : gauge_offset*side*-dir
      : (g==gauge) ? 0 : gauge_offset*-side*dir;
  //echo("dadj",dadj);

  color(track_colors[track+1][0])
  //color("#999")
  if (r0==0 || a0==0 || abs(a0)<5 || y0>dist-5) {
    // straight
    echo("point_space straight",side);
    xx0 = (g/2 + dadj*-dir) * side;
    xx1 = xx0 - clearance*side;
    x01 = (xx0+xx1) / 2;
    round_rail([xx1,0], [xx0,dist]);
    round_rail([x01,0], [xx0,dist]);

  } else if (y0) {
    echo("point_space leader",side);

  } else {
    // curve
    echo("point_space curve",side);
    xx0 = (g/2 + dadj*-dir) * side;
    yy0 = y0;
    rr0 = r0;
    xx1 = xx0 - clearance*side;
    yy1 = yy0;
    rr1 = rr0 * 1.25;

    aa0 = arc_angle(dist, rr0) * dir;
    xx2 = xx0+(curveX(rr0, aa0))*dir;
    yy2 = abs(curveY(rr0, aa0));

    x01 = (xx0+xx1) / 2;
    r01 = (rr0+rr1) / 2;
    curved_point(xx1,xx2,yy1,yy2,rr1*dir);
    curved_point(x01,xx2,yy1,yy2,r01*dir);

  }
}

module curved_point(xx1,xx2,yy1,yy2,r)
{
  //echo("curved_point()");
  //pin(xx1, yy1); echo("xx1,yy1",xx1,yy1);
  //pin(xx2, yy2); echo("xx2,yy2",xx2,yy2);
  //echo("r,dir",r,dir);
  dir = plusminus(r);
  rr1 = abs(r);

  dd = sqrt(sq(xx2-xx1) + sq(yy2-yy1));
  dxx = (dir<0) ? xx2-xx1 : xx1-xx2;
  ang = atan2(yy2-yy1, dxx);
  as = ang - acos(dd / (2*rr1));
  ae = ang + acos(dd / (2*rr1));
  at = (ae>90) ? as-(ae-180) : ae-as;
  arot = (dir<0) ? ae-180 : 180-ae;
  //echo("ang,as,ae",ang,as,ae);
  //echo("arot,at",arot,at);

  translate([xx1, yy1, jig_height-track_depth])
  rotate([0, 0, arot])
    arc(rr1*dir, at, rail_foot, jig_height);
}

module round_rail(p0, p1)
{
  translate([0,0,jig_height-track_depth])
  hull()
  {
    translate([p0.x-rail_foot2,p0.y]) cube([rail_foot,rail_foot, jig_height]);
    translate(p1) cylinder(h=jig_height, d=rail_foot);
  }
}



/*******************************/
/********** FUNCTIONS **********/
/*******************************/

function plusminus(v,x=0) = (v<x) ? -1 : 1;
function plus0minus(v,x=0) = (v==x) ? 0: plusminus(v,x);
function sq(n) = n * n;
function isdef(v) = (str(v)=="undef") ? false : true;
function constrain(val, a, b) = max(a, min(b, val));
function reduce(list) = [for (i=list) for(j=i) if(j!=[]) j];
function reverse(list) = [for (i = [len(list)-1:-1:0]) list[i]];
function mm(v=0) = v * 25.4;
function in(v=0) = v / 25.4;
function scalemm(val) = (val / scale) * 25.4;
function arcX(r,y) = r - sqrt(sq(r) - sq(y));
function curveX(r,a) = r - cos(a) * r;
function curveY(r,a) = sin(a) * r;
function arc_length(r,a) = r * a * (PI/180);
function arc_angle(l, r) = abs(l/r)*(180/PI);
function arc_radius(x0,y0,a0, x1,y1,a1) =
  let(
    mx = (x0+x1) / 2,
    my = (y0+y1) / 2,
    c = (y1-y0) / (x1-x0),
    b = tan((a0+a1) / 2),
    p = -1/c,
    cx = (my - mx*p + mx*b - my*b) / (b-p),
    cy = my + p*(cx-mx),
    r = sqrt(sq(cx-x0) + sq(cy-y0))
  )
  r;
function distance(x0,y0, x1,y1) = sqrt(sq(x1-x0) + sq(y1-y0));

function floor_zero(n) = (n<0) ? 0 : n;

function sum(v, dflt=0) = _sum(v,v[0]*0);
function _sum(v,_total,_i=0) =
  _i>=len(v) ? _total : _sum(v,_total+v[_i], _i+1);
function mean(v) = sum(v)/len(v);

function triangle(base, angle) =
  triangleA(base, base / cos(90-angle));
function triangleA(base, c) =
  sqrt(pow(c,2) - pow(base,2));

function rotateX(x0,y0, a, x1,y1) =
  x0 + (x1-x0) * cos(a) - (y1-y0) * sin(a);
function rotateY(x0,y0, a, x1,y1) =
  y0 + (x1-x0) * sin(a) + (y1-y0) * cos(a);

// point x,y; angle a, distance d
function lineX(x,y,a,d) = x + (d * cos(90+a));
function lineY(x,y,a,d) = y + (d * sin(90+a));

function straight_cross(l, r) = sqrt( (r*r) - ((r-l)*(r-l)) );
function curve_cross(x0,y0,r0, x1,y1,r1) =
  //echo("x0,y0,r0", x0,y0,r0)
  //echo("x1,y1,r1", x1,y1,r1)
  let(
    dx = x1 - x0,
    dy = y1 - y0,
    d = sqrt(sq(dx) + sq(dy)),
    a = (sq(r0) - sq(r1) + sq(d)) / (d*2),
    h = sqrt(abs(sq(r0) - sq(a))),

    x = (x0 + (dx * a/d)) + (-dy * h/d),
    y = abs((y0 + (dy * a/d)) + ( dx * h/d)),
    output = (d>r0+r1 || d<abs(r0-r1)) ? [] : [x,y]
  )
  //echo("d,a,h",d,a,h)
  //echo("x,y",x,y)
  output;

function straight_angle(x0,y0, x1,y1, x2,y2, x3,y3) =
  let(
    v0x = x1 - x0,
    v0y = y1 - y0,
    v1x = x3 - x2,
    v1y = y3 - y2,
    v2x = x0 - x2,
    v2y = y0 - y2,
    c = (v0x*v1y) - (v0y*v1x),
    u = (v1x*v2y - v1y*v2x) / c,
    x = x0+v0x*u,
    y = y0+v0y*u,

    output = (c == 0) ? []
      : (y < y0) ? []
      : [x, y]
  )
  output;

function curve_angle(x, y, cx, cy) =
  let(
    a = (atan2(y-cy, x-cx) + 360) % 360,
    deg = (a == 360) ? 0 : a,
    output = (deg<=90) ? deg : deg-180
  )
  output;

function make_line(x,y,a) =
  let(
    x0 = lineX(x,y,-a,-max_radius),
    y0 = lineY(x,y,-a,-max_radius),
    x1 = lineX(x,y,-a, max_radius),
    y1 = lineY(x,y,-a, max_radius)
  )
  [x0,y0,x1,y1];

function line_adj(line, adj) =
  [line[0]+adj.x, line[1]+adj.y, line[2]+adj.x,line[3]+adj.y];

function track_sort(list) =
  let(
    left = [for(t=list) if(t.y<0) t ],
    straight = [for(t=list) if(t.x==0 || t.y==0) t ],
    right = [for(t=list) if(t.y>0) t ]
    )
    concat(
      reduce(sort_by_index(left,0)),
      straight,
      reduce(reverse(sort_by_index(right,0)))
    );

function sort_by_index(l,idx) =
    len(l) == 0 ? [] :
    len(l) == 1 ? [l] : 
    let(
        pivot   = l[floor(len(l)/2)][idx],
        equal   = [ for(li=l) if( li[idx]==pivot) li ],
        lesser  = [ for(li=l) if( li[idx]< pivot) li ],
        greater = [ for(li=l) if( li[idx]> pivot) li ]
    )
    concat(
        sort_by_index(lesser,idx), 
        [equal], 
        sort_by_index(greater,idx)
    );

function track_min_radius(p) =
  let(
    m = min([for(i=p) (i.y<0) ? i.x-99999 : i.x ]),
    n = (m<0) ? m+99999 : m
  )
  search(n,p,0, 0)[0];

function track_max_radius(p) =
  let(
    m = max([for(i=p) (i.y<0) ? i.x-99999 : i.x ]),
    n = (m<0) ? m+99999 : m
  )
  search(n,p,0, 0)[0];

function track_min_angle(p) =
  let(m=min([for(i=p)i.y]))
  search(m,p,0, 1)[0];
function track_max_angle(p) =
  let(m=max([for(i=p)i.y]))
  search(m,p,0, 1)[0];

function same_side(tracks) =
  (
    (tracks[0][1]<0 && tracks[1][1]<0) ||
    (tracks[0][1]>0 && tracks[1][1]>0)
  ) ? -1 : 1;


function find_left_right(tracks=tracks) =
  // determine the left-most and right-most tracks
  let(
    t = track_sort(tracks),
    ltrack = t[0],
    rtrack = t[len(t)-1]
  )
  [ ltrack, rtrack ];

function track_edges(y=jig_length) =
  // find x for left/right tracks at position y
  // input: length from zero
  //echo("*** track_edges ***")
  let(
    l = outside_tracks[0],
    r = outside_tracks[1],
    sameside = same_side([l,r]),
    //ls = (l[1]<0) ? -1 : 1,
    //rs = (r[1]<0) ? -1 : 1,
    ls = plusminus(l[1]),
    rs = plusminus(r[1]),

    lg = (l[1]<0) ? -l[3] : l[3],
    lg2 = lg / 2,
    lr = l[0] + lg2,
    lo = l[2], // straight leader
    loffset = (l[4]) ? 0
      : (l[3]==gauge) ? 0 
        : (dualgauge_common==0) ? 0 : abs(gauge_offset),

    rg = (r[1]<0) ? -r[3] : r[3],
    rg2 = rg / 2,
    rr = r[0] - rg2,
    ro = r[2], // straight leader
    roffset = (r[4]) ? 0
      : (r[3]==gauge) ? 0
        : (dualgauge_common==0) ? 0 : abs(gauge_offset),


    top_ly = lo + abs(curveY(lr, l[1])),
    top_lcx = curveX(lr, l[1]),
    top_lyx = (y<=lo) ? 0 : arcX(lr,y-lo),
    top_lx = (top_ly<y) ? top_lcx-loffset : top_lyx-loffset,
    lx = (top_ly >= y) ? top_lx
    : top_lx + triangle(y-top_ly, 90-abs(l[1])),

    top_ry = ro + abs(curveY(rr, r[1])),
    top_rcx = curveX(rr, r[1]),
    top_ryx = (y<=ro) ? 0 : arcX(rr,y-ro),
    top_rx = (top_ry<y) ? top_rcx-roffset : top_ryx-roffset,
    rx = (top_ry >= y) ? top_rx
    : top_rx + triangle(y-top_ry, 90-abs(r[1])),

    _ = (DEBUG == 0) ? 0
      : (DEBUG != 0) ? 0
      : (!DEBUG > 2) ? 0
        : echo(str("--track_edges(",y,")"))
          echo("left,right",l,r)
          echo("top_x",top_lx,top_rx)
          echo("top_y",top_ly,top_ry)
          echo("offset",loffset,roffset)
  )
  [
    (lx<0) ? 0: ls*lx,
    (rx<0) ? 0: rs*rx
  ];

function tiepos(pos) =
  (pos * tie_spacing) + (tie_width/2);

//function lineline_cross(x0,y0, x1,y1, x2,y2, x3,y3) =
function lineline_cross(line0, line1) =
  let(
    x0 = line0[0], y0 = line0[1],
    x1 = line0[2], y1 = line0[3],
    x2 = line1[0], y2 = line1[1],
    x3 = line1[2], y3 = line1[3],
    d = ((y3-y2) * (x1-x0) - (x3-x2) * (y1-y0)),
    ua = ((x3-x2) * (y0-y2) - (y3-y2) * (x0-x2)) / d,
    ub = ((x1-x0) * (y0-y2) - (y1-y0) * (x0-x2)) / d,
    x = x0 + ua*(x1-x0),
    y = y0 + ua*(y1-y0),

    output = (d==0) ? []
      : (ua<0 || ua>1 || ub<0 || ub>1) ? []
      : [x,y]
  )
  output;

function linecurve_cross(x0,y0,x1,y1, cx,cy,cr) =
  let(
    v0x = x1 - x0,
    v0y = y1 - y0,
    v1x = x0 - cx,
    v1y = y0 - cy,
    b = -2*((v0x*v1x) + (v0y*v1y)),
    c =  2*((v0x*v0x) + (v0y*v0y)),
    d = sqrt(sq(b) - 2*c*(sq(v1x) + sq(v1y) - sq(cr))),

    u1 = (b-d) / c,
    u2 = (b+d) / c,
    x = (u2>=0 && u2<=1) ? x0+v0x*u2
      : (u1>=0 && u1<=1) ? x0+v0x*u1
      : [],
    y = (u2>=0 && u2<=1) ? y0+v0y*u2
      : (u1>=0 && u1<=1) ? y0+v0y*u1
      : [],

    output = [x,y]
  )
  //echo("linecurve_cross: output",output)
  //echo("u1", u1, x0+v0x*u1, y0+v0y*u1)
  //echo("u2", u2, x0+v0x*u2, y0+v0y*u2)
  output;

function curvecurve_cross(x0,y0,r0, x1,y1,r1) =
  let(
    wye = (x0<0 && x1>0) ? 1 : -1,
    yy = (y0+y1==0) ? 1 : wye,
    dir = plusminus(x0), //(x0<0) ? -1 : 1,

    dx = x1 - x0,
    dy = (y1 - y0) * yy,
    d = sqrt(sq(dx) + sq(dy)),
    a = ((sq(r0) - sq(r1)) + sq(d)) / (d*2),
    h = sqrt(abs(sq(r0) - sq(a))),

    x = (x0 + (dx * a/d)) + (-dy * h/d),
    y = abs( (-y0*dir + (dy * a/d)) + (dx * h/d) ),

    output = [x,y]
  )
  output;

//function straight_straight(line0, line1, a0 ,a1) =
function straight_straight(x0,y0, x1,y1,a01, x2,y2, x3,y3,a23) =
  let(
    line0 = make_line(x0,y0,a01),
    line1 = make_line(x2,y2,a23),
    xy = lineline_cross(x0,y0, x1,y1, x2,y2, x3,y3),

    d = ((y3-y2) * (x1-x0) - (x3-x2) * (y1-y0)),
    ua = ((x3-x2) * (y0-y2) - (y3-y2) * (x0-x2)) / d,
    ub = ((x1-x0) * (y0-y2) - (y1-y0) * (x0-x2)) / d,
    x = x0 + ua*(x1-x0),
    y = y0 + ua*(y1-y0),

    // find frog guard rails
    adj = guardrail_offset + rail_foot,
    d0 = (swap) ? a0+90 : a0-90,
    ox0 = x0 - lineX(x0,y0,d0, adj),
    oy0 = y0 - lineY(x0,y0,d0,-adj),
    bline0 = line_adj(line0, [ox0,oy0]),
    d1 = (swap) ? a1-90 : a1+90,
    ox1 = x1 - lineX(x1,y1,d1, adj),
    oy1 = y1 - lineY(x1,y1,d1,-adj),
    bline1 = line_adj(line1, [ox1,oy1]),

    b0 = lineline_cross(bline0, line1),
    b1 = lineline_cross(bline1, line0),

    output = (d==0) ? []
      : (ua<0 || ua>1 || ub<0 || ub>1) ? []
      : [xy.x, xy.y, [["S",-a01,b0],["S",-a23,b1]]]
  )
  output;

function straight_curve(x0,y0,a0, cx,cy,r, swap=false) =
  let(
    line0 = make_line(x0,y0,a0),

    xy = linecurve_cross(line0[0],line0[1],line0[2],line0[3], cx,cy,r),
    x = xy.x,
    y = xy.y,
    a1 = curve_angle(x, y, cx, cy),

    // find frog guard rails
    adj = guardrail_offset + rail_foot,
    dir = (swap) ? 1 : -1,
    b0 = linecurve_cross(x0+adj*dir,y0,x0+adj*dir,line0[3], cx,cy,r),
    b1 = linecurve_cross(x0,y0,x0,line0[3], cx,cy,r-adj),

    output = (xy==[]) ? []
      : (swap)
        ? [x,y, [["C",a1,b1],["S",a0,b0]]]
        : [x,y, [["S",a0,b0],["C",a1,b1]]],

    _ = (DEBUG == 0) ? 0
      : (DEBUG != 0)
        ? echo("--straight_curve()")
          echo("x0,y0,a0", x0,y0,a0)
          echo("cx,cy,r", cx,cy,r)
          echo("output",output)
          echo("swap",swap)
          (DEBUG > 1)
            ? echo("dx,dy", dx,dy)
              echo("d,a,h", d,a,h)
            : 0
      : 0
  )
  output;

function curve_curve(x0,y0,r0, x1,y1,r1) =
  let(
    xy = curvecurve_cross(x0,y0,r0, x1,y1,r1),
    x = xy.x,
    y = xy.y,
    a0 = curve_angle(x, y, x0, y0),
    a1 = curve_angle(x, y, x1, y1),

    // find frog guard rails
    adj = guardrail_offset + rail_foot,
    dir0 = plusminus(x0), //(x0<0) ? -1 : 1,
    dir1 = -plusminus(x1), //(x1>0) ? -1 : 1,
    b0 = curvecurve_cross(x0,y0,r0+adj*dir0, x1,y1,r1),
    b1 = curvecurve_cross(x0,y0,r0, x1,y1,r1+adj*dir1),

    output = (x!=x) ? [] : [x,y, [["C",a0,b0],["C",a1,b1]]],
    _ = (DEBUG == 0) ? 0
      : (DEBUG != 0)
        ? echo("--curve_curve()")
          echo("x0,y0,r0", x0,y0,r0)
          echo("x1,y1,r1", x1,y1,r1)
          echo("output",output)
      : (DEBUG > 1)
        ? echo("dx,dy", dx,dy)
          echo("d,a,h", d,a,h)
      : 0
  )
  output;

function curvestraight_straight(x0,y0,a0, x1,y1,a1, swap=false) =
  let(
    line0 = make_line(x0,y0,a0),
    line1 = make_line(x1,y1,a1),
    xy = straight_angle(line0[0],line0[1],line0[2],line0[3], line1[0],line1[1],line1[2],line1[3]),

    // find frog guard rails
    adj = guardrail_offset + rail_foot,
    d0 = (swap) ? a0+90 : a0-90,
    ox0 = x0 - lineX(x0,y0,d0, adj),
    oy0 = y0 - lineY(x0,y0,d0,-adj),
    bline0 = line_adj(line0, [ox0,oy0]),
    d1 = (swap) ? a1-90 : a1+90,
    ox1 = x1 - lineX(x1,y1,d1, adj),
    oy1 = y1 - lineY(x1,y1,d1,-adj),
    bline1 = line_adj(line1, [ox1,oy1]),

    b0 = lineline_cross(bline0, line1),
    b1 = lineline_cross(bline1, line0),

    output = (xy==[]) ? []
      : (swap)
        ? [xy.x ,xy.y, [["S",a1,b1],["S",-a0,b0]]]
        : [xy.x, xy.y, [["S",-a0,b0],["S",a1,b1]]],
    
    _ = (DEBUG == 0) ? 0
      : (DEBUG != 0)
        ? echo("-- curvestraight_straight()")
          echo("x0,y0,a0",x0,y0,a0)
          echo("x1,y1,a1",x1,y1,a1)
          echo("swap",swap)
          echo("output",output)
          echo("xy",xy)
          //echo("line0",line0)
          //echo("bline0",bline0)
          //echo("line1",line1)
          //echo("bline1",bline1)
      : 0
  )
  output;

function curvestraight_curve(x0,y0,a0, cx,cy,r, swap=false) =
  let(
    line0 = make_line(x0,y0,a0),
    xy = linecurve_cross(line0[0],line0[1],line0[2],line0[3], cx,cy,r),
    x = xy.x,
    y = xy.y,
    a1 = curve_angle(x, y, cx, cy),

    // find frog guard rails
    adj = guardrail_offset + rail_foot,
    d0 = (swap) ? a0+90 : a0-90,
    ox0 = x0 - lineX(x0,y0,d0, adj),
    oy0 = y0 - lineY(x0,y0,d0,-adj),
    bline0 = line_adj(line0, [ox0,oy0]),
    d1 = plusminus(cx), //(cx<0) ? -1 : 1,

    b0 = linecurve_cross(bline0[0],bline0[1],bline0[2],bline0[3], cx,cy,r),
    b1 = linecurve_cross(line0[0],line0[1],line0[2],line0[3], cx,cy,r-adj),

    output = (xy==[]) ? []
      : (swap)
        ? [x,y, [["C",a1,b1],["S",-a0,b0]]]
        : [x,y, [["S",-a0,b0],["C",a1,b1]]],

    _ = (DEBUG == 0) ? 0
      : (DEBUG != 0)
        ? echo("-- curvestraight_curve()")
          echo("x0,y0,a0",x0,y0,a0)
          echo("cx,cy,r",cx,cy,r)
          echo("swap",swap)
          echo("cs_c output",output)
          echo("bline0",bline0)
      : 0
  )
  echo("csc output",output)
  output;

function curvestraight_curvestraight(x0,y0,a0, x1,y1,a1) =
  let(
    line0 = make_line(x0,y0,a0),
    line1 = make_line(x1,y1,a1),
    xy = lineline_cross(line0, line1),

    // find frog guard rails
    adj = guardrail_offset + rail_foot,
    ox0 = x0 - lineX(x0,y0,a0-90, adj),
    oy0 = y0 - lineY(x0,y0,a0-90,-adj),
    bline0 = line_adj(line0, [ox0,oy0]),
    ox1 = x1 - lineX(x1,y1,a1+90, adj),
    oy1 = y1 - lineY(x1,y1,a1+90,-adj),
    bline1 = line_adj(line1, [ox1,oy1]),

    b0 = lineline_cross(bline0, line1),
    b1 = lineline_cross(bline1, line0),

    output = (xy==[]) ? []
      : [xy.x, xy.y, [["S",-a0,b0],["S",-a1,b1]]],

    _ = (DEBUG == 0) ? 0
      : (DEBUG != 0)
        ? echo("-- curvestraight_curvestraight()")
          echo("x0,y0,a0",x0,y0,a0)
          echo("x1,y1,a1",x1,y1,a1)
          echo("output",output)
          //echo("adj",adj)
          //echo("ox0,oy0",ox0,oy0)
          //echo("ox1,oy1",ox1,oy1)
      : 0
  )
  output;

function frog(rail0, rail1, group="std") =
// rail = [x, y, radius, angle, gauge]
  let(
    wye = (rail0[3]<0 && rail1[3]>0) ? 1 : 0,
    x0 = rail0[0],
    y0 = rail0[1],
    r0 = rail0[2],
    a0 = rail0[3],
    g0 = rail0[4],
    dir0 = plusminus(a0), //(a0<0) ? -1 : 1,
    cl0 = dir0 * (abs(x0) - r0),
    cx0 = dir0 * curveX(r0, a0) + cl0,
    cy0 = abs(curveY(r0, a0)) + y0,

    x1 = rail1[0],
    y1 = rail1[1],
    r1 = rail1[2],
    a1 = rail1[3],
    g1 = rail1[4],
    dir1 = plusminus(a1), //(a1<0) ? -1 : 1,
    cl1 = dir1 * (abs(x1) - r1),
    cx1 = dir1 * curveX(r1, a1) + cl1,
    cy1 = abs(curveY(r1, a1)) + y1,

    d0 = distance(cx0,cy0, x1,y1), // is cx,cy inside of r1?
    d1 = distance(x0,y0, cx1,cy1),
    cc = curve_curve(x0,y0,r0, x1,y1,r1),

    output = (a0 == 0)
        ? (cx1 > x0)
          ? echo("xy: S0, C1")
            straight_curve(x0,y0,a0, x1,y1,r1, false)
          : echo("xy: S0, C1-S")
            curvestraight_straight(cx1,cy1,a1, x0,y0,a0, true)
      : (a1 == 0)
        ? (cx0 < x1)
          ? echo("xy: C0, S1")
            straight_curve(x1,y1,a1, x0,y0,r0, true)
          : echo("xy: C0-S, S1")
            curvestraight_straight(cx0,cy0,a0, x1,y1,a1, false)
      : (cx0>x1 && cx1<x0)
          ? echo("xy: C0, C1")
            cc
      : ((cy0 < cc[1]) && (cy1 < cc[1]))
        ? echo("xy: C0-S, C1-S")
          curvestraight_curvestraight(cx0,cy0,a0, cx1,cy1,a1)
      : (cy0 < cc[1])
        ? echo("xy: C0-S, C1")
          curvestraight_curve(cx0,cy0,a0, x1,y1,r1, false)
      : (cy1 < cc[1])
        ? echo("xy: C0, C1-S")
          curvestraight_curve(cx1,cy1,a1, x0,y0,r0, true)
      : echo("xy: C0, C1")
        cc,

    _ = (DEBUG == 0) ? 0
      : (DEBUG != 0)
        ? echo("-- frog()")
          echo("rail0",rail0)
          echo("rail1",rail1)
          echo("group",group)
          echo("output",output)
          echo("cl0,cl1",cl0,cl1)
         (!DEBUG > 1) ? 0
          : echo("x0,y0,r0",x0,y0,r0)
            echo("x1,y1,r1",x1,y1,r1)
            echo("cxy0",cx0,cy0)
            echo("cxy1",cx1,cy1)
            echo("cc",cc)
      : 0
  )
  output;

function all_frogs() = [
  if (track_count>0)
  for(i=[0:track_count-1])
    let(
      si = (tracks[i][3] == gauge)
        ? (tracks[i][4] && dualgauge_common) ? [0,1,2] : [0,1]
        : (dualgauge_common<0) ? [0,2] : [1,2]
    )
    for(side0=si)
      let(
        a0 = tracks[i][1],
        dir0 = (a0 == 0) ? 0
          : plusminus(a0), //(a0 < 0) ? -1 : 1,
        gauge0 = gauge,
        r0 = (side0<2 || dir0==0) ? tracks[i][0] - (gauge0/2 * dir0)
          : (dir0 < 0)
            ? tracks[i][0] + (-dual_center_offset*dualgauge_common)
            : tracks[i][0] + ( dual_center_offset*dualgauge_common),
        x0 = (a0!=0) ? tracks[i][0] * dir0
          : (side0==0) ? -gauge2
          : (side0==1) ? gauge2
          : -dual_center_offset * dualgauge_common,
        y0 = tracks[i][2],
        g0 = (side0<2) ? gauge : dualgauge,
        rail0 = [ x0, y0, r0, a0, g0 ]
      )
      for(j=[i+1:track_count])
      if (i != j)
        let(
          sj = (tracks[j][3] == gauge)
            ? (tracks[j][4]) ? [0,1,2] : [0,1]
            : (dualgauge_common<0) ? [0,2] : [1,2]
        )
        for(side1=sj)
        if ((i!=0 || side0!=0) && (side0!=side1) && (j!=track_count || side1!=1))
          let(
            //_0 = (! DEBUG) ? 0
            _0 = (false) ? 0
              :echo(str("----- new[",i,side0,", ",j,side1,"] -----")),
            a1 = tracks[j][1],
            dir1 = (a1 == 0) ? 0
              : plusminus(a1), //(a1 < 0) ? -1 : 1,
            gauge1 = gauge,
            r1 = (side1<2 || dir1==0) ? tracks[j][0] + (gauge1/2 * dir1)
              : (dir1 < 0)
                ? tracks[j][0] + (-dual_center_offset*dualgauge_common)
                : tracks[j][0] + ( dual_center_offset*dualgauge_common),
            x1 = (a1!=0) ? tracks[j][0] * dir1
              : (side1==0) ? -gauge2
              : (side1==1) ? gauge2
              : -dual_center_offset * dualgauge_common,
            y1 = tracks[j][2],
            g1 = (side1<2) ? gauge : dualgauge,
            rail1 = [ x1, y1, r1, a1, g1 ],

            cc = reduce(frog(rail0,rail1,"std")),
            frog = (cc==[]) ? [] : [cc, [[i,side0],[j,side1]]]
          )
          //echo("frog cc", cc)
//echo("offset0,s0,r0,x0",offset0,s0,r0,x0)
//echo("offset1,s1,r1,x1",offset1,s1,r1,x1)
//echo("track i",i,si)
//echo("track j",j,sj)
          //reduce(frog)
          frog
  ];

function uniq_frogs(frogs, mx=0) =
  let( c = len(frogs)-1 )
  [
    if (c>=0)
    for (i=[0:c])
      let(
        s = search([frogs[i].x], frogs, 1, 0),
        pass = (
             frogs[i][0][1] > 0
  //        && frogs[i][0][1]<=mx
        )
      )
      if(s.x==i && pass) frogs[i]
  ];


/*************************
for(f=all_frogs)
if (f.y.y > 10)
{
  translate([f.y.x, f.y.y, 0])
  {
    color("red") cylinder(h=10,d=1);
    //rotate([0, 0, 172]) translate([-0.1,0,0]) cube([0.2, 300, jig_height]);
  }
//  translate([8.607, 0, 0])
//  rotate([0, 0, 180])
//    arc(tracks[0][0], 8, 0.2, jig_height);
}
/*************************/

//color("#f00") pin(-8.60655, 167.042);
//color("#f00") pin(8.60655, 70.4591); // [02, 11]
//color("#f00") pin(8.60655, 125.565); // [11, 20]


/*****************************/
/********** MODULES **********/
/*****************************/

module pin(x,y)
{
  //echo("PIN",x,y);
  translate([x,y,0]) cylinder(h=10,d=1);
}

module show_pins(y, CL, lx, rx)
{
  color("red")
  translate([CL, y, jig_height])
    cylinder(h=jig_height*2, r=0.25, center=true);

  color("#ccf")
  translate([-lx-gauge2, y, jig_height+1])
    cylinder(h=jig_height*2, r=0.25, center=true);

  color("#cfc")
  translate([rx+gauge2, y, jig_height+1])
    cylinder(h=jig_height*2, r=0.25, center=true);

}

module point2point(p0, p1, d)
{
  translate([0, 0, jig_height])
  hull()
  {
    translate(p0) sphere(d=d);
    translate(p1) sphere(d=d);
  }
}

module ring(d=10, wall=1)
{
  $fn=180;
  difference() {
    cylinder(h=jig_height, d=d+wall/2);
    translate([0, 0, -0.1]) cylinder(h=jig_height+0.2, d=d-wall/2);
  }
}

module roundedcube(x,y,z, r)
{
  hull()
  {
    translate([r,r,0]) cylinder(h=z, r=r);
    translate([r,y-r,0]) cylinder(h=z, r=r);
    translate([x-r,r,0]) cylinder(h=z, r=r);
    translate([x-r,y-r,0]) cylinder(h=z, r=r);
  }
}

module cylindercube(x,y,z)
{
  hull()
  {
    cylinder(h=z,d=x);
    translate([-x/2,y-2*x,0]) cube([x,x,z]);
//    translate([0, x/2-y/2, 0]) cylinder(h=z, d=x);
//    translate([0, y/2-x/2, 0]) cylinder(h=z, d=x);
  }
}


module bold_text(text="",depth=0.2,size=5,halign="left",valign="center",font="style=Bold")
{
  width = 0.25;

  translate([-width/2, 0, 0])
  linear_extrude(depth)
    text(text=text, size=size, halign=halign, valign=valign, font=font);

  linear_extrude(depth)
    text(text=text, size=size, halign=halign, valign=valign, font=font);
  
  translate([ width/2, 0, 0])
  linear_extrude(depth)
    text(text=text, size=size, halign=halign, valign=valign, font=font);
}

module guardrail(x,y,z, r=0, dir=1, double=false)
{
  //echo("guardrail r",r);
/*  
  if (r)
  {
    arc(r, dir*arc_angle(y, r), rail_foot, jig_height);
    
  } else
*/
  {
    cylindercube(x,y,z);
    translate([0,y-x,0])
    rotate([0, 0, -guardrail_point_angle*dir])
    {
      cylindercube(x,guardrail_point_length, z);
    }
  }
}

module arc(r, a, w, h)
{
  $fn = 360;
  m = (r<0) ? 0 : 1;
  d = (a<0) ? -r : r;

  translate([d, 0, h/2])
  mirror([0, m, 0])
  rotate_extrude(angle=a)
  translate([-d, 0, 0])
    square(size = [w, h], center = true);
}

module draw_all_tracks(inside=false)
{
  z = (inside) ? 0 : jig_height-track_depth;
  translate([0, 0, z])
  {
/*
    translate([0, -base_length-1, 0])
      //draw_track(0, 0, base[3], base[4]);
      straight_rail(base_length+1, base[3], base[4]);
*/
    // extra base track
    //translate([0, 0, -track_depth*2])
    mirror([0, 1, 0])
    {
      if (base[2] > 0)
        draw_track(0, 0, base[3], any_dual, inside=inside);
      else
        draw_track(base[0], base[1], base[3], any_dual, inside=inside);
    }

    // draw tracks
    for(i=[0:track_count])
    {
      track = tracks[i];
      // base offset
      color(track_colors[i+1][1])
        straight_rail(track[2], track[3], dual=track[4], inside=inside);

      // main track
      translate([0, track[2], 0])
          draw_track(track[0], track[1], track[3], dual=track[4], color=i+1, inside=inside);
    }
  }
}

module clear_track_points(r, angle, leader, gauge=gauge, dual=false)
{
  //echo("clear_track_points: r,a,l,g,d", r,angle,leader,gauge,dual);
  translate([0, 0, jig_height-track_depth])
  {
    straight_rail(leader, gauge, dual, inside=false);
    if (r && angle)
      translate([0, leader, 0])
      draw_track(r, angle, gauge, dual=dual, nostraight=true, inside=false);
    else straight_rail(leader, gauge, dual, inside=false);
  }

}

module draw_track(r, angle, gauge=gauge, dual=false, color=0, inside=false, nostraight=false)
{
  straight_x = (inside) ? inside_rail_width : 0;

  x1 = (angle<0) ? -1 : 1;
  x = curveX(r, abs(angle)) * x1;
  y = curveY(r, abs(angle));
  if (angle) {
    color(track_colors[color][0])
    curve_rail(r, angle, gauge=gauge, dual=dual, inside=inside);

    if (!nostraight)
    color(track_colors[color][1])
    translate([x, y, 0])
    rotate(-angle)
    translate([0, -0.1, 0])
        straight_rail(jig_length*2, gauge=gauge, dual=dual, inside=inside);

  } else {
    color(track_colors[color][0])
    straight_rail(jig_length*2, gauge=gauge, dual=dual, inside=inside);
//      if (dualgauge_common != 0)
//        straight_rail(jig_length);
  }
}

module straight_rail(len, gauge=gauge, dual=false, inside=false) {
  gauge2 = gauge / 2;
  offset = (gauge==dualgauge) ? gauge_offset : 0;
  channel = 0.4;
//echo("straight_rail()",len,gauge,dual);

  translate([offset, 0, 0])
  {
    if (inside)
    translate([-gauge2+rail_foot2, 0, 0])
      cube([inside_rail_width, len, jig_height*2]);
    else
    translate([-gauge2-rail_foot2, -0.01, 0])
    {
      cube([rail_foot, len + 0.02, jig_height*2]);
      //translate([rail_foot-flange_width, 0, -flange_depth])
      //  cube([flange_width, len + 0.02, jig_height+1]);

      if (use_rail_slots)
      {
        translate([0, 0, -0.2])
          cube([channel, len + 0.02, jig_height]);
        translate([rail_foot-channel, 0, -0.2])
          cube([channel, len + 0.02, jig_height]);
      }
    }

    if (inside)
    translate([gauge2-rail_foot2-inside_rail_width, 0, 0])
      cube([inside_rail_width, len, jig_height*2]);
    else
    translate([gauge2-rail_foot2, 0, 0])
    {
      cube([rail_foot, len + 0.02, jig_height*2]);
      //translate([0, 0, -flange_depth])
      //  cube([flange_width, len + 0.02, jig_height+1]);

      if (use_rail_slots)
      {  
        translate([0, 0, -0.2])
          cube([channel, len + 0.02, jig_height]);
        translate([rail_foot-channel, 0, -0.2])
          cube([channel, len + 0.02, jig_height]);
      }
    }
  }

  if (any_dual && dual && gauge!=dualgauge)
  {
    if (dualgauge_common > 0)
    if (inside)
    translate([-dual_center_offset+rail_foot2, 0, 0])
      cube([inside_rail_width, len, jig_height*2]);
    else
    translate([-dual_center_offset-rail_foot2, -0.01, 0])
    {
      cube([rail_foot, len + 0.02, jig_height*2]);
      //translate([rail_foot-flange_width, 0, -flange_depth])
      //  cube([flange_width, len + 0.02, jig_height+1]);

      if (use_rail_slots)
      {
        translate([0, 0, -0.2])
          cube([channel, len + 0.02, jig_height]);
        translate([rail_foot-channel, 0, -0.2])
          cube([channel, len + 0.02, jig_height]);
      }
    }

    if (dualgauge_common < 0)
    if (inside)
    translate([dual_center_offset-rail_foot2-inside_rail_width, 0, 0])
      cube([inside_rail_width, len, jig_height*2]);
    else
    translate([dual_center_offset-rail_foot2, 0, 0])
    {
      cube([rail_foot, len + 0.02, jig_height*2]);
      //translate([0, 0, -flange_depth])
      //  cube([flange_width, len + 0.02, jig_height+1]);

      if (use_rail_slots)
      {
        translate([0, 0, -0.2])
          cube([channel, len + 0.02, jig_height]);
        translate([rail_foot-channel, 0, -0.2])
          cube([channel, len + 0.02, jig_height]);
      }
    }
  }
}

module curve_rail(r, angle, gauge=gauge, dual=false, inside=false) {
  gauge2 = gauge / 2;
  rad = (angle<0) ? gauge2 : -gauge2;
  inside_rad = (angle<0) ? inside_rail_width : -inside_rail_width;
  offset = (gauge==dualgauge) ? gauge_offset : 0;
  inside_offset = rail_foot2 + inside_rail_width2;
  gauge_diff = gauge2 - dualgauge2;
  dir = plusminus(angle);
  channel = 0.4;
  chan_adj = (angle<0)
    ? channel/2 - rail_foot2
    : channel*0.375 - rail_foot2;

  translate([offset, 0, 0])
  {
    if (inside)
    translate([inside_offset-gauge2, 0, 0])
      arc(r-rad+inside_rad, angle, inside_rail_width, jig_height*2);
    else
    translate([-gauge2, 0, 0])
    {
      arc(r-rad, angle, rail_foot, jig_height*2);
      //  arc(r-rad-rail_foot-flange_width, angle, flange_width, jig_height+1);

      if (use_rail_slots)
      {
        translate([chan_adj, 0, -0.2])
          arc(r-rad+chan_adj, angle, channel, 0.4);
        translate([-chan_adj, 0, -0.2])
          arc(r-rad-chan_adj, angle, channel, 0.4);
      }
    }

    if (inside)
    translate([gauge2-inside_offset, 0, 0])
      arc(r+rad-inside_rad, angle, inside_rail_width, jig_height*2);
    else
    translate([gauge2, 0, 0])
    {
      arc(r+rad, angle, rail_foot, jig_height+1);
      //translate([rshift/1.5, 0, -flange_depth])
      //  arc(r+rad, angle, flange_width, jig_height+1);

      if (use_rail_slots)
      {
        translate([chan_adj, 0, -0.2])
          arc(r+rad+chan_adj, angle, channel, 0.4);
        translate([-chan_adj, 0, -0.2])
          arc(r+rad-chan_adj, angle, channel, 0.4);
      }
    }

    if (any_dual && dual && gauge!=dualgauge)
    //translate([0, 0, -track_depth*2])
    {
      rad = (angle<0) ? dualgauge2 : -dualgauge2;
      if (dualgauge_common > 0)
      if (inside)
      translate([-dual_center_offset+rail_foot2+inside_rail_width2, 0, 0])
        arc(r+dual_center_offset, angle, inside_rail_width, jig_height*2);
      else
      translate([-dual_center_offset, 0, 0])
      {
        arc(r+dual_center_offset, angle, rail_foot, jig_height*2);
        //translate([0, 0, -flange_depth])
        //  arc(r-rad, angle, flange_width, jig_height+1);
      }

      if (dualgauge_common < 0)
      if (inside)
      translate([dual_center_offset-rail_foot2-inside_rail_width2, 0, 0])
        arc(r-dual_center_offset, angle, inside_rail_width, jig_height*2);
      else
      translate([dual_center_offset, 0, 0])
      {
        arc(r-dual_center_offset, angle, rail_foot, jig_height*2);
        //translate([-0.75-shift/1.8, 0, -flange_depth])
        //  arc(r+rad, angle, flange_width, jig_height+1);
      }
    }
  }
}

module tie(x, y=0, len=tie_length, mark=false, override=false) {
  style = (show_all_ties || (show_pcb_ties && mark)) ? 1 : 0;
  depth = (show_all_ties || (show_pcb_ties && mark))
    ? 0 : (jig_height-tie_bottom)-hidden_tie_depth;
  color = (depth) ? "#a509" : "#940";
  w = (override) ? override : tie_width;

  color(color)
  translate([x, y-w/2, tie_bottom+depth])
  {
    //cube([len, w, jig_height]);
    diff = 0.6;
    if (style)
    hull()
    {
      cube([len, w, 0.1]);
      translate([0, -diff/2, jig_height])
        cube([len, w+diff, 0.1]);
    }
    else
    difference()
    {
      cube([len, w, jig_height]);

      if (hidden_tie_style>0)
      translate([0.5, 0.5, 0])
        cube([len-1, w-1, jig_height]);

      if (hidden_tie_style>1)
      translate([2.5, 0, 0])
        cube([len-5, w, jig_height]);
    }
      
    if (mark!=false && mark!=" ") marktie(len, mark);
  }
}

module marktie(len, mark=false) {
  diff = 2.5;
  translate([-diff, tie_width/2, 0])
  if (mark == true)
    mirror([1, 0, 0])
    cylinder(h=jig_height, r=tie_width/1.5, $fn=3);
  else if (mark=="x" || mark==">")
    color("red")
    translate([-diff, 4.5, -text_depth])
      markcut();
//    linear_extrude(height=jig_height)
//      text(text=mark, size=3, valign="center");
    

  translate([len+diff, tie_width/2, 0])
  if (mark == true)
    cylinder(h=jig_height, r=tie_width/1.5, $fn=3);
  else if (mark=="x" || mark=="<")
    color("red")
    translate([diff, 4.5, -text_depth])
      markcut();
//    linear_extrude(height=jig_height)
//      text(text=mark, size=4, valign="center");
}

module mark_tie_y(y, mark=true)
{
  tie_y = floor(y/tie_spacing) * tie_spacing + tie_spacing + tie_offset;
  edges = track_edges(tie_y);
  lx = edges[0];
  rx = edges[1];
  //x = tie_width - gauge + lx - (rail_foot*0.75);
  x = lx - tie_length2;
  tie_len = tie_length + (rx-lx);

  if (y < 0) {
    tie(-tie_length/2, tie_y, tie_len, mark=mark);
  } else if (mark == true) {
    tie(x, tie_y, tie_len, mark=mark);
  } else {
    tie_y2 = floor(y/tie_spacing2) * tie_spacing2 + tie_spacing2 + tie_offset;
    translate([x, tie_y2-tie_spacing, jig_height])
    marktie(tie_len, mark);
  }
}

module markcut() {
  len = tie_width * 0.75;
  width = 1;

  rotate(45)
  hull()
  {
    translate([0, -len, 0]) cylinder(h=text_depth*2, d=width);
    translate([0,  len, 0]) cylinder(h=text_depth*2, d=width);
  }

  rotate(-45)
  hull()
  {
    translate([0, -len, 0]) cylinder(h=text_depth*2, d=width);
    translate([0,  len, 0]) cylinder(h=text_depth*2, d=width);
  }
}

module point_spacing(ltrack, rtrack)
{
  //
  // add some extra at the bottom for longer points
  //
  
  a0 = ltrack[1];
  y0 = ltrack[2];
  g0 = ltrack[3];
  dir0 = plus0minus(a0);
  r0 = abs(ltrack[0] - (g0/2)*dir0);

  a1 = rtrack[1];
  y1 = rtrack[2];
  g1 = rtrack[3];
  dir1 = plus0minus(a1);
  r1 = abs(rtrack[0] + (g1/2)*dir1);

  dx0 = (a0<0) ? 0 : 1;
  dx1 = (a1<0) ? 1 : 0;
  c0 = (r0) ? r0 : r1;
  c1 = (r1) ? r1 : r0;
  // linecurve_cross(x0,y0,x1,y1, cx,cy,cr)
  p0 = straight_cross(rail_foot, c0);
  p1 = straight_cross(rail_foot, c1);
  //s0 = straight_cross(rail_foot, r0);
  //s1 = straight_cross(rail_foot, r1);
  //echo("***",s1+y1);
  deg0 = arc_angle(p0, c0);
  deg1 = arc_angle(p1, c1);
  curveX0 = curveX(r0, deg0*1.5);
  curveX1 = curveX(r1, deg1);
  curveY0 = curveY(r0, deg0*1.5);
  curveY1 = curveY(r1, deg1*1.5);
  rr0 = (curveY0 < y0) ? 0 : r0;
  rr1 = (curveY1 < y1) ? 0 : r1;
  ang0 = (dir0<0) ? 1.2 : -1.8;
  ang1 = (dir1<0) ? -1.8 : 1.2;
  p = max(curveY0, curveY1);

  width = rail_foot*0.875;

  cx0 = (rr0==0) ? g0/2
    : curveX(r0, deg0*1.5)-rail_foot*0.75 + dx0*(g0/2 + rail_foot*0.75);
  cy0 = (rr0==0) ? curveY1 : curveY0;
  cx1 = (rr1==0) ? curveX1-g1/2
    : -curveX(r1, deg1*1.5)+rail_foot*0.75 - dx1*(g1/2 + rail_foot*0.75);
  cy1 = (rr1==0) ? curveY0 : curveY1;
  os0 = (g0==gauge) ? 0 
    : (dualgauge_common<0)
      ? (dir0<0) ? gauge_offset*2 : gauge_offset
      : (dir0<0) ? 0 : gauge_offset;
  os1 = (g1==gauge) ? 0
    : (dualgauge_common<0)
      ? (dir1<0) ? gauge_offset : 0
      : (dir1<0) ? gauge_offset : gauge_offset*2;

echo("r0",ltrack[0], r0);
echo("r1",rtrack[0], r1);
echo("y",y0,y1);
echo("dir",dir0,dir1);
echo("dx",dx0,dx1);
echo("c",c0,c1);
echo("p",p0,p1,p);
echo("deg",deg0,deg1);
echo("curveX",curveX0,curveX1);
echo("curveY",curveY0,curveY1);
echo("rr",rr0,rr1);
echo("cx0,cy0",cx0,cy0);
echo("cx1,cy1",cx1,cy1);
echo("os",os0,os1);

  // show points where rails diverge
  if ($preview)
  {
    //color("#6e6")
    translate([-g1/2 + rail_foot2, p1, -1])
      cylinder(h=10, d=1);
    //color("#0cf")
    translate([g0/2 - rail_foot2, p0, -1])
      cylinder(h=10, d=1);
  }

  //color("#900")
  intersection()
  {
    // block to contain length of points
    color("#0000")
    translate([-gauge*2, tie_spacing+tie_offset, 0])
      cube([gauge*4, p, jig_height*2]);

    // left point
    union()
    {
    color("#6e6")
    translate([cx1+os1, cy1, 0])
    {
      if ($preview) cylinder(h=10, d=1);
      translate([0, 0, jig_height-track_depth])
      if (rr1==0)
        translate([width/2, cy0-cy1, 0])
        rotate([0, 0, 181.66])
        cube([width, cy1, track_depth*2]);
      else
      {
        rotate([0, 0, 180-deg1*ang1])
        arc(-r1*dir1, deg1*1.5, width, track_depth*2);
/*
        if (deg1*1.5 < cy1)
        {
          len = cy1 - deg1*13.5;
          translate([0, -deg1*13.4, 0])
          rotate([0, 0, 180])
          {
            translate([curveX1-width/2, 0, 0])
            cube([width, len, track_depth*2]);
            if ($preview) cylinder(h=10, d=1);
          }
        }
*/
      }
    }

    // right point
    color("#9ef")
    translate([cx0+os0, cy0, 0])
    {
      if ($preview) cylinder(h=10, d=1);
      translate([0, 0, jig_height-track_depth])
      if (rr0==0)
        translate([width/2, cy1-cy0, 0])
        rotate([0, 0, 178.33])
        cube([width, cy0, track_depth*2]);
      else
        rotate([0, 0, 180+deg0*ang0])
        arc(-r0*dir0, deg0*1.5, width, track_depth*2);
    }
    }
  }
}
//point_spacing(left, right);
