Creating Complex Mesh

I have developed a code that generates various roof types, consisting of over 250,000 characters and featuring more than a dozen distinct designs. I have attached a portion of this code to establish a basic foundation to gain some support for this project.

I’m trying to find a way to convert the outlines of these shapes into a Complex Mesh, so the entire structure can be represented as a single mesh rather than just separate lines. Additionally, I’ve noticed an issue with one side of the Dutch Gable, where the intersection calculations aren’t working correctly.

While I can use a lathe to shape some parts by applying a scaling factor and adjusting the number of sides, I prefer not to use that method. Instead, I want to keep the underlying skeleton to accurately place custom boxes that will act as rafters at the line locations.

<!DOCTYPE html>
<html lang="en">c
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>3D Building Model</title>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/three@0.128/examples/js/controls/OrbitControls.js"></script>
    <style>
        body {
            margin: 0;
            overflow: hidden;
            display: flex;
            flex-direction: column;
            height: 100vh;
            background-color: #1a1a1a;
        }
        #container {
            flex: 1;
            display: flex;
            justify-content: center;
            align-items: center;
        }
        #controls {
            padding: 5px;
            background-color: #333;
            color: white;
            display: flex;
            justify-content: space-between;
            align-items: center;
            position: absolute;
            bottom: 10px;
            width: 100%;
            border-radius: 10px;
        }
        label {
            margin: 0 5px;
            font-size: 0.9em;
        }
        input {
            margin-left: 5px;
            width: 50px; /* Increased width for better visibility */
        }
        select {
            margin-left: 5px;
        }
        button {
            padding: 5px 10px;
            background-color: #555;
            color: white;
            border: none;
            border-radius: 5px;
            cursor: pointer;
        }
        button:hover {
            background-color: #777;
        }
        .input-container {
            display: none; /* Hide all containers by default */
        }
        .active {
            display: flex; /* Show active container */
        }
    </style>
</head>
<body>
    <div id="container"></div>
    <div id="controls">
      <label for="roofType">Roof Type:</label>
<select id="roofType">
<option value="gable">Gable</option>
<option value="dutchGable">Dutch Gable</option>
  

</select>

              <label for="rise">Roof Slope (rise/run):</label>
            <input type="number" id="rise" value="9" step="0.0625" min="0">/
            <input type="number" id="run" value="12" step="1" min="1">
      
           <!-- Gable Input Container -->
        <div id="gableInputs" class="input-container">

            <label for="length">Length:</label>
            <input type="number" id="length" value="480" step="1" min="3" max="1200">
            <label for="width">Width:</label>
            <input type="number" id="width" value="240" step="1" min="3" max="600">
                     <label for="materialThickness">Material Thickness:</label>
            <input type="number" id="materialThickness" value="1.5" step="0.0625" min="0.5" max="5.5">
        </div>

 <!-- Dutch Gable Input Container -->
        <div id="dutchGableInputs" class="input-container">


            <label for="length7">Length:</label>
            <input type="number" id="length7" value="480" step="1" min="3" max="1200">
            <label for="width7">Width:</label>
            <input type="number" id="width7" value="240" step="1" min="3" max="1200">    


            <label for="materialThickness7">Material Thickness:</label>
            <input type="number" id="materialThickness7" value="1.5" step="0.0625" min="0.5" max="5.5">
        </div>

 
        <button id="update">Update</button>
    </div>

    <script>
// Scene, camera, renderer
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 5000); // Increased far plane from 1000 to 5000
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight * 0.9);
document.getElementById('container').appendChild(renderer.domElement);

const controls = new THREE.OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;

// Set the camera position further back
camera.position.z = 300; // You can adjust this value based on your scene size

const animate = function () {
    requestAnimationFrame(animate);
    controls.update();
    renderer.render(scene, camera);
};

animate();



    </script>
<script>

   function createGable(rise, run, length, width, materialThickness) {
    const spacing = 24; // Spacing set inside the function
    const totalRun = width / 2 - materialThickness / 2;
    const angleInRadians = Math.atan(rise / run);
    const totalRise = Math.tan(angleInRadians) * totalRun;
    const height = totalRise;

    const bottomVertices = [
        new THREE.Vector3(-length / 2, 0, width / 2),
        new THREE.Vector3(length / 2, 0, width / 2),
        new THREE.Vector3(length / 2, 0, -width / 2),
        new THREE.Vector3(-length / 2, 0, -width / 2)
    ];

    const topVertices = [
        new THREE.Vector3(-length / 2, height, materialThickness / 2),
        new THREE.Vector3(length / 2, height, materialThickness / 2),
        new THREE.Vector3(length / 2, height, -materialThickness / 2),
        new THREE.Vector3(-length / 2, height, -materialThickness / 2)
    ];

    // Add bottom rectangle
    const bottomGeometry = new THREE.BufferGeometry().setFromPoints(bottomVertices);
    const bottomMaterial = new THREE.LineBasicMaterial({ color: 0x00ff00 });
    const bottomRectangle = new THREE.LineLoop(bottomGeometry, bottomMaterial);
    scene.add(bottomRectangle);

    // Add top rectangle
    const topGeometry = new THREE.BufferGeometry().setFromPoints(topVertices);
    const topMaterial = new THREE.LineBasicMaterial({ color: 0x0000ff });
    const topRectangle = new THREE.LineLoop(topGeometry, topMaterial);
    scene.add(topRectangle);

    // Calculate the hypotenuse for the gable structure
    const hypotenuse = Math.sqrt(rise * rise + run * run);
    const connectingLinesMaterial = new THREE.LineBasicMaterial({ color: 0xff0000 });

    // Create connecting lines from bottom to top vertices (extended at the bottom)
    for (let i = 0; i < bottomVertices.length; i++) {
        const bottomPoint = bottomVertices[i];
        const topPoint = topVertices[i];
        const direction = new THREE.Vector3().subVectors(topPoint, bottomPoint).normalize();
        const extendedStart = bottomPoint.clone().sub(direction.clone().multiplyScalar(hypotenuse));
        const extendedEnd = topPoint.clone();

        const lineGeometry = new THREE.BufferGeometry();
        const points = [extendedStart, extendedEnd];
        lineGeometry.setFromPoints(points);
        const connectingLine = new THREE.Line(lineGeometry, connectingLinesMaterial);
        scene.add(connectingLine);
    }

    // Create the additional array of connecting lines every 24 units OC
    const numDivisions = Math.floor(length / spacing);  // Total divisions based on spacing
    const step = length / numDivisions;  // Step size for placement along the length

    for (let i = 0; i < bottomVertices.length; i++) {
        const bottomPoint = bottomVertices[i];
        const topPoint = topVertices[i];

        // Create connecting lines spaced every 24 units along the length
        for (let j = 1; j < numDivisions; j++) {  // Start at 1 to avoid the endpoints
            const offset = step * j - length / 2;  // Calculate position based on step size
            const newBottomPoint = new THREE.Vector3(offset, 0, bottomPoint.z);
            const newTopPoint = new THREE.Vector3(offset, height, topPoint.z);

            // Extend the bottom point for the array connecting lines
            const direction = new THREE.Vector3().subVectors(newTopPoint, newBottomPoint).normalize();
            const extendedBottomPoint = newBottomPoint.clone().sub(direction.clone().multiplyScalar(hypotenuse));

            const lineGeometry = new THREE.BufferGeometry();
            const points = [extendedBottomPoint, newTopPoint];
            lineGeometry.setFromPoints(points);
            const arrayConnectingLine = new THREE.Line(lineGeometry, connectingLinesMaterial);
            scene.add(arrayConnectingLine);
        }
    }
}

       function createDutchGable(rise, run, length, width, materialThickness) {
             const midWidth = width / 2;
 const totalRun = width / 2-midWidth/2 - materialThickness / 2;
            const angleInRadians = Math.atan(rise / run);
            const totalRise = Math.tan(angleInRadians) * totalRun;
           


   const totalRun2 = midWidth/2 - materialThickness / 2;
            const angleInRadians2 = Math.atan(rise / run);
            const totalRise2 = Math.tan(angleInRadians2) * totalRun2;
            const height2 = totalRise2;

 const height = totalRise+totalRise2;
 const ridgeLength = length-(width-midWidth);

    const middleVertices = [
        new THREE.Vector3(-ridgeLength / 2, totalRise2, -midWidth / 2),
        new THREE.Vector3(ridgeLength / 2, totalRise2, -midWidth / 2),
        new THREE.Vector3(ridgeLength / 2, totalRise2, midWidth / 2),
        new THREE.Vector3(-ridgeLength / 2, totalRise2, midWidth / 2),
    ];

    const topVertices = [
        new THREE.Vector3(-ridgeLength / 2, height, -materialThickness / 2),
        new THREE.Vector3(ridgeLength / 2, height, -materialThickness / 2),
        new THREE.Vector3(ridgeLength / 2, height, materialThickness / 2),
        new THREE.Vector3(-ridgeLength / 2, height, materialThickness / 2),
    ];

    const bottomVertices = [];
    const spacing = 24; // Spacing for the lines
    const connectingLinesMaterial = new THREE.LineBasicMaterial({ color: 0x00ff00 });
    const baseRectMaterial = new THREE.LineBasicMaterial({ color: 0xff0000 });
    const topRectMaterial = new THREE.LineBasicMaterial({ color: 0x0000ff }); // Blue for the top rectangle
    const pinkMaterial = new THREE.LineBasicMaterial({ color: 0xff00ff }); // Pink for the connecting lines

    // Create vertices for the rectangular base
    bottomVertices.push(new THREE.Vector3(-length / 2, 0, -width / 2)); // Bottom-left 
    bottomVertices.push(new THREE.Vector3(length / 2, 0, -width / 2));  // Bottom-right 
    bottomVertices.push(new THREE.Vector3(length / 2, 0, width / 2));   // Top-right 
    bottomVertices.push(new THREE.Vector3(-length / 2, 0, width / 2));  // Top-left 

const middleGeometry = new THREE.BufferGeometry().setFromPoints(middleVertices);
            const middleMaterial = new THREE.LineBasicMaterial({ color: 0x00ff00 });
            const middleRectangle = new THREE.LineLoop(middleGeometry, middleMaterial);
            scene.add(middleRectangle);

  const hypotenuse = Math.sqrt(rise * rise + run * run);
         

            for (let i = 0; i < middleVertices.length; i++) {
                const lineGeometry = new THREE.BufferGeometry();
                const bottomPoint = middleVertices[i];
                const topPoint = topVertices[i];
                const direction = new THREE.Vector3().subVectors(topPoint, bottomPoint).normalize();
                const extendedStart = bottomPoint.clone();
                const extendedEnd = topPoint.clone();
                const points = [extendedStart, extendedEnd];
                lineGeometry.setFromPoints(points);
                const connectingLine1 = new THREE.Line(lineGeometry, connectingLinesMaterial);
                scene.add(connectingLine1);
            }

    // Draw the rectangular base
    const baseRectGeometry = new THREE.BufferGeometry().setFromPoints([...bottomVertices, bottomVertices[0]]);
    const baseRect = new THREE.LineLoop(baseRectGeometry, baseRectMaterial);
    scene.add(baseRect);

    // Draw the top rectangle
    const topRectGeometry = new THREE.BufferGeometry().setFromPoints([...topVertices, topVertices[0]]);
    const topRect = new THREE.LineLoop(topRectGeometry, topRectMaterial);
    scene.add(topRect);

   // Calculate bottom points for base edges
const baseMidPoints = [
    new THREE.Vector3((bottomVertices[0].x + bottomVertices[1].x) / 2, bottomVertices[0].y, (bottomVertices[0].z + bottomVertices[1].z) / 2),
    new THREE.Vector3((bottomVertices[1].x + bottomVertices[2].x) / 2, bottomVertices[1].y, (bottomVertices[1].z + bottomVertices[2].z) / 2),
    new THREE.Vector3((bottomVertices[2].x + bottomVertices[3].x) / 2, bottomVertices[2].y, (bottomVertices[2].z + bottomVertices[3].z) / 2),
    new THREE.Vector3((bottomVertices[3].x + bottomVertices[0].x) / 2, bottomVertices[3].y, (bottomVertices[3].z + bottomVertices[0].z) / 2)
];


   // Calculate midpoints for middle edges
const middleMidPoints = [
    new THREE.Vector3((middleVertices[0].x + middleVertices[1].x) / 2, middleVertices[0].y, (middleVertices[0].z + middleVertices[1].z) / 2),
    new THREE.Vector3((middleVertices[1].x + middleVertices[2].x) / 2, middleVertices[1].y, (middleVertices[1].z + middleVertices[2].z) / 2),
    new THREE.Vector3((middleVertices[2].x + middleVertices[3].x) / 2, middleVertices[2].y, (middleVertices[2].z + middleVertices[3].z) / 2),
    new THREE.Vector3((middleVertices[3].x + middleVertices[0].x) / 2, middleVertices[3].y, (middleVertices[3].z + middleVertices[0].z) / 2)
];


    // Draw center lines from base midpoints to top midpoints
    baseMidPoints.forEach((baseMidPoint, index) => {
        const centerLineGeometry = new THREE.BufferGeometry();
        const centerLinePoints = [baseMidPoint, middleMidPoints[index]]; // Connecting base midpoint to top midpoint
        centerLineGeometry.setFromPoints(centerLinePoints);
        const centerLine = new THREE.Line(centerLineGeometry, connectingLinesMaterial);
        scene.add(centerLine);
    });

 // Create the additional array of connecting lines every 24 units OC
    const numDivisions1 = Math.floor(ridgeLength / spacing);  // Total divisions based on spacing
    const step1 = ridgeLength / numDivisions1;  // Step size for placement along the length

    for (let i = 0; i < middleVertices.length; i++) {
        const bottomPoint = middleVertices[i];
        const topPoint = topVertices[i];

        // Create connecting lines spaced every 24 units along the length
        for (let j = 1; j < numDivisions1; j++) {  // Start at 1 to avoid the endpoints
            const offset = step1 * j - ridgeLength / 2;  // Calculate position based on step size
            const newBottomPoint = new THREE.Vector3(offset, height2, bottomPoint.z);
            const newTopPoint = new THREE.Vector3(offset, height, topPoint.z);

            // Extend the bottom point for the array connecting lines
            const direction = new THREE.Vector3().subVectors(newTopPoint, newBottomPoint).normalize();
            const extendedBottomPoint = newBottomPoint.clone();

            const lineGeometry = new THREE.BufferGeometry();
            const points = [extendedBottomPoint, newTopPoint];
            lineGeometry.setFromPoints(points);
            const arrayConnectingLine = new THREE.Line(lineGeometry, connectingLinesMaterial);
            scene.add(arrayConnectingLine);
        }
    }


// For each side of the base, connect it to the corresponding side of the top rectangle
for (let i = 0; i < bottomVertices.length; i++) {
    const bottomPoint1 = bottomVertices[i];
    const bottomPoint2 = bottomVertices[(i + 1) % bottomVertices.length];

    // Get the top points corresponding to this side
    const topPoint1 = middleVertices[i];
    const topPoint2 = middleVertices[(i + 1) % middleVertices.length];

    // Draw the connecting lines between the bottom vertices and the top rectangle vertices with pink color
    const mainEdgeGeometry1 = new THREE.BufferGeometry().setFromPoints([bottomPoint1, topPoint1]);
    const mainEdgeLine1 = new THREE.Line(mainEdgeGeometry1, pinkMaterial); // Use pink material
    scene.add(mainEdgeLine1);

    const mainEdgeGeometry2 = new THREE.BufferGeometry().setFromPoints([bottomPoint2, topPoint2]);
    const mainEdgeLine2 = new THREE.Line(mainEdgeGeometry2, pinkMaterial); // Use pink material
    scene.add(mainEdgeLine2);

    // Calculate the edge length of the base side
    const edgeLength = bottomPoint1.distanceTo(bottomPoint2);

    // Calculate the number of divisions along this edge based on the spacing
    const numDivisions = Math.floor(edgeLength / spacing);
    const stepX = (bottomPoint2.x - bottomPoint1.x) / numDivisions;
    const stepZ = (bottomPoint2.z - bottomPoint1.z) / numDivisions;

    // Determine the midpoint of the edge
    const midPoint = new THREE.Vector3().addVectors(bottomPoint1, bottomPoint2).multiplyScalar(0.5);

    // Store the original positions of the arrayed lines
    const originalLines = [];

    // Draw arrayed lines for half the edge
    for (let j = 1; j <= Math.floor(numDivisions / 2); j++) { // Only draw half
        // Calculate the new bottom point along the base edge
        const newBottomPoint = new THREE.Vector3(
            bottomPoint1.x + stepX * j,
            0,
            bottomPoint1.z + stepZ * j
        );

        // Determine the direction of the line by averaging the corresponding midpoints
        const midPoint1 = baseMidPoints[i];
        const midPoint2 = middleMidPoints[i];
        const direction = new THREE.Vector3().subVectors(midPoint2, midPoint1).normalize(); // Get the direction from base to top

        // Calculate the initial parallel point above the new bottom point using the direction
        let parallelTopPoint = newBottomPoint.clone().add(direction.clone().multiplyScalar(height2)); // Move up by the height

        // Check for intersections with the top rectangle
        const intersectsTopRect = checkLineIntersection(newBottomPoint, parallelTopPoint, middleVertices);

        // Check for intersections with the pink lines (the pink edges)
        const intersectsPinkLines = checkLineIntersection(newBottomPoint, parallelTopPoint, [bottomPoint1, topPoint1, bottomPoint2, topPoint2]);

        // If it intersects the top rectangle or connecting lines, set the parallelTopPoint to the intersection point
        if (intersectsTopRect) {
            parallelTopPoint = intersectsTopRect;
        } else if (intersectsPinkLines) {
            parallelTopPoint = intersectsPinkLines; // Stop at the intersection with the pink lines
        }

        // Create geometry for the connecting line
        const lineGeometry = new THREE.BufferGeometry();
        const points = [newBottomPoint, parallelTopPoint];
        lineGeometry.setFromPoints(points);

        // Create and add the connecting line to the scene
        const connectingLine = new THREE.Line(lineGeometry, connectingLinesMaterial);
        scene.add(connectingLine);

        // Store the original bottom and top points for mirroring later
        originalLines.push({ bottomPoint: newBottomPoint.clone(), topPoint: parallelTopPoint.clone(), direction });
    }

    // Draw mirrored lines for the other half of the edge
    for (const line of originalLines) {
        // Get the original points
        const originalBottomPoint = line.bottomPoint;
        const originalTopPoint = line.topPoint;
        const direction = line.direction;

        // Calculate the length of the line segment
        const length = originalTopPoint.distanceTo(originalBottomPoint);

        // Mirror the bottom point about the midpoint
        const mirroredBottomPoint = new THREE.Vector3(
            midPoint.x + (midPoint.x - originalBottomPoint.x),
            0,
            midPoint.z + (midPoint.z - originalBottomPoint.z)
        );

        // Calculate the mirrored top point based on the direction and length
        const mirroredTopPoint = mirroredBottomPoint.clone().add(direction.clone().multiplyScalar(length));

        // Create geometry for the mirrored connecting line
        const mirroredLineGeometry = new THREE.BufferGeometry();
        const mirroredPoints = [mirroredBottomPoint, mirroredTopPoint];
        mirroredLineGeometry.setFromPoints(mirroredPoints);

        // Create and add the mirrored connecting line to the scene
        const mirroredConnectingLine = new THREE.Line(mirroredLineGeometry, connectingLinesMaterial);
        scene.add(mirroredConnectingLine);
    }
}

}
// Function to check for intersections with lines defined by vertices
function checkLineIntersection(startPoint, endPoint, vertices) {
    const raycaster = new THREE.Raycaster(startPoint, new THREE.Vector3().subVectors(endPoint, startPoint).normalize());
    const intersects = raycaster.intersectObject(new THREE.LineLoop(new THREE.BufferGeometry().setFromPoints(vertices)));

    if (intersects.length > 0) {
        return intersects[0].point; // Return the intersection point
    }
    return null; // No intersection
}


      function updateComponents() {
            while (scene.children.length > 0) {
                scene.remove(scene.children[0]);
            }

            const rise = parseFloat(document.getElementById('rise').value);
            const run = parseFloat(document.getElementById('run').value);
  



            const length = parseFloat(document.getElementById('length').value);
            const width = parseFloat(document.getElementById('width').value);         
            const materialThickness = parseFloat(document.getElementById('materialThickness').value);

          
const length7 = parseFloat(document.getElementById('length7').value);
const width7 = parseFloat(document.getElementById('width7').value);
const materialThickness7 = parseFloat(document.getElementById('materialThickness7').value);




        const roofType = document.getElementById('roofType').value;

          if (roofType === 'gable') {
                createGable(rise, run, length, width, materialThickness);
            }  else if (roofType === 'dutchGable') {
                createDutchGable(rise, run, length7, width7, materialThickness7);
            }

        }

  document.getElementById('roofType').addEventListener('change', function() {
    const roofType = this.value;

    const gableInputs = document.getElementById('gableInputs');

const dutchGableInputs = document.getElementById('dutchGableInputs'); 



    // Reset all inputs to inactive

    gableInputs.classList.remove('active');

dutchGableInputs.classList.remove('active');


    // Activate the corresponding inputs based on selected roof type
   if (roofType === 'dutchGable') {
        dutchGableInputs.classList.add('active');
    }  else {
        gableInputs.classList.add('active'); // Default to gable
    }


    updateComponents(); // Update components when changing roof type
});

   document.getElementById('update').addEventListener('click', updateComponents);

          </script>

</body>
</html>


I have gotten it to work with this function, for the Dutch Gable roof type
All I had to do was update so it meshes the bottom vertices to the middle vertices, then the middle vertices to the top vertices to create 2 separate Meshes.

I’ll post the entire updated code as a resource when I get the rafters added and textures for shakes, shingles, etc. so others can utilize this resource in the future.


// Function to create a solid mesh from vertices
function createComplexMesh(vertices, color) {
    const geometry = new THREE.BufferGeometry();

    // Flatten the vertices array
    const positions = new Float32Array(vertices.length * 3);
    vertices.forEach((v, i) => {
        positions[i * 3] = v.x;
        positions[i * 3 + 1] = v.y;
        positions[i * 3 + 2] = v.z;
    });

    geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));

    // Define faces (triangles) for the geometry
    const indices = [
        0, 1, 2, // Bottom face
        0, 2, 3,
        4, 5, 6, // Middle face
        4, 6, 7,
        0, 1, 5, // Side face connecting bottom to middle
        0, 5, 4,
        1, 2, 6, // Side face connecting middle to top
        1, 6, 5,
        2, 3, 7, // Top face
        2, 7, 6,
        3, 0, 4, // Side face connecting bottom to middle
        3, 4, 7,
    ];

    geometry.setIndex(indices);
    geometry.computeVertexNormals(); // Calculate normals for lighting

  const material = new THREE.MeshBasicMaterial({ 
    color: color, 
    side: THREE.DoubleSide // Make the material double-sided
});

    return new THREE.Mesh(geometry, material);
}

// Create two solid meshes: bottom-middle and middle-top
const bottomMiddleMesh = createComplexMesh([...bottomVertices, ...middleVertices], 0x808080); // Grey color

// Create the middle-top mesh
const middleTopMesh = createComplexMesh([...middleVertices, ...topVertices], 0x808080); // Grey color

// Add meshes to the scene
scene.add(bottomMiddleMesh);
scene.add(middleTopMesh);