import {log, BinaryAttribute} from '@deck.gl/core'; import {Geometry} from '@luma.gl/engine'; import {modifyPolygonWindingDirection, WINDING} from '@math.gl/polygon'; type ColumnGeometryProps = { id?: string; radius: number; height?: number; nradial?: number; vertices?: number[]; }; export default class ColumnGeometry extends Geometry { constructor(props: ColumnGeometryProps) { const {indices, attributes} = tesselateColumn(props); super({ ...props, indices, // @ts-expect-error attributes }); } } /* eslint-disable max-statements, complexity */ function tesselateColumn(props: ColumnGeometryProps): { indices: Uint16Array; attributes: Record; } { const {radius, height = 1, nradial = 10} = props; let {vertices} = props; if (vertices) { log.assert(vertices.length >= nradial); // `vertices` must contain at least `diskResolution` points vertices = vertices.flatMap(v => [v[0], v[1]]); modifyPolygonWindingDirection(vertices, WINDING.COUNTER_CLOCKWISE); } const isExtruded = height > 0; const vertsAroundEdge = nradial + 1; // loop const numVertices = isExtruded ? vertsAroundEdge * 3 + 1 // top, side top edge, side bottom edge, one additional degenerage vertex : nradial; // top const stepAngle = (Math.PI * 2) / nradial; // Used for wireframe const indices = new Uint16Array(isExtruded ? nradial * 3 * 2 : 0); // top loop, side vertical, bottom loop const positions = new Float32Array(numVertices * 3); const normals = new Float32Array(numVertices * 3); let i = 0; // side tesselation: 0, 1, 2, 3, 4, 5, ... // // 0 - 2 - 4 ... top // | / | / | // 1 - 3 - 5 ... bottom // if (isExtruded) { for (let j = 0; j < vertsAroundEdge; j++) { const a = j * stepAngle; const vertexIndex = j % nradial; const sin = Math.sin(a); const cos = Math.cos(a); for (let k = 0; k < 2; k++) { positions[i + 0] = vertices ? vertices[vertexIndex * 2] : cos * radius; positions[i + 1] = vertices ? vertices[vertexIndex * 2 + 1] : sin * radius; positions[i + 2] = (1 / 2 - k) * height; normals[i + 0] = vertices ? vertices[vertexIndex * 2] : cos; normals[i + 1] = vertices ? vertices[vertexIndex * 2 + 1] : sin; i += 3; } } // duplicate the last vertex to create proper degenerate triangle. positions[i + 0] = positions[i - 3]; positions[i + 1] = positions[i - 2]; positions[i + 2] = positions[i - 1]; i += 3; } // The column geometry is rendered as a triangle strip, so // in order to render sides and top in one go we need to use degenerate triangles. // Duplicate last vertex of side trinagles and first vertex of the top cap to preserve winding order. // top tesselation: 0, -1, 1, -2, 2, -3, 3, ... // // 0 -- 1 // / \ // -1 2 // | | // -2 3 // \ / // -3 -- 4 // for (let j = isExtruded ? 0 : 1; j < vertsAroundEdge; j++) { const v = Math.floor(j / 2) * Math.sign(0.5 - (j % 2)); const a = v * stepAngle; const vertexIndex = (v + nradial) % nradial; const sin = Math.sin(a); const cos = Math.cos(a); positions[i + 0] = vertices ? vertices[vertexIndex * 2] : cos * radius; positions[i + 1] = vertices ? vertices[vertexIndex * 2 + 1] : sin * radius; positions[i + 2] = height / 2; normals[i + 2] = 1; i += 3; } if (isExtruded) { let index = 0; for (let j = 0; j < nradial; j++) { // top loop indices[index++] = j * 2 + 0; indices[index++] = j * 2 + 2; // side vertical indices[index++] = j * 2 + 0; indices[index++] = j * 2 + 1; // bottom loop indices[index++] = j * 2 + 1; indices[index++] = j * 2 + 3; } } return { indices, attributes: { POSITION: {size: 3, value: positions}, NORMAL: {size: 3, value: normals} } }; }