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.

225 lines (198 loc) 4.92 kB
/** @module roundedRectangle */ import { checkArguments, computePlane, getCellsTypedArray, TMP, } from "./utils.js"; /** * @typedef {object} RoundedCubeOptions * @property {number} [sx=1] * @property {number} [sy=sx] * @property {number} [nx=1] * @property {number} [ny=nx] * @property {number} [radius=sx * 0.25] * @property {number} [roundSegments=8] * @property {number} [edgeSegments=1] */ /** * @alias module:roundedRectangle * @param {RoundedCubeOptions} [options={}] * @returns {import("../types.js").SimplicialComplex} */ function roundedRectangle({ sx = 1, sy = sx, nx = 1, ny = nx, radius = sx * 0.25, roundSegments = 8, edgeSegments = 1, } = {}) { checkArguments(arguments); const size = (nx + 1) * (ny + 1) + (roundSegments + 1) * (roundSegments + 1) * 4 + (roundSegments + 1) * (edgeSegments + 1) * 4; const geometry = { positions: new Float32Array(size * 3), normals: new Float32Array(size * 3), uvs: new Float32Array(size * 2), cells: new (getCellsTypedArray(size))( (nx * ny + roundSegments * roundSegments * 4 + roundSegments * edgeSegments * 4) * 6, ), }; const r2 = radius * 2; const widthX = sx - r2; const widthY = sy - r2; const faceSX = widthX / sx; const faceSY = widthY / sy; const radiusSX = radius / sx; const radiusSY = radius / sy; const indices = { vertex: 0, cell: 0 }; const uvOffsetCorner = (su, sv) => [ [radius / (su + r2), 0], [1 - radius / (su + r2), 0], [1, 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)]; const [su, sv, nu, nv, direction, pw, uvScale, uvOffset, center] = [ widthX, widthY, nx, ny, "z", 0, [faceSX, faceSY], [radiusSX, radiusSY], (x, y) => [x, y, 0], ]; // Plane face 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; // Flip for quad seams to be radial const flip = i % 2 === 0; // Corners computePlane( geometry, indices, radius, radius, roundSegments, roundSegments, flip ? "-z" : "z", pw, false, [(flip ? -1 : 1) * (radius / (su + r2)), radius / (sv + r2)], uvOffsetCorner(su, sv)[i], center(x, y), !flip, ); // 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; 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]; let needsRounding = false; if (position[0] < -rx) { if (position[1] < -ry) { position[0] = -rx; position[1] = -ry; needsRounding = true; } else if (position[1] > ry) { position[0] = -rx; position[1] = ry; needsRounding = true; } } else if (position[0] > rx) { if (position[1] < -ry) { position[0] = rx; position[1] = -ry; needsRounding = true; } else if (position[1] > ry) { position[0] = rx; position[1] = ry; needsRounding = true; } } TMP[0] -= position[0]; TMP[1] -= position[1]; geometry.normals[i + 2] = 1; if (needsRounding) { const x = Math.sqrt(TMP[0] ** 2 + TMP[1] ** 2) / Math.max(Math.abs(TMP[0]), Math.abs(TMP[1])); geometry.positions[i] = position[0] + TMP[0] / x; geometry.positions[i + 1] = position[1] + TMP[1] / x; } } return geometry; } export default roundedRectangle;