最近在研究自定义建模,有一个多断柱模型比较有意思,分享下,就是利用几组点串,比如上中下,然后每组点又不一样多,点续还不一样,(比如第一个环的第一个点在左边,第二个环在右边),然后进行插值,连接三角网
主函数:
export function createMultiRingPrismMesh(rings: Vector3[][],material?: Material,
) {if (!material) {const texture = new TextureLoader().load(green)material = new MeshStandardMaterial({map:texture,color: 0x3399ff,side: DoubleSide,wireframe: false,})}if (rings.length < 2) {throw new Error('至少需要两个环来构建几何体')}// 将所有环插值为统一点数const targetCount = Math.max(...rings.map((r) => r.length))const processedRings = rings.map((ring) => {const center = computeCenter(ring)const angles = computeAngles(ring, center)const { ring: sortedRing, angles: sortedAngles } = sortRingByAngle(ring,angles,)return interpolateRingByAngles(sortedRing, sortedAngles, targetCount)})const vertices = []const uvs = []const indices = []const ringCount = processedRings.lengthconst count = targetCount// 顶点和UVprocessedRings.forEach((ring, ringIndex) => {ring.forEach((p, i) => {vertices.push(p.x, p.y, p.z)// UV 映射:U 是角度,V 是高度归一化// const angle = (i / count) * Math.PI * 2uvs.push(i / count, ringIndex / (ringCount - 1)) // U: 角度比例, V: 层级比例})})// 侧面三角形索引for (let i = 0; i < ringCount - 1; i++) {const offset = i * countfor (let j = 0; j < count; j++) {const curr = offset + jconst next = offset + ((j + 1) % count)const currTop = curr + countconst nextTop = next + countindices.push(curr, next, currTop)indices.push(next, nextTop, currTop)}}// 封底(第一个环)const bottomCenter = computeCenter(processedRings[0])const bottomCenterIndex = vertices.length / 3vertices.push(bottomCenter.x, bottomCenter.y, bottomCenter.z)uvs.push(0.5, 0.5)for (let i = 0; i < count; i++) {const next = (i + 1) % countindices.push(bottomCenterIndex, next, i)}// 封顶(最后一个环)const topCenter = computeCenter(processedRings[ringCount - 1])const topCenterIndex = vertices.length / 3const topOffset = (ringCount - 1) * countvertices.push(topCenter.x, topCenter.y, topCenter.z)uvs.push(0.5, 0.5)for (let i = 0; i < count; i++) {const next = (i + 1) % countindices.push(topCenterIndex, topOffset + i, topOffset + next)}const geometry = new BufferGeometry()geometry.setAttribute('position', new Float32BufferAttribute(vertices, 3))geometry.setAttribute('uv', new Float32BufferAttribute(uvs, 2))geometry.setIndex(indices)geometry.computeVertexNormals()return new Mesh(geometry, material)
}
辅助函数
/*** 计算环中心点(平均点)* @param {Vector3[]} ring* @returns {Vector3}*/
function computeCenter(ring: Vector3[]) {const center = new Vector3(0, 0, 0)ring.forEach((p) => center.add(p))center.multiplyScalar(1 / ring.length)return center
}
/*** 计算环中每点相对于中心的角度(0~2π)* 这里按 XZ 平面计算角度* @param {Vector3[]} ring* @param {Vector3} center* @returns {number[]} 角度数组,与ring点一一对应*/
function computeAngles(ring: Vector3[], center: Vector3) {return ring.map((p) => {const dx = p.x - center.xconst dz = p.z - center.zlet angle = Math.atan2(dz, dx)if (angle < 0) angle += Math.PI * 2return angle})
}function interpolateRingByAngles(ring: Vector3[],angles: number[],targetCount: number,
) {const result = []const len = ring.length// 角度归一化到 [0, 2PI)const TWO_PI = Math.PI * 2for (let i = 0; i < targetCount; i++) {const t = (i / targetCount) * TWO_PI// 找到 t 在 angles 中所在区间 [angles[j], angles[j+1]]// 注意最后一个区间是 [angles[len-1], angles[0] + 2PI]let j = 0for (; j < len; j++) {const a0 = angles[j]const a1 = angles[(j + 1) % len] + (j === len - 1 ? TWO_PI : 0)if (t >= a0 && t <= a1) {// 找到区间const p0 = ring[j]const p1 = ring[(j + 1) % len]const localT = (t - a0) / (a1 - a0)const interpolated = new Vector3().lerpVectors(p0, p1, localT)result.push(interpolated)break}}// 保险 fallback,如果没找到区间,返回第一个点if (j === len) {result.push(ring[0].clone())}}return result
}// 对角度和点排序(保证升序)
function sortRingByAngle(ring: Vector3[], angles: number[]) {const pairs = ring.map((p, i) => ({ p, angle: angles[i] }))pairs.sort((a, b) => a.angle - b.angle)return {ring: pairs.map((pair) => pair.p),angles: pairs.map((pair) => pair.angle),}
}
使用
createMultiRingPrismMesh([points,points2,points3])