MoebiusArray

include<GHOUL/Config.scad>

// Set the viewpoint and animation.
$vpt=[0,0,0];
// Animation is a simple viewpoint rotation, two Z-turns to see the entire surface of the Möbius band, one simultaneous Y-turn to keep the current 'Index' facing the viewer.
$vpr=[0,0+$t*360,$t*720];
$vpd=170;
// Parse the rotation angles for display.
vpr=[round($vpr.x%360),round($vpr.y%360),round($vpr.z%360)];

// Set a few parameters.
Width=6;
Thickness=0.9;
// Pad a 2D ellipse vertex array to 3D.
Elly=PadArray(Ellipse(20,30,true,true),3,0);
TotalVertices=len(Elly);
Len=TotalVertices-1;

// Möbius band makes 180 degree twist in one full turn.
// Generate an array with JUST the offset vectors to width/2.
// An offset vector, perpendicular to the curve and in the Z=0 plane is rotated over an angle proportional to the current vertex number over the array.
Nelly=[ for(I=[0:Len])
        AffineTransformArray(
            RotationMatrix(
            // Rotation axis is vector between current vertex and the previous.
            MoebiusArray(Elly,I+1)-Elly[I],
                // Angle is proportional to vertex number.
                I/(Len+1)*180
            ),
            // The offset vector is perpendicular to the curve in Z=0 plane.
            [LPerpVector(MoebiusArray(Elly,I+1)-Elly[I],Width/2)]
        // The 'TransformArray' is only one vector...
        )[0]
];
// As above, but now rotate a small vertical (i.e. perpendicular to the width-offset vector) vector representing the band's thickness.
// Offset to thickness/2.
Telly=[ for(I=[0:Len])
        AffineTransformArray(
            RotationMatrix(
            // Rotation axis vector and angle.
            MoebiusArray(Elly,I+1)-Elly[I],
                I/Len*180
            ),
            // Offset vector.
            [[0,0,Thickness/2]]
        )[0]
];
// Add +/- offsets for width and thickness to generate the 4 curves forming the 4 corners of the band cross-section.
Jelly=[for(I=[0:Len]) Elly[I]+Nelly[I]-Telly[I]];
Belly=[for(I=[0:Len]) Elly[I]-Nelly[I]-Telly[I]];
Kelly=[for(I=[0:Len]) Elly[I]+Nelly[I]+Telly[I]];
Helly=[for(I=[0:Len]) Elly[I]-Nelly[I]+Telly[I]];
// I think I've ran out of five lettered names ending with 'elly'...
// Concat all vertices.
Vertices=concat(Jelly,Belly,Helly,Kelly);
// Define bottom, side and top faces and stitch the ends together.
Faces=[
    for(I=[0:Len-1]) // Bottom faces.
        [I,I+TotalVertices,I+TotalVertices+1,I+1],
    for(I=[0:Len-1]) // Near side faces.
        [I+TotalVertices,I+2*TotalVertices,I+2*TotalVertices+1,I+TotalVertices+1],
    for(I=[0:Len-1]) // Top faces.
        [I+2*TotalVertices,I+3*TotalVertices,I+3*TotalVertices+1,I+2*TotalVertices+1],
    for(I=[0:Len-1]) // Far side faces.
        [I+3*TotalVertices,I,I+1,I+3*TotalVertices+1],
        // The last four faces are not 'regular' as one end has made a 180 degree rotation. Stitch them together explicitly.
        [3*TotalVertices-1,4*TotalVertices-1,1*TotalVertices,0],
        [4*TotalVertices-1,1*TotalVertices-1,2*TotalVertices,TotalVertices],
        [1*TotalVertices-1,2*TotalVertices-1,3*TotalVertices,2*TotalVertices],
        [2*TotalVertices-1,3*TotalVertices-1,0,3*TotalVertices]
];

// Generate the 'Indices' text.
for(I=[0:41]){
    // Adjust spacing.
    PStep=7.56;
    // Find the nearest vertex on the original curve to place the text.
    Vertex=FindCurveVertex(Elly,I*PStep,true);
    // We need two points for a rotation vector.
    Node1=MoebiusArray(Elly,Vertex);
    Node2=MoebiusArray(Elly,Vertex+1);
    Vector=Node2-Node1;
    // The rotation is proportional to vertex number over array length, like above.
    Roll=Vertex/(Len+1)*180;

    // Give the 'currently viewer facing' Index some colour.
    color(((I)/42<$t&&(I+1)/42>$t)?"red":"gold")
    // Place the text.
    Position(Vector,Node1,Roll)
    // Align text with +Z-axis for Position(), above.
    rotate([90,0,-90])
    // Small correction to center text and raise it above the band surface.
    translate([-0.15,0,0.2])
    SimpleText(str("[",I,"]"),1.5,Thickness=0.5,Center=true,Font="Noto Mono",Spacing=0.8);
}

// Generate the Möbius band.
color("darkslategrey")
polyhedron(Vertices,Faces,5);

// Visualise the coordinate system.
CoordinateSystem(Size=10,Radius=0.25,Point=0.2,Polar=false);

// Put some info in the picture.
// Compensate for viewpoint rotation to keep text facing torward the user during animation.
rotate($vpr)
// Put it in the top-right corner.
translate([26,30,0]){
    // Put the square behind the text.
    translate([0,0,-1])
    color("darkslategrey",0.7)
    square([22,5],center=true);
    // Show rotation angles.
    SimpleText(str(vpr),Size=2,Thickness=0.2,Center=true,Font="Noto Mono",Spacing=0.8);
}