Search code examples
javascriptthree.js3dgeometry

How to make a Prismoid in Three.js?


I'm using Three.js and I'm trying to make a pyramid, but with its top cut off, and its base is a rectangle, and the top is also a rectangle, but not in the same proportions.

Heres the pic from wikipedia, but I want it to allow for rectangle bases

https://upload.wikimedia.org/wikipedia/commons/thumb/6/6a/Usech_kvadrat_piramid.png/110px-Usech_kvadrat_piramid.png

I think the word for it is Prismoid, except in my case the base isnt going to be a square, but a rectangle. Another thing I want to do with this is make the top not centered on the base, allowing me to create things like Parallelepiped

I've looked into CylinderGeometry and PolyhedronGeometry, but they both require that the base sides are all the same length.

What's the most effective way of making these shapes?


Solution

  • It was a long complicated process figuring this all out, but I did it! Thanks to @Łukasz D. Mastalerz for his help!

    heres the code:

    function createGeometry(width, depth, height, widthTop, depthTop, posTop = new THREE.Vector3(0,0,0)) {
    
        //create geometry
        let bufferGeometry = new BufferGeometry();
    
        //create base points
        const w2 = width / 2;
        const d2 = depth / 2;
        let v0 = new Vector3(-w2, 0, -d2);
        let v1 = new Vector3(w2, 0, -d2);
        let v2 = new Vector3(w2, 0, d2);
        let v3 = new Vector3(-w2, 0, d2);
        //create top points
        const wt2 = widthTop / 2;
        const dt2 = depthTop / 2;
        let v4 = new Vector3(-wt2, height, -dt2).add(posTop);
        let v5 = new Vector3(wt2, height, -dt2).add(posTop);
        let v6 = new Vector3(wt2, height, dt2).add(posTop);
        let v7 = new Vector3(-wt2, height, dt2).add(posTop);
        // points
        let points = [
            v0, v1, v2, v3,
            v4, v5, v6, v7,
        ];
    
        //vertices
        const vertices = new Float32Array(
            points
                .map(p => [p.x, p.y, p.z])
                .flat(Infinity)
        );
    
        //indices
        const indices = new Uint32Array([
    
            //right
            0, 3, 7,
            7, 4, 0,
    
            //left
            2, 1, 5,
            5, 6, 2,
    
            //top
            7, 6, 5,
            5, 4, 7,
    
            //bottom
            0, 1, 2,
            2, 3, 0,
    
            //back
            1, 0, 4,
            4, 5, 1,
    
            //front
            3, 2, 6,
            6, 7, 3,
    
        ]);
    
        //width uv constants
        const widthOffset = posTop.x;
        const maxWidth = Math.max(width, w2 + wt2 + Math.abs(widthOffset));
        const widthDiffLeft = w2 - wt2 + widthOffset;
        const widthDiffRight = width - (w2 + wt2 + widthOffset);
    
        //depth uv constants
        const depthOffset = posTop.z;
        const maxDepth = Math.max(depth, d2 + dt2 + Math.abs(depthOffset));
        const depthDiffLeft = d2 - dt2 + depthOffset;
        const depthDiffRight = depth - (d2 + dt2 + depthOffset);
    
        //uv
        const uvs = new Float32Array([
    
            //right
            Math.max(0, (0 - depthDiffLeft) / maxDepth), 0,//BL
            Math.min(1, (maxDepth + depthDiffRight) / maxDepth), 0,//BR
            Math.min(1, (maxDepth - depthDiffRight) / maxDepth), 1,//TR
            Math.min(1, (maxDepth - depthDiffRight) / maxDepth), 1,//TR
            Math.max(0, (0 + depthDiffLeft) / maxDepth), 1,//TL
            Math.max(0, (0 - depthDiffLeft) / maxDepth), 0,//BL
    
            //left
            Math.max(0, (0 - depthDiffRight) / maxDepth), 0,//BL
            Math.min(1, (maxDepth + depthDiffLeft) / maxDepth), 0,//BR
            Math.min(1, (maxDepth - depthDiffLeft) / maxDepth), 1,//TR
            Math.min(1, (maxDepth - depthDiffLeft) / maxDepth), 1,//TR
            Math.max(0, (0 + depthDiffRight) / maxDepth), 1,//TL
            Math.max(0, (0 - depthDiffRight) / maxDepth), 0,//BL
    
            //top
            0, 0,
            1, 0,
            1, 1,
            1, 1,
            0, 1,
            0, 0,
    
            //bottom
            0, 0,
            1, 0,
            1, 1,
            1, 1,
            0, 1,
            0, 0,
    
            //back
            Math.max(0, (0 - widthDiffRight) / maxWidth), 0,//BL
            Math.min(1, (maxWidth + widthDiffLeft) / maxWidth), 0,//BR
            Math.min(1, (maxWidth - widthDiffLeft) / maxWidth), 1,//TR
            Math.min(1, (maxWidth - widthDiffLeft) / maxWidth), 1,//TR
            Math.max(0, (0 + widthDiffRight) / maxWidth), 1,//TL
            Math.max(0, (0 - widthDiffRight) / maxWidth), 0,//BL
    
            //front
            Math.max(0, (0 - widthDiffLeft) / maxWidth), 0,//BL
            Math.min(1, (maxWidth + widthDiffRight) / maxWidth), 0,//BR
            Math.min(1, (maxWidth - widthDiffRight) / maxWidth), 1,//TR
            Math.min(1, (maxWidth - widthDiffRight) / maxWidth), 1,//TR
            Math.max(0, (0 + widthDiffLeft) / maxWidth), 1,//TL
            Math.max(0, (0 - widthDiffLeft) / maxWidth), 0,//BL
    
        ]);
    
        //compile it together
        bufferGeometry.setIndex(new BufferAttribute(indices, 1));
        bufferGeometry.setAttribute('position', new BufferAttribute(vertices, 3));
    
        const sideCount = 6;
        for (let i = 0; i < sideCount; i++) {
            bufferGeometry.addGroup(i * sideCount, sideCount, i);
        }
        bufferGeometry.attributes.position.needsUpdate = true;
    
        bufferGeometry = bufferGeometry.toNonIndexed();
    
        bufferGeometry.setAttribute('uv', new BufferAttribute(uvs, 2));
        bufferGeometry.attributes.uv.needsUpdate = true;
    
        //return
        return bufferGeometry;
    }
    
    1. first you have to define where the vertices are going to go (the vertices array).
    2. then you have to define the faces (the indices array). make sure to use the right-hand rule when declaring the points. in this method, i declared the points in the same order in each face so it makes the algorithms easier to program later on.
    3. after that come the uvs (the uvs array). this was the hard part. for each triangle, for each point, put 2 floats from 0 to 1 in the uvs array. The trouble is that up until now, we've been setting things using indexing, but the uvs require us to not use indexing (at least in my case). So to make it match, we have to call toNonIndexed() before we set the uvs. There's a lot of code in the uvs array declaration, but they all result in a number between 0 and 1. I put special code in there to make it look natural when the top isnt centered and when its a different size than the base.

    After that, compile it all together and you have a prismoid geometry that can also handle doing parallelepipeds.