Hello, so I have a code here, that I’ve been working on with AI, and basically I’ve gotten into a little trouble modeling the famous arches forming at the top of the Twin Towers..
Here’s an attached photo for the base columns and their arches, which appear to be right.
My issue is at the top of the building, it has arches too:
I’m hoping someone good with loops and applying geometry with a pattern could help me solve this.. I need the top arches to look like this, but I’ve made a mess of the loop with AI.
You can view the roof here:
360-degree Panorama from the roof of the New York World Trade Center - World Trade Center (1973–2001) - Wikipedia
Now, here’s my entire function for the Twin Towers, I apologize, as it’s not entirely modeled or accurate, I used AI to build it in THREE:
function buildTwinTower(bldgW, bldgL, coreW, coreL, pColSize, cColSize, towerH, cx, cz, bedrockDepth, f1H, f2H, typH, isNorthTower) {
const towerGrp = new THREE.Group();
const chamfer = 117; // The historic exact chamfer geometry (9 ft 9 inches)
// Shared structural layout bounds for perfect vertical alignment
let fwOuter = bldgW / 2;
let flOuter = bldgL / 2;
let pColStartX = cx - fwOuter + chamfer + (pColSize / 2);
let pColStartZ = cz - flOuter + chamfer + (pColSize / 2);
let pColSpanX = bldgW - 2 * chamfer - pColSize;
let pColSpanZ = bldgL - 2 * chamfer - pColSize;
// Upper column spacing calculated first so we can use it to offset the lobby bases
let spaceColX = pColSpanX / 58;
let spaceColZ = pColSpanZ / 58;
// Lobby & Crown columns shifted inward by exactly 2 upper column spaces
let lobbyStartX = pColStartX + (2 * spaceColX);
let lobbyStartZ = pColStartZ + (2 * spaceColZ);
let lobbySpanX = pColSpanX - (4 * spaceColX);
let lobbySpanZ = pColSpanZ - (4 * spaceColZ);
let lobbySpaceX = lobbySpanX / 18;
let lobbySpaceZ = lobbySpanZ / 18;
// Architectural heights for the top crown sections
let mechGapStart = towerH - (4 * typH);
let mechGapHeight = 3 * typH;
let topFloorStart = towerH - typH;
// ==========================================
// FOUNDATION: INDIVIDUAL BEDROCK CORE COLUMNS
// ==========================================
let fdnCoreGeo = new THREE.BoxGeometry(cColSize * 1.5, bedrockDepth, cColSize * 1.5);
let footingGeo = new THREE.BoxGeometry(cColSize * 3.5, 48, cColSize * 3.5);
extraGeoms.push(fdnCoreGeo, footingGeo);
let coreStartX = cx - (coreW / 2) + (cColSize / 2);
let coreStartZ = cz - (coreL / 2) + (cColSize / 2);
for(let i = 0; i < 5; i++) {
for(let j = 0; j < 9; j++) {
let px = coreStartX + i * ((coreW - cColSize) / 4);
let pz = coreStartZ + j * ((coreL - cColSize) / 8);
let fdnCol = new THREE.Mesh(fdnCoreGeo, MATS.coreMetal);
fdnCol.position.set(px, -bedrockDepth/2, pz);
fdnCol.userData.layer = 'foundation';
let footing = new THREE.Mesh(footingGeo, MATS.concrete);
footing.position.set(px, -bedrockDepth + 24, pz);
footing.userData.layer = 'foundation';
towerGrp.add(fdnCol, footing);
}
}
// ==========================================
// GOTHIC ARCH (TRIDENT) GENERATOR
// ==========================================
// Master function to generate both the lobby and the mechanical gap tridents
const createTridentGeo = (spaceSpan, height, colW, baseW, depth) => {
let shape = new THREE.Shape();
// Start at bottom left of the wide base
shape.moveTo(-baseW, 0);
// Left branch outer sweep going up and out
shape.bezierCurveTo(-baseW, height * 0.4, -spaceSpan - colW/2, height * 0.6, -spaceSpan - colW/2, height);
shape.lineTo(-spaceSpan + colW/2, height);
// Left branch inner sweep coming back down to the inner crotch
shape.bezierCurveTo(-spaceSpan + colW/2, height * 0.6, -colW/2, height * 0.4, -colW/2, height * 0.2);
// Center prong going straight up
shape.lineTo(-colW/2, height);
shape.lineTo(colW/2, height);
shape.lineTo(colW/2, height * 0.2);
// Right branch inner sweep going up and out
shape.bezierCurveTo(colW/2, height * 0.4, spaceSpan - colW/2, height * 0.6, spaceSpan - colW/2, height);
shape.lineTo(spaceSpan + colW/2, height);
// Right branch outer sweep coming back down to the base column
shape.bezierCurveTo(spaceSpan + colW/2, height * 0.6, baseW, height * 0.4, baseW, 0);
shape.lineTo(-baseW, 0); // Close
let geo = new THREE.ExtrudeGeometry(shape, { depth: depth, bevelEnabled: false });
geo.translate(0, 0, -depth / 2); // Center perfectly in depth
return geo;
};
// ==========================================
// UNIFIED BASE & LOBBY PERIMETER COLUMNS
// ==========================================
let archH = f1H * 0.45; // The upper 45% of the lobby height is the branching arch
let baseColH = f1H * 0.55; // The lower 55% is the straight massive base column
let lobbyTridentGeoX = createTridentGeo(spaceColX, archH, pColSize, pColSize * 1.1, pColSize * 1.6);
let lobbyTridentGeoZ = createTridentGeo(spaceColZ, archH, pColSize, pColSize * 1.1, pColSize * 1.6);
let fdnLobbyColGeo = new THREE.BoxGeometry(pColSize * 2.5, bedrockDepth, pColSize * 2.5);
let perimFootingGeo = new THREE.BoxGeometry(pColSize * 5, 48, pColSize * 5);
let lobbyColGeo = new THREE.BoxGeometry(pColSize * 2.2, baseColH, pColSize * 2.2);
extraGeoms.push(fdnLobbyColGeo, perimFootingGeo, lobbyColGeo, lobbyTridentGeoX, lobbyTridentGeoZ);
const addBasePerimeterCol = (px, pz, rotY, isXFace) => {
let fCol = new THREE.Mesh(fdnLobbyColGeo, MATS.metal);
fCol.position.set(px, -bedrockDepth/2, pz);
fCol.rotation.y = rotY;
fCol.userData.layer = 'foundation';
let pFooting = new THREE.Mesh(perimFootingGeo, MATS.concrete);
pFooting.position.set(px, -bedrockDepth + 24, pz);
pFooting.rotation.y = rotY;
pFooting.userData.layer = 'foundation';
let lCol = new THREE.Mesh(lobbyColGeo, MATS.metal);
lCol.position.set(px, baseColH / 2, pz);
lCol.rotation.y = rotY;
lCol.userData.layer = 'first_floor';
// Add the Gothic Arch pointing UP
let trGeo = isXFace ? lobbyTridentGeoX : lobbyTridentGeoZ;
let trident = new THREE.Mesh(trGeo, MATS.metal);
trident.position.set(px, baseColH, pz);
trident.rotation.y = rotY;
trident.userData.layer = 'first_floor';
towerGrp.add(fCol, pFooting, lCol, trident);
};
// Base Flat Faces
for(let i = 0; i <= 18; i++) {
addBasePerimeterCol(lobbyStartX + i * lobbySpaceX, cz - flOuter + pColSize/2, 0, true); // North Face
addBasePerimeterCol(lobbyStartX + i * lobbySpaceX, cz + flOuter - pColSize/2, 0, true); // South Face
addBasePerimeterCol(cx - fwOuter + pColSize/2, lobbyStartZ + i * lobbySpaceZ, Math.PI/2, false); // West Face
addBasePerimeterCol(cx + fwOuter - pColSize/2, lobbyStartZ + i * lobbySpaceZ, Math.PI/2, false); // East Face
}
// ==========================================
// FACADES (Chamfered Glass)
// ==========================================
let glassShape = createChamferedShape(bldgW - pColSize*0.5, bldgL - pColSize*0.5, chamfer - pColSize*0.25, pColSize*0.5);
let glassGeo = new THREE.ExtrudeGeometry(glassShape, { depth: towerH, bevelEnabled: false }).rotateX(Math.PI/2);
let glassMesh = new THREE.Mesh(glassGeo, MATS.glass);
glassMesh.position.set(cx, towerH, cz);
glassMesh.userData.layer = 'first_floor';
towerGrp.add(glassMesh);
extraGeoms.push(glassGeo);
// ==========================================
// FLOORS: CONCRETE SLABS & SPANDREL BANDS
// ==========================================
let floorCount = 2 + Math.floor((towerH - f1H - f2H) / typH);
let plateThick = 2;
let floorW = bldgW - (pColSize * 2) - (plateThick * 2);
let floorL = bldgL - (pColSize * 2) - (plateThick * 2);
let floorChamfer = chamfer - pColSize - plateThick;
let floorShape = createChamferedShape(floorW, floorL, floorChamfer);
let cw = coreW / 2; let cl = coreL / 2;
let coreHole = new THREE.Path();
coreHole.moveTo(-cw, -cl); coreHole.lineTo(-cw, cl);
coreHole.lineTo(cw, cl); coreHole.lineTo(cw, -cl);
floorShape.holes.push(coreHole);
let slabGeo = new THREE.ExtrudeGeometry(floorShape, { depth: 4, bevelEnabled: false }).rotateX(Math.PI/2);
let spandrelW = bldgW - (pColSize * 2);
let spandrelL = bldgL - (pColSize * 2);
let spandrelChamfer = chamfer - pColSize;
let spandrelShape = createChamferedShape(spandrelW, spandrelL, spandrelChamfer, plateThick);
let spandrelGeo = new THREE.ExtrudeGeometry(spandrelShape, { depth: 52, bevelEnabled: false }).rotateX(Math.PI/2);
extraGeoms.push(slabGeo, spandrelGeo);
let slabInst = new THREE.InstancedMesh(slabGeo, MATS.concrete, floorCount);
let spandrelInst = new THREE.InstancedMesh(spandrelGeo, MATS.metal, floorCount);
// ==========================================
// STEEL FLOOR JOISTS (Mounting to Inner Plate)
// ==========================================
let trussLines = [];
let trussDepth = 33;
let tw = bldgW / 2 - pColSize - plateThick;
let tl = bldgL / 2 - pColSize - plateThick;
let tChamfer = chamfer - pColSize - plateThick;
let trussSpaceX = (tw * 2 - 2 * tChamfer) / 58;
for(let i = 0; i <= 58; i++) {
let px = -tw + tChamfer + i * trussSpaceX;
let zLimit = tl;
if (px < -tw + tChamfer) zLimit = tl - (tChamfer - (px - (-tw)));
if (px > tw - tChamfer) zLimit = tl - (tChamfer - (tw - px));
if (px >= -cw && px <= cw) {
trussLines.push(new THREE.Vector3(px, -4, -cl), new THREE.Vector3(px, -4, -zLimit));
trussLines.push(new THREE.Vector3(px, -4 - trussDepth, -cl), new THREE.Vector3(px, -4 - trussDepth, -zLimit));
trussLines.push(new THREE.Vector3(px, -4, -cl), new THREE.Vector3(px, -4 - trussDepth, -zLimit));
trussLines.push(new THREE.Vector3(px, -4, cl), new THREE.Vector3(px, -4, zLimit));
trussLines.push(new THREE.Vector3(px, -4 - trussDepth, cl), new THREE.Vector3(px, -4 - trussDepth, zLimit));
trussLines.push(new THREE.Vector3(px, -4, cl), new THREE.Vector3(px, -4 - trussDepth, zLimit));
} else {
trussLines.push(new THREE.Vector3(px, -4, -zLimit), new THREE.Vector3(px, -4, zLimit));
trussLines.push(new THREE.Vector3(px, -4 - trussDepth, -zLimit), new THREE.Vector3(px, -4 - trussDepth, zLimit));
trussLines.push(new THREE.Vector3(px, -4, -zLimit), new THREE.Vector3(px, -4 - trussDepth, zLimit));
}
}
let trussSpaceZ = (tl * 2 - 2 * tChamfer) / 58;
for(let i = 0; i <= 58; i++) {
let pz = -tl + tChamfer + i * trussSpaceZ;
let xLimit = tw;
if (pz < -tl + tChamfer) xLimit = tw - (tChamfer - (pz - (-tl)));
if (pz > tl - tChamfer) xLimit = tw - (tChamfer - (tl - pz));
if (pz > -cl && pz < cl) {
trussLines.push(new THREE.Vector3(-cw, -4, pz), new THREE.Vector3(-xLimit, -4, pz));
trussLines.push(new THREE.Vector3(-cw, -4 - trussDepth, pz), new THREE.Vector3(-xLimit, -4 - trussDepth, pz));
trussLines.push(new THREE.Vector3(-cw, -4, pz), new THREE.Vector3(-xLimit, -4 - trussDepth, pz));
trussLines.push(new THREE.Vector3(cw, -4, pz), new THREE.Vector3(xLimit, -4, pz));
trussLines.push(new THREE.Vector3(cw, -4 - trussDepth, pz), new THREE.Vector3(xLimit, -4 - trussDepth, pz));
trussLines.push(new THREE.Vector3(cw, -4, pz), new THREE.Vector3(xLimit, -4 - trussDepth, pz));
}
}
let floorTrussGeo = new THREE.BufferGeometry().setFromPoints(trussLines);
extraGeoms.push(floorTrussGeo);
let baseTrussMesh = new THREE.LineSegments(floorTrussGeo, MATS.joistWire);
let dummy = new THREE.Object3D();
for (let i = 0; i < floorCount; i++) {
let level = i + 1;
let yPos = 0;
if (level === 1) yPos = f1H;
else if (level === 2) yPos = f1H + f2H;
else yPos = f1H + f2H + (level - 2) * typH;
if (yPos > towerH) continue;
// Leave the top 3 mechanical floors completely empty (no slab, no spandrel, no joist)
if (yPos > mechGapStart + 1 && yPos < topFloorStart - 1) {
continue;
}
dummy.position.set(cx, yPos, cz);
dummy.updateMatrix();
slabInst.setMatrixAt(i, dummy.matrix);
spandrelInst.setMatrixAt(i, dummy.matrix);
let floorTruss = baseTrussMesh.clone();
floorTruss.position.set(cx, yPos, cz);
floorTruss.userData.layer = 'first_floor';
towerGrp.add(floorTruss);
}
slabInst.userData.layer = 'first_floor';
spandrelInst.userData.layer = 'first_floor';
towerGrp.add(slabInst, spandrelInst);
// ==========================================
// MASSIVE THICK TAPERED CONCRETE ROOF CAP (8 to 4 sides)
// ==========================================
let oh = 12; // Overhangs slightly past the outer columns at base
let rh = 120; // 120" thick solid concrete
let ins = rh; // Tapers inward precisely by the thickness of the topping
// Base Points: 8-sided Chamfer
let basePts = [
[cx + fwOuter + oh - chamfer, towerH, cz - flOuter - oh], // 0: NE top
[cx + fwOuter + oh, towerH, cz - flOuter - oh + chamfer], // 1: NE right
[cx + fwOuter + oh, towerH, cz + flOuter + oh - chamfer], // 2: SE right
[cx + fwOuter + oh - chamfer, towerH, cz + flOuter + oh], // 3: SE bottom
[cx - fwOuter - oh + chamfer, towerH, cz + flOuter + oh], // 4: SW bottom
[cx - fwOuter - oh, towerH, cz + flOuter + oh - chamfer], // 5: SW left
[cx - fwOuter - oh, towerH, cz - flOuter - oh + chamfer], // 6: NW left
[cx - fwOuter - oh + chamfer, towerH, cz - flOuter - oh] // 7: NW top
];
// Top Points: 4-sided pure square (Inset by EXACTLY the concrete thickness 'ins' from the outer bounds)
let topPts = [
[cx + fwOuter + oh - ins, towerH + rh, cz - flOuter - oh + ins], // 8: NE corner
[cx + fwOuter + oh - ins, towerH + rh, cz + flOuter + oh - ins], // 9: SE corner
[cx - fwOuter - oh + ins, towerH + rh, cz + flOuter + oh - ins], // 10: SW corner
[cx - fwOuter - oh + ins, towerH + rh, cz - flOuter - oh + ins] // 11: NW corner
];
let roofVerts = [];
basePts.forEach(p => roofVerts.push(...p));
topPts.forEach(p => roofVerts.push(...p));
roofVerts.push(cx, towerH, cz); // 12 (bottom center)
roofVerts.push(cx, towerH + rh, cz); // 13 (top center)
let roofIndices = [
// Sides (Counter-Clockwise winding for outward normals)
0, 8, 1, // NE Chamfer Triangle
1, 8, 9, 1, 9, 2, // East Flat Quad
2, 9, 3, // SE Chamfer Triangle
3, 9, 10, 3, 10, 4, // South Flat Quad
4, 10, 5, // SW Chamfer Triangle
5, 10, 11, 5, 11, 6, // West Flat Quad
6, 11, 7, // NW Chamfer Triangle
7, 11, 8, 7, 8, 0, // North Flat Quad
// Bottom Cap (Facing Down)
0, 1, 12, 1, 2, 12, 2, 3, 12, 3, 4, 12, 4, 5, 12, 5, 6, 12, 6, 7, 12, 7, 0, 12,
// Top Cap (Facing Up)
8, 13, 9, 9, 13, 10, 10, 13, 11, 11, 13, 8
];
let thickRoofGeo = new THREE.BufferGeometry();
thickRoofGeo.setAttribute('position', new THREE.Float32BufferAttribute(roofVerts, 3));
thickRoofGeo.setIndex(roofIndices);
thickRoofGeo.computeVertexNormals();
let thickRoofMesh = new THREE.Mesh(thickRoofGeo, MATS.concrete);
thickRoofMesh.userData.layer = 'first_floor';
towerGrp.add(thickRoofMesh);
extraGeoms.push(thickRoofGeo);
// ==========================================
// ELEVATOR SHAFTS (Inside the Core)
// ==========================================
let elevW = coreW * 0.35;
let elevL = coreL * 0.25;
let elevGeo = new THREE.BoxGeometry(elevW, towerH - f1H, elevL);
extraGeoms.push(elevGeo);
for (let ix = -1; ix <= 1; ix += 2) {
for (let iz = -1; iz <= 1; iz += 2) {
let elevMesh = new THREE.Mesh(elevGeo, MATS.concrete);
let px = cx + ix * (coreW * 0.22);
let pz = cz + iz * (coreL * 0.3);
elevMesh.position.set(px, f1H + (towerH - f1H) / 2, pz);
elevMesh.userData.layer = 'first_floor';
towerGrp.add(elevMesh);
}
}
// ==========================================
// TIERED PERIMETER COLUMNS (Main Standard Body)
// ==========================================
// Shrink the standard 3-tier array to fit exactly below the mechanical gap
let upperTowerH = mechGapStart - f1H;
let t1H = upperTowerH * 0.33, t2H = upperTowerH * 0.33, t3H = upperTowerH * 0.34;
let pColGeo1 = new THREE.BoxGeometry(pColSize, t1H, pColSize);
let pColGeo2 = new THREE.BoxGeometry(pColSize * 0.8, t2H, pColSize * 0.8);
let pColGeo3 = new THREE.BoxGeometry(pColSize * 0.6, t3H, pColSize * 0.6); // Matches the top
extraGeoms.push(pColGeo1, pColGeo2, pColGeo3);
const addTaperedCol = (px, pz, rotY = 0) => {
let col1 = new THREE.Mesh(pColGeo1, MATS.metal); col1.position.set(px, f1H + (t1H / 2), pz); col1.rotation.y = rotY;
let col2 = new THREE.Mesh(pColGeo2, MATS.metal); col2.position.set(px, f1H + t1H + (t2H / 2), pz); col2.rotation.y = rotY;
let col3 = new THREE.Mesh(pColGeo3, MATS.metal); col3.position.set(px, f1H + t1H + t2H + (t3H / 2), pz); col3.rotation.y = rotY;
col1.userData.layer = col2.userData.layer = col3.userData.layer = 'first_floor';
towerGrp.add(col1, col2, col3);
};
// Main Body Flat Faces
for(let i = 0; i < 59; i++) {
addTaperedCol(pColStartX + i * spaceColX, cz - flOuter + pColSize/2, 0); // North Face
addTaperedCol(pColStartX + i * spaceColX, cz + flOuter - pColSize/2, 0); // South Face
addTaperedCol(cx - fwOuter + pColSize/2, pColStartZ + i * spaceColZ, 0); // West Face
addTaperedCol(cx + fwOuter - pColSize/2, pColStartZ + i * spaceColZ, 0); // East Face
}
// ==========================================
// MECHANICAL GAP TRIDENTS & COLUMNS
// ==========================================
let archH_mech = typH * 1.1;
let topTrColW = pColSize * 0.6;
let mechBaseW = topTrColW * 1.1;
let mechTrDepth = pColSize * 0.8;
let mechTridentGeoX = createTridentGeo(spaceColX, archH_mech, topTrColW, mechBaseW, mechTrDepth);
let mechTridentGeoZ = createTridentGeo(spaceColZ, archH_mech, topTrColW, mechBaseW, mechTrDepth);
// The straight column connecting the lower downward arch to the upper upward arch
let mechStraightH = mechGapHeight - (2 * archH_mech);
let mechColGeo = new THREE.BoxGeometry(mechBaseW * 2, mechStraightH, mechBaseW * 2);
extraGeoms.push(mechTridentGeoX, mechTridentGeoZ, mechColGeo);
const addMechGapAssembly = (px, pz, rotY, isXFace) => {
const trGeo = isXFace ? mechTridentGeoX : mechTridentGeoZ;
// Offset between adjacent columns
const offset = isXFace ? spaceColX : spaceColZ;
const dx = isXFace ? offset : 0;
const dz = isXFace ? 0 : offset;
// Loop through 5 columns
for (let i = -2; i <= 2; i++) {
const currentX = px + dx * i;
const currentZ = pz + dz * i;
// =====================================================
// 1. STRAIGHT COLUMN (Always added)
// =====================================================
let col = new THREE.Mesh(mechColGeo, MATS.metal);
col.position.set(
currentX,
mechGapStart + archH_mech + mechStraightH / 2,
currentZ
);
col.rotation.y = rotY;
col.userData.layer = 'first_floor';
towerGrp.add(col);
// =====================================================
// 2. ARCHES (Only added in between the columns)
// =====================================================
// By checking (i > -2 && i < 2), we prevent the arches
// from being created at the far left and far right columns,
// which removes the "overhang" at the ends of the assembly.
if (i > -2 && i < 2) {
// UPPER ARCH
let archTop = new THREE.Mesh(trGeo, MATS.metal);
if (i % 2 !== 0) { archTop.scale.x = -1; }
archTop.position.set(currentX, topFloorStart - archH_mech, currentZ);
archTop.rotation.set(0, rotY, 0);
archTop.userData.layer = 'first_floor';
towerGrp.add(archTop);
// LOWER ARCH
let archBottom = new THREE.Mesh(trGeo, MATS.metal);
if (i % 2 !== 0) { archBottom.scale.x = -1; }
archBottom.position.set(currentX, mechGapStart + archH_mech, currentZ);
archBottom.rotation.set(Math.PI, rotY, 0);
archBottom.userData.layer = 'first_floor';
towerGrp.add(archBottom);
}
}
};
for(let i = 0; i <= 18; i++) {
addMechGapAssembly(lobbyStartX + i * lobbySpaceX, cz - flOuter + pColSize/2, 0, true); // North Face
addMechGapAssembly(lobbyStartX + i * lobbySpaceX, cz + flOuter - pColSize/2, 0, true); // South Face
addMechGapAssembly(cx - fwOuter + pColSize/2, lobbyStartZ + i * lobbySpaceZ, Math.PI/2, false); // West Face
addMechGapAssembly(cx + fwOuter - pColSize/2, lobbyStartZ + i * lobbySpaceZ, Math.PI/2, false); // East Face
}
// ==========================================
// TOP FLOOR COLUMNS (Above the Upward Tridents)
// ==========================================
let topColGeo = new THREE.BoxGeometry(pColSize * 0.6, typH, pColSize * 0.6);
extraGeoms.push(topColGeo);
for(let i = 0; i < 59; i++) {
let pxN = pColStartX + i * spaceColX, pzN = cz - flOuter + pColSize/2;
let pxS = pColStartX + i * spaceColX, pzS = cz + flOuter - pColSize/2;
let pxW = cx - fwOuter + pColSize/2, pzW = pColStartZ + i * spaceColZ;
let pxE = cx + fwOuter - pColSize/2, pzE = pColStartZ + i * spaceColZ;
[ [pxN,pzN, 0], [pxS,pzS, 0], [pxW,pzW, Math.PI/2], [pxE,pzE, Math.PI/2] ].forEach(pos => {
let col = new THREE.Mesh(topColGeo, MATS.metal);
col.position.set(pos[0], topFloorStart + (typH / 2), pos[1]);
col.rotation.y = pos[2];
col.userData.layer = 'first_floor';
towerGrp.add(col);
});
}
// ==========================================
// CHAMFER PLATES (2" thick FULL HEIGHT outer plates)
// ==========================================
let plateThickOuter = 2;
let plateW = chamfer * Math.SQRT2;
let chamferPlateGeo = new THREE.BoxGeometry(plateW, towerH, plateThickOuter);
extraGeoms.push(chamferPlateGeo);
let offsetD = (pColSize / 2) + (plateThickOuter / 2);
let invSqrt2 = Math.SQRT1_2;
const addChamferPlate = (mx, mz, normX, normZ, rotY) => {
let plate = new THREE.Mesh(chamferPlateGeo, MATS.metal);
let px = mx + offsetD * normX;
let pz = mz + offsetD * normZ;
plate.position.set(px, towerH / 2, pz);
plate.rotation.y = rotY;
plate.userData.layer = 'first_floor';
towerGrp.add(plate);
};
addChamferPlate(cx + fwOuter - chamfer/2 - pColSize/2, cz - flOuter + chamfer/2 + pColSize/2, invSqrt2, -invSqrt2, -Math.PI/4); // NE
addChamferPlate(cx - fwOuter + chamfer/2 + pColSize/2, cz - flOuter + chamfer/2 + pColSize/2, -invSqrt2, -invSqrt2, Math.PI/4); // NW
addChamferPlate(cx + fwOuter - chamfer/2 - pColSize/2, cz + flOuter - chamfer/2 - pColSize/2, invSqrt2, invSqrt2, Math.PI/4); // SE
addChamferPlate(cx - fwOuter + chamfer/2 + pColSize/2, cz + flOuter - chamfer/2 - pColSize/2, -invSqrt2, invSqrt2, -Math.PI/4); // SW
// ==========================================
// HAT TRUSS (Extruded Wide Flanges) & MAST
// ==========================================
let hatTrussPts = [];
let hatBase = mechGapStart; // Perfectly spans the new top structural gaps
for(let x = cx - cw; x <= cx + cw; x += cw) {
hatTrussPts.push(new THREE.Vector3(x, hatBase, cz - cl), new THREE.Vector3(x, towerH, cz - flOuter));
hatTrussPts.push(new THREE.Vector3(x, towerH, cz - cl), new THREE.Vector3(x, hatBase, cz - flOuter));
hatTrussPts.push(new THREE.Vector3(x, hatBase, cz + cl), new THREE.Vector3(x, towerH, cz + flOuter));
hatTrussPts.push(new THREE.Vector3(x, towerH, cz + cl), new THREE.Vector3(x, hatBase, cz + flOuter));
}
for(let z = cz - cl; z <= cz + cl; z += cl) {
hatTrussPts.push(new THREE.Vector3(cx + cw, hatBase, z), new THREE.Vector3(cx + fwOuter, towerH, z));
hatTrussPts.push(new THREE.Vector3(cx + cw, towerH, z), new THREE.Vector3(cx + fwOuter, hatBase, z));
hatTrussPts.push(new THREE.Vector3(cx - cw, hatBase, z), new THREE.Vector3(cx - fwOuter, towerH, z));
hatTrussPts.push(new THREE.Vector3(cx - cw, towerH, z), new THREE.Vector3(cx - fwOuter, hatBase, z));
}
let wfSize = 24;
let flThick = 4;
let webThick = 4;
let wfShape = new THREE.Shape();
wfShape.moveTo(-wfSize/2, -wfSize/2);
wfShape.lineTo(wfSize/2, -wfSize/2);
wfShape.lineTo(wfSize/2, -wfSize/2 + flThick);
wfShape.lineTo(webThick/2, -wfSize/2 + flThick);
wfShape.lineTo(webThick/2, wfSize/2 - flThick);
wfShape.lineTo(wfSize/2, wfSize/2 - flThick);
wfShape.lineTo(wfSize/2, wfSize/2);
wfShape.lineTo(-wfSize/2, wfSize/2);
wfShape.lineTo(-wfSize/2, wfSize/2 - flThick);
wfShape.lineTo(-webThick/2, wfSize/2 - flThick);
wfShape.lineTo(-webThick/2, -wfSize/2 + flThick);
wfShape.lineTo(-wfSize/2, -wfSize/2 + flThick);
wfShape.lineTo(-wfSize/2, -wfSize/2);
for(let i = 0; i < hatTrussPts.length; i += 2) {
let p1 = hatTrussPts[i];
let p2 = hatTrussPts[i+1];
let path = new THREE.LineCurve3(p1, p2);
let extrudeSettings = { extrudePath: path, steps: 1, bevelEnabled: false };
let beamGeo = new THREE.ExtrudeGeometry(wfShape, extrudeSettings);
let beamMesh = new THREE.Mesh(beamGeo, MATS.metal);
beamMesh.userData.layer = 'first_floor';
towerGrp.add(beamMesh);
extraGeoms.push(beamGeo);
}
if (isNorthTower) {
let mastH = 4320;
let mastGeo = new THREE.CylinderGeometry(40, 60, mastH, 8);
let mast = new THREE.Mesh(mastGeo, MATS.coreMetal);
mast.position.set(cx, towerH + rh + (mastH / 2), cz);
mast.userData.layer = 'first_floor';
towerGrp.add(mast);
extraGeoms.push(mastGeo);
}
return towerGrp;
}
The top arches stem from this loop:
const addMechGapAssembly = (px, pz, rotY, isXFace) => {
const trGeo = isXFace ? mechTridentGeoX : mechTridentGeoZ;
// Offset between adjacent columns
const offset = isXFace ? spaceColX : spaceColZ;
const dx = isXFace ? offset : 0;
const dz = isXFace ? 0 : offset;
// Loop through 5 columns
for (let i = -2; i <= 2; i++) {
const currentX = px + dx * i;
const currentZ = pz + dz * i;
// =====================================================
// 1. STRAIGHT COLUMN (Always added)
// =====================================================
let col = new THREE.Mesh(mechColGeo, MATS.metal);
col.position.set(
currentX,
mechGapStart + archH_mech + mechStraightH / 2,
currentZ
);
col.rotation.y = rotY;
col.userData.layer = 'first_floor';
towerGrp.add(col);
// =====================================================
// 2. ARCHES (Only added in between the columns)
// =====================================================
// By checking (i > -2 && i < 2), we prevent the arches
// from being created at the far left and far right columns,
// which removes the "overhang" at the ends of the assembly.
if (i > -2 && i < 2) {
// UPPER ARCH
let archTop = new THREE.Mesh(trGeo, MATS.metal);
if (i % 2 !== 0) { archTop.scale.x = -1; }
archTop.position.set(currentX, topFloorStart - archH_mech, currentZ);
archTop.rotation.set(0, rotY, 0);
archTop.userData.layer = 'first_floor';
towerGrp.add(archTop);
// LOWER ARCH
let archBottom = new THREE.Mesh(trGeo, MATS.metal);
if (i % 2 !== 0) { archBottom.scale.x = -1; }
archBottom.position.set(currentX, mechGapStart + archH_mech, currentZ);
archBottom.rotation.set(Math.PI, rotY, 0);
archBottom.userData.layer = 'first_floor';
towerGrp.add(archBottom);
}
}
};
Anyone care to help me fix this so it arrays the arches properly?





