Web编程技术营地
研究、演示、创新

Geometries

撰写时间:2023-09-18

修订时间:2026-05-02

Geometries类负责处理与顶点相关的数据,故有较多的方法,特此作明确的分类。

该类的各个方法的实现,均来源于一线日常实践所需,因此具有非常高的实用性。得益于该类,我们可以很优雅的方式来实现、扩展各种各样的Mesh类。

文件内容框架

import {vec3, mat4, glMatrix} from '/js/esm/gl-matrix/index.js'; const VERTICES_COMP_SIZE = 3; function Point(x, y, z) { return {x, y, z}; } class Geometries { ... } export default Geometries;

Point返回一个顶点对象。

下面开始Geometries的定义。

弧度转换

这两组方法用于弧度与角度值的互换。

获取顶点

GetVertexFromIndex

GetVertexFromIndex从一个由每3个元素代表一个顶点所构成的顶点数组中返回指定索引值的顶点。

源代码:

static GetVertexFromIndex(vertices, index) { index *= VERTICES_COMP_SIZE; return vertices.slice(index, index + VERTICES_COMP_SIZE); }

客户端代码:

GeoPC.js模块中的extend函数,将一个标准的PageConsole实例,扩展为支持打印顶点信息等功能的对象。

通过这种方式,在不影响原有对象功能的基础上,我们可以在特定领域内,自由、充分、独立地扩展任意对象的额外功能。

上面代码,从vertices中取出第1个顶点。

目前的实现是固定为顶点数组中每3个元素代表一个顶点。

GetVerticesFromIndices

GetVerticesFromIndices根据一个指定的索引值数组,从一个由每3个元素代表一个顶点所构成的顶点数组中返回一个包含多个顶点的顶点数组。

源代码:

GetVerticesFromIndices在内部调用上一节中的GetVertexFromIndex方法,取出各相应的顶点以组成一个新的顶点数组,并返回该数组。

客户端代码:

vertices是一个有5个顶点的数组,faceIndices将顶点数组索引值为[0, 3, 4]3个顶点定义为一个面,GetVerticesFromIndices方法据此从顶点数组中提取相应的顶点,并返回一个新的数组。

上一章中的FaceMesh类的构造方法中就使用了GetVerticesFromIndices方法用以方便地构建各个面。

获取顶点索引

static GetIndicesFromVertices(vertices, vertexSize = VERTICES_COMP_SIZE) { let lastVertexIndex = vertices.length / vertexSize - 1; let indices = []; for (let i = 0; i <= lastVertexIndex; i++) { indices.push(i); } return indices; } /* * Given the indices in an arry or in two arrays with same size, * return the front face triangle indices ready to be used for gl.TRIANGLES */ static GetCCWTriangleIndices(indices1, indices2) { let triangleIndices = []; // construct the base triangles if (indices2 === undefined) { indices1 = indices1.slice(); let firstIndex = indices1.shift(); while (indices1.length >= 2) { triangleIndices.push(firstIndex, indices1[0], indices1[1]); indices1.shift(); } } else { // construct the side triangles indices1 = indices1.slice(); indices1.push(indices1[0]); indices2 = indices2.slice(); indices2.push(indices2[0]); while(indices1.length >= 2) { // triangle 1 let v0 = indices1[0]; let v1 = indices1[1]; let v2 = indices2[0]; // triangle 2 let v3 = indices1[1]; let v4 = indices2[1]; let v5 = indices2[0]; triangleIndices.push(v0, v1, v2, v3, v4, v5); indices1.shift(); indices2.shift(); } } return triangleIndices; } /* * Given the indices of an array, * return the indices ready to be used for gl.LINES * * orgIndex specifies the only non-border point */ static GetLinesIndices(indices, isLoop, orgIndex) { indices = indices.slice(); let linesIndices = []; let borderIndices; if (orgIndex !== undefined) { borderIndices = Geometries.RemoveElementAtIndex(indices, orgIndex); borderIndices.forEach(element => linesIndices.push(orgIndex, element)); } else { borderIndices = indices; } let localIndices = borderIndices; if (isLoop) { localIndices.push(localIndices[0]); } while (localIndices.length >= 2) { linesIndices.push(localIndices[0], localIndices[1]); localIndices.shift(); } return linesIndices; } static RemoveElementAtIndex(arr, index) { return arr.slice(0, index).concat(arr.slice(index + 1)); } /* * * @param {Array} indices * the frist element is the origin index; * and the rest elements are the indices of vertices on the cirle * * @returns {Array|Geometries.GenTriangleIndicesInCircle.result} * such as [0, 1, 2, 0, 2, 3, ...] */ static GenTriangleIndicesInCircle(indices) { let orgIndex = indices.shift(); indices.push(indices[0]); let result = []; while(indices.length >= 2) { let index1 = indices.shift(); let index2 = indices[0]; result.push(orgIndex, index1, index2); } return result; }

转换2维数组

static To2DArrays(arr, compSize = 3) { let result = []; for (let index = 0; index < arr.length; index += compSize) { result.push(arr.slice(index, index + compSize)); } return result; }

生成顶点

static GenRegularPolygonPoints(radius, sidesNum, isIncludeOrg = true) { let degreeStep = 360 / sidesNum; let points = []; if (isIncludeOrg) { points.push(Point(0.0, 0.0, 0.0)); } let rotationDegree = 90; if (sidesNum === 4) { rotationDegree += 45; } let degree = rotationDegree; for (let pointNum = 1; pointNum <= sidesNum; pointNum++) { let radian = Geometries.RadianFromDegree(degree); let x = Math.cos(radian) * radius; let y = Math.sin(radian) * radius; points.push(Point(x, y, 0.0)); degree += degreeStep; } return points; } static GenRegularPolygonVertices(radius, sidesNum, isIncludeOrg = true) { let points = Geometries.GenRegularPolygonPoints(radius, sidesNum, isIncludeOrg); let vertices = []; for (let point of points) { const {x, y, z} = point; vertices.push(x, y, z); } return vertices; }

打印信息

static PrintVertices(vertices) { function appendSpace(str) { if (!str.startsWith('-')) { return ' ' + str; } return str; } console.log('==================== Vertices Info ===================='); let coordIndex = 0; for (let i = 0; i < vertices.length; i += VERTICES_COMP_SIZE) { console.log(`${coordIndex}: (${appendSpace(vertices[i].toFixed(2))}, ${appendSpace(vertices[i+1].toFixed(2))}, ${appendSpace(vertices[i+2].toFixed(2))})`); coordIndex++; } console.log('------------------------------------------'); console.log(`Total ${coordIndex} vertices, in forms of (x, y, z)`); console.log('\n'); } static PrintMatrix(matrix) { let matrixSize = Math.sqrt(matrix.length); let rows = matrixSize; let cols = matrixSize; // row-major // for (let row = 0; row < rows; row++) { // let text = ""; // for (let col = 0; col < cols; col++) { // text += matrix[row * rows + col] + ' '; // } // text += '\n'; // console.log(text); // } console.log('==================== Matrix Info ===================='); // gl-matrix is column-major for (let row = 0; row < rows; row++) { //console.log(`row: ${row}`); let text = ""; for (let col = 0; col < cols; col++) { text += matrix[row + col * cols].toFixed(2) + ' '; } text += '\n'; console.log(text); } } static GetMatrixRows(matrix) { let matrixSize = Math.sqrt(matrix.length); let rowSize = matrixSize; let colSize = matrixSize; let rows = []; // gl-matrix is column-major for (let row = 0; row < rowSize; row++) { let valuePerRow = []; for (let col = 0; col < colSize; col++) { valuePerRow.push(matrix[row + col * colSize]); } rows.push(valuePerRow); } return rows; }

矩阵与向量变换

static Mat4MulVec3(matrix, vertex) { if (vertex.length !== VERTICES_COMP_SIZE) { throw new Error('Only applies to a vertex of [x, y, z].'); } let newVertex = vec3.create(); vec3.transformMat4(newVertex, vertex, matrix); return Array.from(newVertex); } static ApplyMatrixToVertices(matrix, vertices) { if (vertices.length % VERTICES_COMP_SIZE !== 0) { throw new Error(`vertices's length is not times of ${VERTICES_COMP_SIZE}`); } let result = []; for (let i = 0; i < vertices.length; i += VERTICES_COMP_SIZE) { let vertex = vertices.slice(i, i + VERTICES_COMP_SIZE); let newVertex = vec3.create(); vec3.transformMat4(newVertex, vertex, matrix); newVertex = Array.from(newVertex); result.push(newVertex); } return result.flat(); } static RotateVerticesAroundAxis(vertices, axisStr, degree) { let newVertices = Geometries.To2DArrays(vertices); let out = vec3.create(); let result = []; newVertices.forEach((vertex) => { if (axisStr === 'x') { vec3.rotateX(out, vertex, [0.0, 0.0, 0.0], glMatrix.toRadian(degree)); } else if (axisStr === 'y') { vec3.rotateY(out, vertex, [0.0, 0.0, 0.0], glMatrix.toRadian(degree)); } else if (axisStr === 'z') { vec3.rotateZ(out, vertex, [0.0, 0.0, 0.0], glMatrix.toRadian(degree)); } else { throw new Error('Parameter axisStr should be from ["x", "y", "z]'); } result.push(Array.from(out)); }); return result.flat(); }

生成几何体

static GenCube(sideLength) { let halfLen = sideLength / 2; let vertices = [ // top face -halfLen, sideLength, -halfLen, // V0 -halfLen, sideLength, halfLen, // V1 halfLen, sideLength, halfLen, // V2 halfLen, sideLength, -halfLen, // V3 // bottom face -halfLen, 0, -halfLen, // V4 -halfLen, 0, halfLen, // V5 halfLen, 0, halfLen, // V6 halfLen, 0, -halfLen // V7 ]; let faces = [ [0, 1, 2, 3], // top [5, 4, 7, 6], // bottom [1, 5, 6, 2], // front [3, 7, 4, 0], // back [0, 4, 5, 1], // left [2, 6, 7, 3] // right ]; return { vertices: vertices, faces: faces }; } static GenCuboid(length, width, height) { let vertices = [ // top face -length/2, height, -width/2, // V0 -length/2, height, width/2, // V1 length/2, height, width/2, // V2 length/2, height, -width/2, // V3 // bottom face -length/2, 0, -width/2, // V4 -length/2, 0, width/2, // V5 length/2, 0, width/2, // V6 length/2, 0, -width/2 // V7 ]; let faces = [ [0, 1, 2, 3], // top [5, 4, 7, 6], // bottom [1, 5, 6, 2], // front [3, 7, 4, 0], // back [0, 4, 5, 1], // left [2, 6, 7, 3] // right ]; return { vertices: vertices, faces: faces }; } /* * A cuboid that can hold something */ static GenBox(length, width, height, lengthThick, widthThick, bottomThick) { let vertices = [ // outer top -length/2, height, -width/2, // V0 -length/2, height, width/2, // V1 length/2, height, width/2, // V2 length/2, height, -width/2, // V3 // outer bottom -length/2, 0, -width/2, // V4 -length/2, 0, width/2, // V5 length/2, 0, width/2, // V6 length/2, 0, -width/2, // V7 // inner top -length/2 + lengthThick, height, -width/2 + widthThick, // V8 -length/2 + lengthThick, height, width/2 - widthThick, // V9 length/2 - lengthThick, height, width/2 - widthThick, // V10 length/2 - lengthThick, height, -width/2 + widthThick, // V11 // inner bottom -length/2 + lengthThick, bottomThick, -width/2 + widthThick, // V12 -length/2 + lengthThick, bottomThick, width/2 - widthThick, // V13 length/2 - lengthThick, bottomThick, width/2 - widthThick, // V14 length/2 - lengthThick, bottomThick, -width/2 + widthThick // V15 ]; let faces = [ [5, 4, 7, 6], // outer bottom [1, 5, 6, 2], // outer south [3, 7, 4, 0], // outer north [0, 4, 5, 1], // outer west [2, 6, 7, 3], // outer east [8, 0, 1, 9], // top west [9, 1, 2, 10], // top south [10, 2, 3, 11], // top east [11, 3, 0, 8], // top north [8, 12, 15, 11], // inner north [9, 13, 12, 8], // inner west [10, 14, 13, 9], // inner south [11, 15, 14, 10], // innner east [12, 13, 14, 15] // inner bottom ]; return { vertices: vertices, faces: faces }; } static GenCone(radius, sidesNum, height) { let vertices = Geometries.GenRegularPolygonVertices(radius, sidesNum, true); let rotatedVertices = Geometries.RotateVerticesAroundAxis(vertices, 'x', -90); rotatedVertices[1] += height; let bottomFace = []; for (let index = 1; index < rotatedVertices.length / 3; index++) { bottomFace.push(index); } let faces = []; faces.push(bottomFace); let tempBottomFace = bottomFace.slice(); tempBottomFace.push(tempBottomFace[0]); while (tempBottomFace.length >= 2) { let face = []; let v1 = tempBottomFace[0]; let v2 = tempBottomFace[1]; face.push(0, v1, v2); faces.push(face); tempBottomFace.shift(); } return { vertices: rotatedVertices, faces: faces }; } static GenConeFrustum(bottomRadius, sidesNum, height, topRadius) { let bottomVertices = Geometries.GenRegularPolygonVertices(bottomRadius, sidesNum, false); bottomVertices = Geometries.RotateVerticesAroundAxis(bottomVertices, 'x', -90); let topVertices = Geometries.GenRegularPolygonVertices(topRadius, sidesNum, false); topVertices = Geometries.RotateVerticesAroundAxis(topVertices, 'x', -90); for(let index = 0; index < topVertices.length; index += 3) { topVertices[index+1] += height; } let vertices = []; vertices = vertices.concat(topVertices); vertices = vertices.concat(bottomVertices); let topFace = []; for (let index = 0; index < topVertices.length / 3; index++) { topFace.push(index); } let bottomFace = []; for (let index = 0; index < bottomVertices.length / 3; index++) { bottomFace.push(topFace.length + index); } let faces = []; faces.push(topFace); faces.push(bottomFace); let tempTopFace = topFace.slice(); tempTopFace.push(tempTopFace[0]); let tempBottomFace = bottomFace.slice(); tempBottomFace.push(tempBottomFace[0]); while (tempTopFace.length >= 2) { let face = []; let v1 = tempTopFace[0]; let v2 = tempBottomFace[0]; let v3 = tempBottomFace[1]; let v4 = tempTopFace[1]; face.push(v1, v2, v3, v4); faces.push(face); tempTopFace.shift(); tempBottomFace.shift(); } return { vertices: vertices, faces: faces }; } static GenCylinder(radius, sidesNum, height) { return Geometries.GenConeFrustum(radius, sidesNum, height, radius); }

参考资源

  1. WebGL 2.0 Specification