0

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());

  • I don't understand what is exactly your question : do you look for the area of an oblique shadow of the volume ? 2) Why the term "zonogonal cylinder" instead of "rectangular prism" which is usual ?
  • – Jean Marie Aug 11 '23 at 21:08
  • If you rotate a zonogonal cylinder, and find the shadow created on the xy-plane by an orthographic projection, what is the area of that shadow? I don't care about perspective. 2) I used zonogonal cylinder because not all of the faces are rectangles. Exactly two faces are identical zonogons that are not necessarily rectangles.
  • – Teg Louis Aug 11 '23 at 21:21
  • But in your 4 examples, the faces look rectangular ! – Jean Marie Aug 11 '23 at 21:22
  • Focus at the tops and bottoms. The tops and bottoms are not rectangular. They are hexagons, octagons, decagons, and 12-gons respectively. – Teg Louis Aug 11 '23 at 21:24
  • 1
    The result should as you said in your question, the sum (over half the faces) of the area of the face multiplied by the dot product of the rotated normal vector of and the $z$ unit vector which is $\mathbf{k}$. This last-mentioned quantity is of course the $z$ component of the rotated normal vector to the face. –  Aug 11 '23 at 21:28
  • @HosamH Why is it still half of the faces? I can visually see that you are correct. But I am trying to think why. – Teg Louis Aug 11 '23 at 21:33
  • 1
    It is actually because of the symmetry of this polyhedron about its center. –  Aug 11 '23 at 22:16