import { MathUtils } from '../math/MathUtils';
/**
* @class
* The mesh builder.
* @memberof THING
*/
class MeshBuilder {
/**
* @typedef {Object} MeshResult
* @property {Array<Number>} position The position.
* @property {Array<Number>} normal The normal.
* @property {Array<Number>} uv The uv.
* @property {Array<Number>} index The index of position.
*/
/**
* Create plane.
* @private
*/
static createPlane(width = 1, height = 1, widthSegments = 1, heightSegments = 1) {
const plane = {};
plane.position = [];
plane.normal = [];
plane.uv = [];
plane.index = [];
const segmentWidth = width / widthSegments;
const segmentHeight = height / heightSegments;
for (let j = 0; j <= heightSegments; j++) {
const yPos = j * segmentHeight - height / 2;
for (let i = 0; i <= widthSegments; i++) {
const xPos = i * segmentWidth - width / 2;
plane.position.push(xPos, yPos, 0);
plane.normal.push(0, 0, 1);
plane.uv.push(i / widthSegments, j / heightSegments);
}
}
for (let j = 0; j < heightSegments; j++) {
for (let i = 0; i < widthSegments; i++) {
const a = i + (widthSegments + 1) * j;
const b = i + (widthSegments + 1) * (j + 1);
const c = a + 1;
const d = b + 1;
plane.index.push(a, b, c);
plane.index.push(b, d, c);
}
}
return plane;
}
/**
* Create circle.
* @param {Object} options The options.
* @property {Number} options.radius The radius.
* @property {Number} options.segments The number of disc segments.
* @property {Number} options.startRad The starting angle.
* @returns {MeshResult}
* @example
* let cirecle = THING.MeshBuilder.createCircle();
* // @expect(cirecle.index.length == 192 );
*/
static createCircle({ radius = 1, segments = 64, startRad = 0 } = {}) {
let circle = {};
circle.position = [];
circle.normal = [];
circle.uv = [];
circle.index = [];
circle.position.push(0, 0, 0);
circle.normal.push(0, 0, 1);
circle.uv.push(0.5, 0.5);
for (let s = 0, i = 3; s <= segments; s++, i += 3) {
const segment = startRad + s / segments * MathUtils.PI * 2;
circle.position.push(radius * MathUtils.cos(segment), radius * MathUtils.sin(segment), 0);
circle.normal.push(0, 0, 1);
circle.uv.push((circle.position[i] / radius + 1) / 2, (circle.position[i + 1] / radius + 1) / 2);
}
for (let i = 1; i <= segments; i++) {
circle.index.push(i, i + 1, 0);
}
return circle;
}
/**
* Create cylinder.
* @param {Object} options The options.
* @property {Number} options.radiusTop The top radius.
* @property {Number} options.radiusBottom The bottom radius.
* @property {Number} options.height The height.
* @property {Number} options.radialSegments The number of divisions.
* @property {Number} options.heightSegments The number of height divisions.
* @property {Boolean} options.openEnded The calculate the top and bottom surfaces.
* @property {Number} options.thetaStart The starting angle.
* @property {Number} options.thetaLength The end angle.
* @property {Number} options.poslength The offset.
* @returns {MeshResult}
*/
static createCylinder({ radiusTop = 1, radiusBottom = 1, height = 2, radialSegments = 64, heightSegments = 1, openEnded = false, thetaStart = 0, thetaLength = MathUtils.PI * 2, poslength = 0 } = {}) {
let cylinder = {};
cylinder.position = [];
cylinder.normal = [];
cylinder.uv = [];
cylinder.index = [];
radialSegments = MathUtils.floor(radialSegments);
heightSegments = MathUtils.floor(heightSegments);
let indexTemp = 0;
const indexArray = [];
const halfHeight = height / 2;
generateTorso();
if (openEnded === false) {
if (radiusTop > 0) generateCap(true);
if (radiusBottom > 0) generateCap(false);
}
function generateTorso() {
const slope = (radiusBottom - radiusTop) / height;
for (let y = 0; y <= heightSegments; y++) {
const indexRow = [];
const v = y / heightSegments;
const radius = v * (radiusBottom - radiusTop) + radiusTop;
for (let x = 0; x <= radialSegments; x++) {
const u = x / radialSegments;
const theta = u * thetaLength + thetaStart;
const sinTheta = MathUtils.sin(theta);
const cosTheta = MathUtils.cos(theta);
const normalNor = MathUtils.normalizeVector([sinTheta, slope, cosTheta]);
cylinder.position.push(radius * sinTheta, -v * height + halfHeight, radius * cosTheta);
cylinder.normal.push(normalNor[0], normalNor[1], normalNor[2]);
cylinder.uv.push(u, 1 - v);
indexRow.push(indexTemp++);
}
indexArray.push(indexRow);
}
for (let x = 0; x < radialSegments; x++) {
for (let y = 0; y < heightSegments; y++) {
const a = indexArray[y][x];
const b = indexArray[y + 1][x];
const c = indexArray[y + 1][x + 1];
const d = indexArray[y][x + 1];
cylinder.index.push(a + poslength, b + poslength, d + poslength);
cylinder.index.push(b + poslength, c + poslength, d + poslength);
}
}
}
function generateCap(top) {
const centerIndexStart = indexTemp;
const radius = (top === true) ? radiusTop : radiusBottom;
const sign = (top === true) ? 1 : -1;
for (let x = 1; x <= radialSegments; x++) {
cylinder.position.push(0, halfHeight * sign, 0);
cylinder.normal.push(0, sign, 0);
cylinder.uv.push(0.5, 0.5);
indexTemp++;
}
const centerIndexEnd = indexTemp;
for (let x = 0; x <= radialSegments; x++) {
const u = x / radialSegments;
const theta = u * thetaLength + thetaStart;
const cosTheta = MathUtils.cos(theta);
const sinTheta = MathUtils.sin(theta);
cylinder.position.push(radius * sinTheta, halfHeight * sign, radius * cosTheta);
cylinder.normal.push(0, sign, 0);
cylinder.uv.push((sinTheta * 0.5) + 0.5, -(cosTheta * 0.5 * sign) + 0.5);
indexTemp++;
}
for (let x = 0; x < radialSegments; x++) {
const c = centerIndexStart + x;
const i = centerIndexEnd + x;
if (top === true) {
cylinder.index.push(i + poslength, i + 1 + poslength, c + poslength);
}
else {
cylinder.index.push(i + 1 + poslength, i + poslength, c + poslength);
}
}
}
return cylinder;
}
/**
* Create torus.
* @param {Object} options The options.
* @property {Number} options.radius The inner radius of ring.
* @property {Number} options.tube The width.
* @property {Number} options.radialSegments The number of tangent circle segments.
* @property {Number} options.tubularSegments The number of ring segments.
* @property {Number} options.arc The display range.
* @returns {MeshResult}
*/
static createTorus({ radius = 0.8, tube = 0.2, radialSegments = 64, tubularSegments = 64, arc = MathUtils.PI * 2 } = {}) {
let torus = {};
torus.position = [];
torus.normal = [];
torus.uv = [];
torus.index = [];
radialSegments = MathUtils.floor(radialSegments);
tubularSegments = MathUtils.floor(tubularSegments);
let centerTemp = [];
let vertexTemp = [];
let normalTemp = [];
for (let j = 0; j <= radialSegments; j++) {
for (let i = 0; i <= tubularSegments; i++) {
const u = i / tubularSegments * arc;
const v = j / radialSegments * MathUtils.PI * 2;
vertexTemp = [
(radius + tube * MathUtils.cos(v)) * MathUtils.cos(u),
(radius + tube * MathUtils.cos(v)) * MathUtils.sin(u),
tube * MathUtils.sin(v)
];
torus.position.push(
(radius + tube * MathUtils.cos(v)) * MathUtils.cos(u),
(radius + tube * MathUtils.cos(v)) * MathUtils.sin(u),
tube * MathUtils.sin(v)
);
centerTemp = [radius * MathUtils.cos(u), radius * MathUtils.sin(u), 0];
normalTemp = MathUtils.normalizeVector(MathUtils.subVector(vertexTemp, centerTemp));
torus.normal.push(normalTemp[0], normalTemp[1], normalTemp[2]);
torus.uv.push(i / tubularSegments);
torus.uv.push(j / radialSegments);
}
}
for (let j = 1; j <= radialSegments; j++) {
for (let i = 1; i <= tubularSegments; i++) {
const a = (tubularSegments + 1) * j + i - 1;
const b = (tubularSegments + 1) * (j - 1) + i - 1;
const c = (tubularSegments + 1) * (j - 1) + i;
const d = (tubularSegments + 1) * j + i;
torus.index.push(a, b, d);
torus.index.push(b, c, d);
}
}
return torus;
}
/**
* Create capsule.
* @param {Object} options The options.
* @property {Number} options.radius The semicircle radius.
* @property {Number} options.cylinderHeight The column height.
* @property {Number} options.widthSegments The widthSegments.
* @property {Number} options.heightSegments The heightSegments.
* @returns {MeshResult}
*/
static createCapsule({ radius = 0.5, cylinderHeight = 1, widthSegments = 64, heightSegments = 64 } = {}) {
let capsule = {};
capsule.position = [];
capsule.normal = [];
capsule.uv = [];
capsule.index = [];
let upSphere = mathSphere(radius, widthSegments, heightSegments, 0, -MathUtils.PI * 2, -MathUtils.PI / 2, MathUtils.PI, cylinderHeight / 2);
let downSphere = mathSphere(radius, widthSegments, heightSegments, 0, -MathUtils.PI * 2, MathUtils.PI / 2, MathUtils.PI, - cylinderHeight / 2, upSphere.position.length / 3);
let cylinder = this.createCylinder({
radiusTop: radius, radiusBottom: radius, height: cylinderHeight, radialSegments: heightSegments, heightSegments: 1,
openEnded: true, thetaStart: -MathUtils.PI / 4 * 2, thetaLength: MathUtils.PI * 2, poslength: upSphere.position.length / 3 + downSphere.position.length / 3
})
capsule.position = [
...upSphere.position,
...downSphere.position,
...cylinder.position
];
capsule.normal = [
...upSphere.normal,
...downSphere.normal,
...cylinder.normal
];
capsule.uv = [
...upSphere.uv,
...downSphere.uv,
...cylinder.uv
];
capsule.index = [
...upSphere.index,
...downSphere.index,
...cylinder.index
];
function mathSphere(radius = 10, widthSegments = 8, heightSegments = 8, phiStart = 0, phiLength = MathUtils.PI * 2,
thetaStart = MathUtils.PI, thetaLength = MathUtils.PI, heightOffset = 0, poslength = 0) {
let sphere = {};
sphere.position = [];
sphere.normal = [];
sphere.uv = [];
sphere.index = [];
widthSegments = MathUtils.max(3, MathUtils.floor(widthSegments));
heightSegments = MathUtils.max(2, MathUtils.floor(heightSegments));
const thetaEnd = MathUtils.min(thetaStart + thetaLength, MathUtils.PI);
let indexTemp = 0;
const grid = [];
for (let iy = 0; iy <= heightSegments; iy++) {
const verticesRow = [];
const v = iy / heightSegments;
let uOffset = 0;
if (iy == 0 && thetaStart == 0) {
uOffset = 0.5 / widthSegments;
}
else if (iy == heightSegments && thetaEnd == MathUtils.PI) {
uOffset = -0.5 / widthSegments;
}
for (let ix = 0; ix <= widthSegments; ix++) {
const u = ix / widthSegments;
sphere.position.push(
-radius * MathUtils.cos(phiStart + u * phiLength) * MathUtils.sin(thetaStart + v * thetaLength),
radius * MathUtils.cos(thetaStart + v * thetaLength) + heightOffset,
radius * MathUtils.sin(phiStart + u * phiLength) * MathUtils.sin(thetaStart + v * thetaLength)
);
let normali = [
-radius * MathUtils.cos(phiStart + u * phiLength) * MathUtils.sin(thetaStart + v * thetaLength),
radius * MathUtils.cos(thetaStart + v * thetaLength),
radius * MathUtils.sin(phiStart + u * phiLength) * MathUtils.sin(thetaStart + v * thetaLength)
]
normali = MathUtils.normalizeVector(normali);
sphere.normal.push(normali[0], normali[1], normali[2]);
if (thetaStart > 0) { sphere.uv.push(-u, -v); }
else { sphere.uv.push(-u, v); }
verticesRow.push(indexTemp++);
}
grid.push(verticesRow);
}
for (let iy = 0; iy < heightSegments / 2; iy++) {
for (let ix = 0; ix < widthSegments; ix++) {
const a = grid[iy][ix + 1];
const b = grid[iy][ix];
const c = grid[iy + 1][ix];
const d = grid[iy + 1][ix + 1];
if (thetaStart > 0) {
sphere.index.push(a + poslength, d + poslength, b + poslength);
sphere.index.push(b + poslength, d + poslength, c + poslength);
} else {
sphere.index.push(b + poslength, c + poslength, d + poslength);
sphere.index.push(b + poslength, d + poslength, a + poslength);
}
}
}
return sphere;
}
return capsule;
}
static createSphere({ radius = 1, widthSegments = 32, heightSegments = 16, phiStart = 0, phiLength = MathUtils.PI * 2,
thetaStart = 0, thetaLength = MathUtils.PI } = {}) {
let sphere = {};
sphere.position = [];
sphere.normal = [];
sphere.uv = [];
sphere.index = [];
widthSegments = MathUtils.max(3, MathUtils.floor(widthSegments));
heightSegments = MathUtils.max(2, MathUtils.floor(heightSegments));
const thetaEnd = MathUtils.min(thetaStart + thetaLength, MathUtils.PI);
let indexTemp = 0;
const grid = [];
for (let iy = 0; iy <= heightSegments; iy++) {
const verticesRow = [];
const v = iy / heightSegments;
let uOffset = 0;
if (iy == 0 && thetaStart == 0) {
uOffset = 0.5 / widthSegments;
}
else if (iy == heightSegments && thetaEnd == MathUtils.PI) {
uOffset = -0.5 / widthSegments;
}
for (let ix = 0; ix <= widthSegments; ix++) {
const u = ix / widthSegments;
let vertex = [
-radius * MathUtils.cos(phiStart + u * phiLength) * MathUtils.sin(thetaStart + v * thetaLength),
radius * MathUtils.cos(thetaStart + v * thetaLength),
radius * MathUtils.sin(phiStart + u * phiLength) * MathUtils.sin(thetaStart + v * thetaLength)
];
sphere.position.push(...vertex);
let normali = MathUtils.normalizeVector(vertex);
sphere.normal.push(...normali);
sphere.uv.push(u + uOffset, 1 - v);
verticesRow.push(indexTemp++);
}
grid.push(verticesRow);
}
for (let iy = 0; iy < heightSegments; iy++) {
for (let ix = 0; ix < widthSegments; ix++) {
const a = grid[iy][ix + 1];
const b = grid[iy][ix];
const c = grid[iy + 1][ix];
const d = grid[iy + 1][ix + 1];
if (iy !== 0 || thetaStart > 0) sphere.index.push(a, b, d);
if (iy !== heightSegments - 1 || thetaEnd < Math.PI) sphere.index.push(b, c, d);
}
}
return sphere;
}
static createRing({ innerRadius = 0.5, outerRadius = 1, thetaSegments = 32, phiSegments = 1, thetaStart = 0, thetaLength = Math.PI * 2 } = {}) {
let ring = {};
ring.position = [];
ring.normal = [];
ring.uv = [];
ring.index = [];
thetaSegments = MathUtils.max(3, MathUtils.floor(thetaSegments));
phiSegments = MathUtils.max(2, MathUtils.floor(phiSegments));
let radius = innerRadius;
const radiusStep = ((outerRadius - innerRadius) / phiSegments);
for (let j = 0; j <= phiSegments; j++) {
for (let i = 0; i <= thetaSegments; i++) {
// values are generate from the inside of the ring to the outside
const segment = thetaStart + i / thetaSegments * thetaLength;
// vertex
const vertex = [
radius * MathUtils.cos(segment),
radius * MathUtils.sin(segment),
0
];
ring.position.push(...vertex);
// normal
ring.normal.push(0, 0, 1);
// uv
const uv = [
(vertex[0] / outerRadius + 1) / 2,
(vertex[1] / outerRadius + 1) / 2
];
ring.uv.push(...uv);
}
// increase the radius for next row of vertices
radius += radiusStep;
}
// indices
for (let j = 0; j < phiSegments; j++) {
const thetaSegmentLevel = j * (thetaSegments + 1);
for (let i = 0; i < thetaSegments; i++) {
const segment = i + thetaSegmentLevel;
const a = segment;
const b = segment + thetaSegments + 1;
const c = segment + thetaSegments + 2;
const d = segment + 1;
// faces
ring.index.push(a, b, d);
ring.index.push(b, c, d);
}
}
return ring;
}
}
export { MeshBuilder };