@luma.gl/engine
Version:
3D Engine Components for luma.gl
146 lines (128 loc) • 4.09 kB
text/typescript
// luma.gl
// SPDX-License-Identifier: MIT
// Copyright (c) vis.gl contributors
import {Geometry} from '../geometry/geometry';
import {uid} from '../utils/uid';
const INDEX_OFFSETS = {
x: [2, 0, 1],
y: [0, 1, 2],
z: [1, 2, 0]
};
export type TruncatedConeGeometryProps = {
topRadius?: number;
bottomRadius?: number;
topCap?: boolean;
bottomCap?: boolean;
height?: number;
nradial?: number;
nvertical?: number;
verticalAxis?: 'x' | 'y' | 'z';
};
/**
* Primitives inspired by TDL http://code.google.com/p/webglsamples/,
* copyright 2011 Google Inc. new BSD License
* (http://www.opensource.org/licenses/bsd-license.php).
*/
export class TruncatedConeGeometry extends Geometry {
constructor(props: TruncatedConeGeometryProps & {id?: string; attributes?: any} = {}) {
const {id = uid('truncated-code-geometry')} = props;
const {indices, attributes} = tesselateTruncatedCone(props);
super({
...props,
id,
topology: 'triangle-list',
indices,
attributes: {
POSITION: {size: 3, value: attributes.POSITION},
NORMAL: {size: 3, value: attributes.NORMAL},
TEXCOORD_0: {size: 2, value: attributes.TEXCOORD_0},
...props.attributes
}
});
}
}
/* eslint-disable max-statements, complexity */
function tesselateTruncatedCone(props: TruncatedConeGeometryProps = {}) {
const {
bottomRadius = 0,
topRadius = 0,
height = 1,
nradial = 10,
nvertical = 10,
verticalAxis = 'y',
topCap = false,
bottomCap = false
} = props;
const extra = (topCap ? 2 : 0) + (bottomCap ? 2 : 0);
const numVertices = (nradial + 1) * (nvertical + 1 + extra);
const slant = Math.atan2(bottomRadius - topRadius, height);
const msin = Math.sin;
const mcos = Math.cos;
const mpi = Math.PI;
const cosSlant = mcos(slant);
const sinSlant = msin(slant);
const start = topCap ? -2 : 0;
const end = nvertical + (bottomCap ? 2 : 0);
const vertsAroundEdge = nradial + 1;
const indices = new Uint16Array(nradial * (nvertical + extra) * 6);
const indexOffset = INDEX_OFFSETS[verticalAxis];
const positions = new Float32Array(numVertices * 3);
const normals = new Float32Array(numVertices * 3);
const texCoords = new Float32Array(numVertices * 2);
let i3 = 0;
let i2 = 0;
for (let i = start; i <= end; i++) {
let v = i / nvertical;
let y = height * v;
let ringRadius;
if (i < 0) {
y = 0;
v = 1;
ringRadius = bottomRadius;
} else if (i > nvertical) {
y = height;
v = 1;
ringRadius = topRadius;
} else {
ringRadius = bottomRadius + (topRadius - bottomRadius) * (i / nvertical);
}
if (i === -2 || i === nvertical + 2) {
ringRadius = 0;
v = 0;
}
y -= height / 2;
for (let j = 0; j < vertsAroundEdge; j++) {
const sin = msin((j * mpi * 2) / nradial);
const cos = mcos((j * mpi * 2) / nradial);
positions[i3 + indexOffset[0]] = sin * ringRadius;
positions[i3 + indexOffset[1]] = y;
positions[i3 + indexOffset[2]] = cos * ringRadius;
normals[i3 + indexOffset[0]] = i < 0 || i > nvertical ? 0 : sin * cosSlant;
normals[i3 + indexOffset[1]] = i < 0 ? -1 : i > nvertical ? 1 : sinSlant;
normals[i3 + indexOffset[2]] = i < 0 || i > nvertical ? 0 : cos * cosSlant;
texCoords[i2 + 0] = j / nradial;
texCoords[i2 + 1] = v;
i2 += 2;
i3 += 3;
}
}
for (let i = 0; i < nvertical + extra; i++) {
for (let j = 0; j < nradial; j++) {
const index = (i * nradial + j) * 6;
indices[index + 0] = vertsAroundEdge * (i + 0) + 0 + j;
indices[index + 1] = vertsAroundEdge * (i + 0) + 1 + j;
indices[index + 2] = vertsAroundEdge * (i + 1) + 1 + j;
indices[index + 3] = vertsAroundEdge * (i + 0) + 0 + j;
indices[index + 4] = vertsAroundEdge * (i + 1) + 1 + j;
indices[index + 5] = vertsAroundEdge * (i + 1) + 0 + j;
}
}
return {
indices,
attributes: {
POSITION: positions,
NORMAL: normals,
TEXCOORD_0: texCoords
}
};
}