UNPKG

primitive-geometry

Version:

Geometries for 3D rendering, including normals, UVs and cell indices (faces). Perfect if you want to supercharge your dependency folder... with 30KB of geometries.

294 lines (265 loc) 6.12 kB
/** @module roundedCube */ import { checkArguments, computePlane, getCellsTypedArray, normalize, TMP, } from "./utils.js"; /** * @typedef {object} RoundedCubeOptions * @property {number} [sx=1] * @property {number} [sy=sx] * @property {number} [sz=sx] * @property {number} [nx=1] * @property {number} [ny=nx] * @property {number} [nz=nx] * @property {number} [radius=sx * 0.25] * @property {number} [roundSegments=8] * @property {number} [edgeSegments=1] */ /** * @alias module:roundedCube * @param {RoundedCubeOptions} [options={}] * @returns {import("../types.js").SimplicialComplex} */ function roundedCube({ sx = 1, sy = sx, sz = sx, nx = 1, ny = nx, nz = nx, radius = sx * 0.25, roundSegments = 8, edgeSegments = 1, } = {}) { checkArguments(arguments); const size = (nx + 1) * (ny + 1) * 2 + (nx + 1) * (nz + 1) * 2 + (nz + 1) * (ny + 1) * 2 + (roundSegments + 1) * (roundSegments + 1) * 24 + (roundSegments + 1) * (edgeSegments + 1) * 24; const geometry = { positions: new Float32Array(size * 3), normals: new Float32Array(size * 3), uvs: new Float32Array(size * 2), cells: new (getCellsTypedArray(size))( (nx * ny * 2 + nx * nz * 2 + nz * ny * 2 + roundSegments * roundSegments * 24 + roundSegments * edgeSegments * 24) * 6, ), }; const halfSX = sx * 0.5; const halfSY = sy * 0.5; const halfSZ = sz * 0.5; const r2 = radius * 2; const widthX = sx - r2; const widthY = sy - r2; const widthZ = sz - r2; const faceSX = widthX / sx; const faceSY = widthY / sy; const faceSZ = widthZ / sz; const radiusSX = radius / sx; const radiusSY = radius / sy; const radiusSZ = radius / sz; const indices = { vertex: 0, cell: 0 }; const PLANES = [ [ widthX, widthY, nx, ny, "z", halfSZ, [faceSX, faceSY], [radiusSX, radiusSY], (x, y) => [x, y, 0], ], [ widthX, widthY, nx, ny, "-z", -halfSZ, [faceSX, faceSY], [radiusSX, radiusSY], (x, y) => [-x, y, 0], ], [ widthZ, widthY, nz, ny, "-x", -halfSX, [faceSZ, faceSY], [radiusSZ, radiusSY], (x, y) => [0, y, x], ], [ widthZ, widthY, nz, ny, "x", halfSX, [faceSZ, faceSY], [radiusSZ, radiusSY], (x, y) => [0, y, -x], ], [ widthX, widthZ, nx, nz, "y", halfSY, [faceSX, faceSZ], [radiusSX, radiusSZ], (x, y) => [x, 0, -y], ], [ widthX, widthZ, nx, nz, "-y", -halfSY, [faceSX, faceSZ], [radiusSX, radiusSZ], (x, y) => [x, 0, y], ], ]; const uvOffsetCorner = (su, sv) => [ [0, 0], [1 - radius / (su + r2), 0], [1 - radius / (su + r2), 1 - radius / (sv + r2)], [0, 1 - radius / (sv + r2)], ]; const uvOffsetStart = (_, sv) => [0, radius / (sv + r2)]; const uvOffsetEnd = (su, sv) => [1 - radius / (su + r2), radius / (sv + r2)]; for (let j = 0; j < PLANES.length; j++) { const [su, sv, nu, nv, direction, pw, uvScale, uvOffset, center] = PLANES[j]; // Cube faces computePlane( geometry, indices, su, sv, nu, nv, direction, pw, false, uvScale, uvOffset, ); // Corner order: ccw uv-like order and L/B (0) R/T (2) // 0,1 -- 1,1 // | -- | // 0,0 -- 1,0 for (let i = 0; i < 4; i++) { const ceil = Math.ceil(i / 2) % 2; const floor = Math.floor(i / 2) % 2; const x = (ceil === 0 ? -1 : 1) * (su + radius) * 0.5; const y = (floor === 0 ? -1 : 1) * (sv + radius) * 0.5; // Corners computePlane( geometry, indices, radius, radius, roundSegments, roundSegments, direction, pw, false, [radius / (su + r2), radius / (sv + r2)], uvOffsetCorner(su, sv)[i], center(x, y), ); // Edges if (i === 0 || i === 2) { // Left / Right computePlane( geometry, indices, radius, sv, roundSegments, edgeSegments, direction, pw, false, [uvOffset[0], uvScale[1]], ceil === 0 ? uvOffsetStart(su, sv) : uvOffsetEnd(su, sv), center(x, 0), ); // Bottom/Top computePlane( geometry, indices, su, radius, edgeSegments, roundSegments, direction, pw, false, [uvScale[0], uvOffset[1]], floor === 0 ? [...uvOffsetStart(sv, su)].reverse() : [...uvOffsetEnd(sv, su)].reverse(), center(0, y), ); } } } const rx = widthX * 0.5; const ry = widthY * 0.5; const rz = widthZ * 0.5; for (let i = 0; i < geometry.positions.length; i += 3) { const position = [ geometry.positions[i], geometry.positions[i + 1], geometry.positions[i + 2], ]; TMP[0] = position[0]; TMP[1] = position[1]; TMP[2] = position[2]; if (position[0] < -rx) { position[0] = -rx; } else if (position[0] > rx) { position[0] = rx; } if (position[1] < -ry) { position[1] = -ry; } else if (position[1] > ry) { position[1] = ry; } if (position[2] < -rz) { position[2] = -rz; } else if (position[2] > rz) { position[2] = rz; } TMP[0] -= position[0]; TMP[1] -= position[1]; TMP[2] -= position[2]; normalize(TMP); geometry.normals[i] = TMP[0]; geometry.normals[i + 1] = TMP[1]; geometry.normals[i + 2] = TMP[2]; geometry.positions[i] = position[0] + radius * TMP[0]; geometry.positions[i + 1] = position[1] + radius * TMP[1]; geometry.positions[i + 2] = position[2] + radius * TMP[2]; } return geometry; } export default roundedCube;