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
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?
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;
}
vertices
array).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.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.