use <utilities.scad>

// A list of points describing the minkowski product of a square of side
// length `flat` and a circle of radius `r`, centred on `[0,0,z]`
function squircle_points_simple(r=1, flat=0, z=0, n_points=32) = [
    for(j=[0:n_points-1]) let(
        angle = 360 * j / n_points,
        unit_vector = [cos(angle), sin(angle), 0],
        corner = [sign(cos(angle)), sign(sin(angle)), 0] * flat/2
    ) corner + unit_vector * r + [0, 0, z]
];

// As with `squircle_points_simple` but spreading the points out, so we don't get
// degenerate points if `r=0`.
function squircle_points(r=1, flat=0, z=0, n_points=32, min_fragment=tiny()) = let(
    circumference = 2*PI*r + 4*flat,
    dc = min(min_fragment, circumference/n_points), // distance between points
    corner_points = min(PI*r/4/dc, n_points/8)      // number of points from corner to flat, i.e. 1/8 of a turn
    // if corner_points == n_points/8, this should give the same behaviour as squircle.
) assert(
    circumference > n_points * min_fragment, "Shape is too small!!"
) assert(
    r >= 0 && flat >= 0, "r and f must be non-negative!"
) [
    // We follow the same convention as the simple version: points start on the
    // x axis and work around anticlockwise.
    for(j=[0:n_points-1]) let(
        i=j+0.5, // This means that if n_points divides by four, we have points either side of the
        // flat sections, rather than having points on the x/y axes.
        // We keep the index of the corners the same, and expand points so they overflow onto the flats.
        nearest_corner = floor(4*i/n_points)/4 + 1/8, // which corner is closest? this will be (2j+1)/8 for j=0...3
        corner_i = nearest_corner * n_points, // what point would be on that corner?
        corner_angle = nearest_corner * 360,
        corner_centre = [sign(cos(corner_angle)), sign(sin(corner_angle))] * flat/2,
        // Having established the nearest corner, now we use our position relative to that corner.
        rel_i = i - corner_i, // index, relative to the nearest corner
        // If we weren't overflowing from the corners, rel_a would simply be rel_i * 360/n_points
        // We cap the magnitude of rel_a at +/- 45, so we don't go past the curved section.
        rel_a = min(abs(rel_i)/corner_points * 45, 45) * sign(rel_i), // The angle around the corner we are
        position_on_curve = corner_centre + [cos(rel_a + corner_angle), sin(rel_a + corner_angle)] * r,
        // If rel_i is large enough, we must also displace the point along the flat:
        flat_angle = (rel_a + corner_angle) + 90 * sign(rel_a), // The angle along the flat
        flat_distance = max(abs(rel_i) - corner_points, 0) * min_fragment,
        position_incl_flat = position_on_curve + [cos(flat_angle), sin(flat_angle)] * flat_distance
    ) [each position_incl_flat, z]
];



// Module: lighttrap_cylinder
// Usage: lighttrap_cylinder(r1, r2, h, ridge=1.5);
// Arguments:
//   r1 = the radius of the bottom of the shape (i.e. the bottom of the bottom truncated cone)
//   r2 = the inner radius of the top of the shape (i.e. the top of the top truncated cone)
//   h = the overall height
//   ---
//   ridge = The height and change in `r` of each ridge (the angle is fixed at 45 degrees)
// Description:
//   A shape made up of truncated cones to form a christmas-tree-like shape.
//   
//   This is designed to be subtracted from a solid block, to form a light path
//   that has minimal reflections from the walls of the cut-out, because the 
//   surfaces are angled.
//   
//   NB for a nominally "straight-edged" cylinder, you must set `r2 = r1 - ridge`.
// Example:
//    lighttrap_cylinder(5, 5-1.5, 21);
// Example:
//    difference(){
//        translate([-10, -10, 0]) cube(20);
//        lighttrap_cylinder(5, 5-1.5, 21);
//        translate([-99, -999, -1]) cube(999);
//    }
// NB the previous version of this function is preserved as old_lighttrap_cylinder,
// because it is much, much easier to read.  The geometry generated by this version
// will be subtly different (the ridge z positions may vary by up to `tiny()`) but
// it should be very similar.
module lighttrap_cylinder(r1, r2, h, ridge=1.5){
    lighttrap_sqylinder(r1, 0, r2, 0, h, ridge=ridge);
}

// Make a light trap but with flat sections on four sides, i.e. make it slightly
// square - so you can fade between square and circular apertures.
// It is made up of rounded truncated pyramids to form a
// square christmas-tree-like shape.
// Similar to lighttrap_cylinder each section has flat sides
// It can be subtracted from an object to create a square shaft that is
// good for trapping stray light in an optical path. The shaft rounded
// corners
// r1 is radius of cuvature of the bottom of the bottom pyramid
// f1 is the flat section of the bottom of the bottom pyramid
// r2 is radius of cuvature of the top of the top pyramid
// f2 is the flat section of the to of the top pyramid
// NOTE: to make a uniform width shaft set r2==r1-ridge and f1=f2
// ALSO NOTE: Each truncated pyramid is made by varying r, not f. As such
//     r1 must be greater than or equal to ridge
module lighttrap_sqylinder(r1, f1, r2, f2, h, ridge=1.5){
    // Set the number of ridges so that the height of each ridge is at least
    // `ridge`.  There is a minimum of 1, if h<ridge we effectively set ridge=h.
    n_cones = max(floor(h/ridge), 1);
    assert(n_cones > 1, "Error: light traps will not render correctly with <=1 ridge!");
    assert(r1>=ridge, "r1 is less than ridge, this will cause the light trap to fail");
    // The structure is actually h+2*tiny() tall, to match the old structure.
    // It starts at z=-tiny() and ends at z=h+tiny().
    cone_h = (h + 2*tiny())/n_cones;
    n_points = determine_number_of_fragments(max(r1, r2));  // honour $fn, $fa, $fs
    echo("n_points", n_points);
    function join_rings_with_quads(start1, start2, N) = [
        for(j=[0:N - 1]) [
                start1 + j,
                start2 + j,
                start2 + ((j + 1) % N),
                start1 + ((j + 1) % N)
            ],
    ];
    function circular_face(start, N, centre, direction=1) = let(
        d = (direction==1) ? 1 : 0
    ) [
        for(j=[0:N - 1]) [
            start + ((j + (1-d)) % N),
            start + ((j + d) % N),
            centre
        ]
    ];
    polyhedron(
        points=[
            for(i=[0:n_cones-1]) let(
                p=i/(n_cones-1),             // p=0 on bottom cone, 1 on top cone
                section_r1=r1*(1-p) + (r2+ridge)*p,  // radius of lower/outer ring
                section_r2=(r1-ridge)*(1-p) + r2*p,  // radius of upper/inner ring
                section_f=f1*(1-p) + f2*p,            // length of the flat section
                z=i*cone_h - tiny()          // NB these are the *bottoms* of the cones
            ) each concat(                   // Alternate between small and large rings
                squircle_points(section_r1, section_f, z=z, n_points=n_points),        // lower/outer ring
                squircle_points(section_r2, section_f, z=z+cone_h, n_points=n_points)  // upper/inner ring
            ),
            // Extra points at the top and bottom, so we can make nicely-triangulated circles
            [0,0,-tiny()],  //bottom
            [0,0,h+tiny()]  //top
        ],
        faces=[
            // Sloping faces
            for(i = [0:(2*n_cones - 2)])
                each join_rings_with_quads(i*n_points, (i+1)*n_points, n_points),
            // Bottom and top faces
            each circular_face(0, n_points, 2*n_points*n_cones, direction=1),
            each circular_face((2*n_cones - 1)*n_points, n_points, 2*n_points*n_cones + 1, direction=-1),
        ]
    );
}
// The module below should be identical to the one above. It is
// less robust and less efficient, but preserved because it is
// easier to read.
module old_lighttrap_cylinder(r1,r2,h,ridge=1.5){
    assert(false, "This module exists for documentation only, do not use.");
    //there must be at least one cone or we divide by zero
    n_cones = max(floor(h/ridge),1);
    cone_h = h/n_cones;

    for(i = [0 : n_cones - 1]){
        p = i/(n_cones - 1);
        section_r1 = (1-p)*r1 + p*(r2+ridge);
        section_r2 = (1-p)*(r1-ridge) + p*r2;
        translate_z(i * cone_h - tiny()){
            cylinder(r1=section_r1, r2=section_r2, h=cone_h+2*tiny());
        }
    }
}

module old_lighttrap_sqylinder(r1,f1,r2,f2,h,ridge=1.5){
    // This is an older version of lighttrap_sqylinder, kept in case it helps
    // make it clearer what is happening - it's more readable than the replacement,
    // though not as performant.
    //A shape made up of rounded truncated pyramids to form a
    //square christmas-tree-like shape.
    //Similar to lighttrap_cylinder each section has flat sides
    //It can be subtracted from and object to create a square shaft that is
    //good for trapping stray light in an optical path. The shaft rounded
    //corners
    //r1 is radius of cuvature of the bottom of the bottom pyramid
    //f1 is the flat section of the bottom of the bottom pyramid
    //r2 is radius of cuvature of the top of the top pyramid
    //f2 is the flat section of the to of the top pyramid
    //NOTE: to make a uniform width shaft set r2==r1-ridge and f1=f2
    //ALSO NOTE: Each truncated pyramid is made by varying r, not f. As such
    //    r1 must be greater than or equal to ridge
    assert(false, "This module exists for documentation only, do not use.");

    assert(r1>=ridge, "r1 is less than ridge this will cause the light trap to fail");
    //there must be at least one cone or we divide by zero
    n_cones = max(floor(h/ridge),1);
    cone_h = h/n_cones;

    for(i = [0 : n_cones - 1]){
        p = i/(n_cones - 1);
        section_r1 = (1-p)*r1 + p*(r2+ridge);
        section_r2 = (1-p)*(r1-ridge) + p*r2;
        section_flat_l = ((1-p)*f1 + p*f2);
        translate_z(i * cone_h - tiny()){
            minkowski(){
                cylinder(r1=section_r1, r2=section_r2, h=cone_h);
                cube([section_flat_l, section_flat_l, 2*tiny()], center=true);
            }
        }
    }
}

// The following code was very useful when developing this module, so I could
// check it rendered correctly.
//$fn=16;
//translate_x(0)  lighttrap_cylinder(10, 6, 20);
//translate_x(30)  lighttrap_sqylinder(6, 8, 4.5, 8, 20);
//translate_x(60)  lighttrap_sqylinder(10, 0, 0, 20, 20);
//translate_x(90)  lighttrap_sqylinder(1.5, 17, 10, 0, 20);