Orthogonal Projection Area of a Zonogonal Cylinder

37 Views Asked by At

A zonogon is a convex polygon that is made up of pairs of parallel line segments that are congruent. A zonogonal cylinder is a cylinder with identical and identically aligned zonogons as the end caps. A cuboid is a special case of a zonogonal cylinder. Thus, this is a generalization of the question: Orthogonal Projection Area of a 3D Cuboid.

Here are four examples of a zonogonal cylinder:

6-zonogonal cylinder 8-zonogonal cylinder 10-zonogonal cylinder 12-zonogonal cylinder

Both zonogons and zonogonal cylinders are centrally symmetric around an axis. And the orthographic projection of a zonogonal cylinder is also always a zonogon. The minimum amount of sides of a $2n$-zonogonal cylinder's orthographic projection is always 4 because from one side it can be a rectangle. And I believe the maximum number of sides of the orthographic projection is exactly $2n+2$.

The format of the solution likely follows the same format for the cuboid, like: $$\sum{(\text{face area}\cdot\prod{\text{trig functions}})}$$

I am giving the code to calculate it numerically in Mathematica as well as in JavaScript so that way it is easier to understand and accessible.

MATHEMATICA CODE:

First, have to create a random zonogon. This is from the SHuisman's code:

ClearAll[CreateRandomZonogon]
CreateRandomZonogon[sides_?EvenQ, lendist : {min_, max_}] := 
 Module[{m, angles, dirs, lengths},
  m = sides/2;
  angles = RandomReal[{0, 1}, m];
  angles /= Total[angles]/(Pi);
  angles = Join[angles, angles];
  dirs = Accumulate[angles];
  dirs += RandomReal[{0, 2 Pi}];
  lengths = RandomReal[lendist, m];
  lengths = Join[lengths, lengths];
  Polygon[Accumulate[MapThread[AngleVector[{#1, #2}] &, {lengths, dirs}]]]
  ] 

Now, we can numerically find the area:

endcapPolygonShape = CreateRandomZonogon[6, {0.5, 10}];
endcapPolygonShapeCoordinates = PolygonCoordinates[endcapPolygonShape];
zonogonalCylinderHeight = RandomInteger[{1, 10}];
bottomZonogalEndcap = Map[Append[#, 0]&,endcapPolygonShapeCoordinates];
topZonogalEndcap = Map[Append[#, zonogonalCylinderHeight]&,endcapPolygonShapeCoordinates];
zonogonalCylinderPoints = Join[bottomZonogalEndcap,topZonogalEndcap];
α = RandomReal[{0,2π}];
β = RandomReal[{0,2π}];
γ = RandomReal[{0,2π}];
rotationMatrixTransorm = Transpose[RollPitchYawMatrix[{β, α, γ},{3,2,1}]];
rotatedBoxPoints = Dot[zonogonalCylinderPoints,rotationMatrixTransorm]; 
xyProjectionPoints = Drop[rotatedBoxPoints,0,-1];
silouetteArea = Area[ConvexHullRegion[xyProjectionPoints]]

JAVASCRIPT CODE:

NOTE: The made first function to create the random zonogon by taking Vitaliy Kaurov's 'stretching' approach in Mathematica and converted it.

function createZonogon(n) {
    let pointsArr = [];
    let rotateAngle = Math.random() * 360;
    let scaleX = 0.5 + Math.random();
    let scaleY = 0.5 + Math.random();
    let skewX = Math.random() * 60 - 30;
    let skewY = Math.random() * 60 - 30;

    let radianRotate = (Math.PI / 180) * rotateAngle;
    let sinRotate = Math.sin(radianRotate);
    let cosRotate = Math.cos(radianRotate);

    let radianSkewX = (Math.PI / 180) * skewX;
    let radianSkewY = (Math.PI / 180) * skewY;

    for (let i = 0; i < n; i++) {
        let angle = 2 * Math.PI * i / n;
        let x = 250 + 100 * Math.cos(angle);
        let y = 250 + 100 * Math.sin(angle);

        // Apply transformations directly:

        // Rotation
        let rotatedX = cosRotate * x - sinRotate * y;
        let rotatedY = sinRotate * x + cosRotate * y;

        // Scaling
        let scaledX = rotatedX * scaleX;
        let scaledY = rotatedY * scaleY;

        // Skewing
        let skewedX = scaledX + scaledY * Math.tan(radianSkewX);
        let skewedY = scaledY + scaledX * Math.tan(radianSkewY);

        pointsArr.push([skewedX, skewedY]);
    }

    return pointsArr;
}

    function rotate3DPoints(points, alpha, beta, gamma) {
        // Define the rotation matrix based on alpha, beta, and gamma
let matrix = [
    [
        Math.cos(alpha) * Math.cos(beta),
        Math.cos(gamma) * Math.sin(beta) + Math.cos(beta) * Math.sin(alpha) * Math.sin(gamma),
        -Math.cos(beta) * Math.cos(gamma) * Math.sin(alpha) + Math.sin(beta) * Math.sin(gamma)
    ],
    [
        -Math.cos(alpha) * Math.sin(beta),
        Math.cos(beta) * Math.cos(gamma) - Math.sin(alpha) * Math.sin(beta) * Math.sin(gamma),
        Math.cos(gamma) * Math.sin(alpha) * Math.sin(beta) + Math.cos(beta) * Math.sin(gamma)
    ],
    [
        Math.sin(alpha),
        -Math.cos(alpha) * Math.sin(gamma),
        Math.cos(alpha) * Math.cos(gamma)
    ]
];
    
        // Apply the rotation matrix to the points
        return points.map(point => {
            let x = point[0];
            let y = point[1];
            let z = point[2];
            
            let rotatedX = matrix[0][0] * x + matrix[0][1] * y + matrix[0][2] * z;
            let rotatedY = matrix[1][0] * x + matrix[1][1] * y + matrix[1][2] * z;
            let rotatedZ = matrix[2][0] * x + matrix[2][1] * y + matrix[2][2] * z;
    
            return [rotatedX, rotatedY, rotatedZ];
        });
    }
    
        
    function computeCentroid(points) {
        let xSum = 0, ySum = 0;
        for (let p of points) {
            xSum += p[0];
            ySum += p[1];
        }
        return [xSum / points.length, ySum / points.length];
    }
    
    function sortPointsByAngle(points, centroid) {
        points.sort((a, b) => {
            return Math.atan2(a[1] - centroid[1], a[0] - centroid[0]) - Math.atan2(b[1] - centroid[1], b[0] - centroid[0]);
        });
    }
    
        function projectTo2D(points) {
            return points.map(p => [p[0], p[1]]);
        }
        
        function shoelace(points) {
            let n = points.length;
            let area = 0;
            for (let i = 0; i < n - 1; i++) {
                area += points[i][0] * points[i+1][1] - points[i+1][0] * points[i][1];
            }
            area += points[n-1][0] * points[0][1] - points[0][0] * points[n-1][1];
            return 0.5 * Math.abs(area);
        }
        
function computeSilhouetteArea() {
    let zonogon = createZonogon(6);
    let height = Math.floor(Math.random() * 10) + 1;
    
    // Convert to 3D and extrude
    let allPoints = zonogon.map(p => [p[0], p[1], 0]).concat(zonogon.map(p => [p[0], p[1], height]));
    
    // Random rotations
    let alpha = Math.random() * 2 * Math.PI;
    let beta = Math.random() * 2 * Math.PI;
    let gamma = Math.random() * 2 * Math.PI;
    
    let rotatedPoints = rotate3DPoints(allPoints, alpha, beta, gamma);
    let projectedPoints = projectTo2D(rotatedPoints);
    let centroid = computeCentroid(projectedPoints);
    
    sortPointsByAngle(projectedPoints, centroid);
    
    return shoelace(projectedPoints);
}

console.log(computeSilhouetteArea());