Select Git revision
mesh-builder.ts
mesh-builder.ts 12.80 KiB
/**
* Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { ValueCell } from 'mol-util/value-cell'
import { Vec3, Mat4, Mat3 } from 'mol-math/linear-algebra';
import { ChunkedArray } from 'mol-data/util';
import { Plane, PlaneProps } from '../primitive/plane';
import { Cylinder, CylinderProps } from '../primitive/cylinder';
import { Sphere, SphereProps } from '../primitive/sphere';
import { Mesh } from './mesh';
import { getNormalMatrix } from '../util';
import { addSheet } from '../primitive/sheet';
import { addTube } from '../primitive/tube';
import { StarProps, Star } from '../primitive/star';
import { Octahedron } from '../primitive/octahedron';
import { Primitive } from '../primitive/primitive';
import { Wedge, Box, DiamondPrism, PentagonalPrism, HexagonalPrism } from '../primitive/prism';
import { OctagonalPyramide } from '../primitive/pyramid';
export interface MeshBuilderState {
vertices: ChunkedArray<number, 3>
normals: ChunkedArray<number, 3>
indices: ChunkedArray<number, 3>
}
export interface MeshBuilder {
add(t: Mat4, _vertices: ArrayLike<number>, _normals: ArrayLike<number>, _indices?: ArrayLike<number>): void
addBox(t: Mat4): void
addPlane(t: Mat4, props?: PlaneProps): void
addWedge(t: Mat4): void
addDiamondPrism(t: Mat4): void
addPentagonalPrism(t: Mat4): void
addHexagonalPrism(t: Mat4): void
addOctagonalPyramid(t: Mat4): void
addStar(t: Mat4, props?: StarProps): void
addOctahedron(t: Mat4): void
addCylinder(start: Vec3, end: Vec3, lengthScale: number, props: CylinderProps): void
addDoubleCylinder(start: Vec3, end: Vec3, lengthScale: number, shift: Vec3, props: CylinderProps): void
addFixedCountDashedCylinder(start: Vec3, end: Vec3, lengthScale: number, segmentCount: number, props: CylinderProps): void
addSphere(center: Vec3, radius: number, detail: number): void
addTube(centers: ArrayLike<number>, normals: ArrayLike<number>, binormals: ArrayLike<number>, linearSegments: number, radialSegments: number, width: number, height: number, waveFactor: number, startCap: boolean, endCap: boolean): void
addSheet(centers: ArrayLike<number>, normals: ArrayLike<number>, binormals: ArrayLike<number>, linearSegments: number, width: number, height: number, arrowHeight: number, startCap: boolean, endCap: boolean): void
setId(id: number): void
getMesh(): Mesh
}
const cylinderMap = new Map<string, Primitive>()
const sphereMap = new Map<string, Primitive>()
const up = Vec3.create(0, 1, 0)
const tmpV = Vec3.zero()
const tmpMat3 = Mat3.zero()
const tmpCylinderDir = Vec3.zero()
const tmpCylinderMatDir = Vec3.zero()
const tmpCylinderCenter = Vec3.zero()
const tmpCylinderMat = Mat4.zero()
// const tmpCylinderMatTrans = Mat4.zero()
const tmpCylinderStart = Vec3.zero()
const tmpUp = Vec3.zero()
function setCylinderMat(m: Mat4, start: Vec3, dir: Vec3, length: number) {
Vec3.setMagnitude(tmpCylinderMatDir, dir, length / 2)
Vec3.add(tmpCylinderCenter, start, tmpCylinderMatDir)
// ensure the direction used to create the rotation is always pointing in the same
// direction so the triangles of adjacent cylinder will line up
Vec3.copy(tmpUp, up)
if (Vec3.dot(tmpCylinderMatDir, tmpUp) < 0) Vec3.scale(tmpUp, tmpUp, -1)
Vec3.makeRotation(m, tmpUp, tmpCylinderMatDir)
return Mat4.setTranslation(m, tmpCylinderCenter)
}
function getCylinder(props: CylinderProps) {
const key = JSON.stringify(props)
let cylinder = cylinderMap.get(key)
if (cylinder === undefined) {
cylinder = Cylinder(props)
cylinderMap.set(key, cylinder)
}
return cylinder
}
const tmpSphereMat = Mat4.identity()
function setSphereMat(m: Mat4, center: Vec3) {
return Mat4.setTranslation(m, center)
}
function getSphere(props: SphereProps) {
const key = JSON.stringify(props)
let sphere = sphereMap.get(key)
if (sphere === undefined) {
sphere = Sphere(props)
sphereMap.set(key, sphere)
}
return sphere
}
// TODO cache primitives based on props
export namespace MeshBuilder {
export function create(initialCount = 2048, chunkSize = 1024, mesh?: Mesh): MeshBuilder {
const vertices = ChunkedArray.create(Float32Array, 3, chunkSize, mesh ? mesh.vertexBuffer.ref.value : initialCount);
const normals = ChunkedArray.create(Float32Array, 3, chunkSize, mesh ? mesh.normalBuffer.ref.value : initialCount);
const indices = ChunkedArray.create(Uint32Array, 3, chunkSize * 3, mesh ? mesh.indexBuffer.ref.value : initialCount * 3);
const state: MeshBuilderState = { vertices, normals, indices };
const ids = ChunkedArray.create(Float32Array, 1, chunkSize, mesh ? mesh.idBuffer.ref.value : initialCount);
const offsets = ChunkedArray.create(Uint32Array, 1, chunkSize, mesh ? mesh.offsetBuffer.ref.value : initialCount);
let currentId = -1
function add(t: Mat4, _vertices: ArrayLike<number>, _normals: ArrayLike<number>, _indices: ArrayLike<number>) {
const { elementCount } = vertices
const n = getNormalMatrix(tmpMat3, t)
for (let i = 0, il = _vertices.length; i < il; i += 3) {
// position
Vec3.fromArray(tmpV, _vertices, i)
Vec3.transformMat4(tmpV, tmpV, t)
ChunkedArray.add3(vertices, tmpV[0], tmpV[1], tmpV[2]);
// normal
Vec3.fromArray(tmpV, _normals, i)
Vec3.transformMat3(tmpV, tmpV, n)
ChunkedArray.add3(normals, tmpV[0], tmpV[1], tmpV[2]);
// id
ChunkedArray.add(ids, currentId);
}
for (let i = 0, il = _indices.length; i < il; i += 3) {
ChunkedArray.add3(indices, _indices[i] + elementCount, _indices[i + 1] + elementCount, _indices[i + 2] + elementCount);
}
}
return {
add,
addBox: (t: Mat4) => {
const { vertices, normals, indices } = Box()
add(t, vertices, normals, indices)
},
addPlane: (t: Mat4, props?: PlaneProps) => {
const { vertices, normals, indices } = Plane(props)
add(t, vertices, normals, indices)
},
addWedge: (t: Mat4) => {
const { vertices, normals, indices } = Wedge()
add(t, vertices, normals, indices)
},
addDiamondPrism: (t: Mat4) => {
const { vertices, normals, indices } = DiamondPrism()
add(t, vertices, normals, indices)
},
addPentagonalPrism: (t: Mat4) => {
const { vertices, normals, indices } = PentagonalPrism()
add(t, vertices, normals, indices)
},
addHexagonalPrism: (t: Mat4) => {
const { vertices, normals, indices } = HexagonalPrism()
add(t, vertices, normals, indices)
},
addOctagonalPyramid: (t: Mat4) => {
const { vertices, normals, indices } = OctagonalPyramide()
add(t, vertices, normals, indices)
},
addStar: (t: Mat4, props?: StarProps) => {
const { vertices, normals, indices } = Star(props)
add(t, vertices, normals, indices)
},
addOctahedron: (t: Mat4) => {
const { vertices, normals, indices } = Octahedron()
add(t, vertices, normals, indices)
},
addCylinder: (start: Vec3, end: Vec3, lengthScale: number, props: CylinderProps) => {
const d = Vec3.distance(start, end) * lengthScale
props.height = d
const { vertices, normals, indices } = getCylinder(props)
Vec3.sub(tmpCylinderDir, end, start)
setCylinderMat(tmpCylinderMat, start, tmpCylinderDir, d)
add(tmpCylinderMat, vertices, normals, indices)
},
addDoubleCylinder: (start: Vec3, end: Vec3, lengthScale: number, shift: Vec3, props: CylinderProps) => {
const d = Vec3.distance(start, end) * lengthScale
props.height = d
const { vertices, normals, indices } = getCylinder(props)
Vec3.sub(tmpCylinderDir, end, start)
// positivly shifted cylinder
Vec3.add(tmpCylinderStart, start, shift)
setCylinderMat(tmpCylinderMat, tmpCylinderStart, tmpCylinderDir, d)
add(tmpCylinderMat, vertices, normals, indices)
// negativly shifted cylinder
Vec3.sub(tmpCylinderStart, start, shift)
setCylinderMat(tmpCylinderMat, tmpCylinderStart, tmpCylinderDir, d)
add(tmpCylinderMat, vertices, normals, indices)
},
addFixedCountDashedCylinder: (start: Vec3, end: Vec3, lengthScale: number, segmentCount: number, props: CylinderProps) => {
const s = Math.floor(segmentCount / 2)
const step = 1 / segmentCount
// automatically adjust length so links/bonds that are rendered as two half cylinders
// have evenly spaced dashed cylinders
if (lengthScale < 1) {
const bias = lengthScale / 2 / segmentCount
lengthScale += segmentCount % 2 === 1 ? bias : -bias
}
const d = Vec3.distance(start, end) * lengthScale
props.height = d * step
const { vertices, normals, indices } = getCylinder(props)
Vec3.sub(tmpCylinderDir, end, start)
for (let j = 0; j < s; ++j) {
const f = step * (j * 2 + 1)
Vec3.setMagnitude(tmpCylinderDir, tmpCylinderDir, d * f)
Vec3.add(tmpCylinderStart, start, tmpCylinderDir)
setCylinderMat(tmpCylinderMat, tmpCylinderStart, tmpCylinderDir, d * step)
add(tmpCylinderMat, vertices, normals, indices)
}
},
addSphere: (center: Vec3, radius: number, detail: number) => {
const { vertices, normals, indices } = getSphere({ radius, detail })
setSphereMat(tmpSphereMat, center)
add(tmpSphereMat, vertices, normals, indices)
},
addTube: (centers: ArrayLike<number>, normals: ArrayLike<number>, binormals: ArrayLike<number>, linearSegments: number, radialSegments: number, width: number, height: number, waveFactor: number, startCap: boolean, endCap: boolean) => {
const addedVertexCount = addTube(centers, normals, binormals, linearSegments, radialSegments, width, height, waveFactor, startCap, endCap, state)
for (let i = 0, il = addedVertexCount; i < il; ++i) ChunkedArray.add(ids, currentId);
},
addSheet: (controls: ArrayLike<number>, normals: ArrayLike<number>, binormals: ArrayLike<number>, linearSegments: number, width: number, height: number, arrowHeight: number, startCap: boolean, endCap: boolean) => {
const addedVertexCount = addSheet(controls, normals, binormals, linearSegments, width, height, arrowHeight, startCap, endCap, state)
for (let i = 0, il = addedVertexCount; i < il; ++i) ChunkedArray.add(ids, currentId);
},
setId: (id: number) => {
if (currentId !== id) {
currentId = id
ChunkedArray.add(offsets, vertices.elementCount)
}
},
getMesh: () => {
ChunkedArray.add(offsets, vertices.elementCount)
const vb = ChunkedArray.compact(vertices, true) as Float32Array
const ib = ChunkedArray.compact(indices, true) as Uint32Array
const nb = ChunkedArray.compact(normals, true) as Float32Array
const idb = ChunkedArray.compact(ids, true) as Float32Array
const ob = ChunkedArray.compact(offsets, true) as Uint32Array
return {
vertexCount: vertices.elementCount,
triangleCount: indices.elementCount,
offsetCount: offsets.elementCount,
vertexBuffer: mesh ? ValueCell.update(mesh.vertexBuffer, vb) : ValueCell.create(vb),
indexBuffer: mesh ? ValueCell.update(mesh.indexBuffer, ib) : ValueCell.create(ib),
normalBuffer: mesh ? ValueCell.update(mesh.normalBuffer, nb) : ValueCell.create(nb),
idBuffer: mesh ? ValueCell.update(mesh.idBuffer, idb) : ValueCell.create(idb),
offsetBuffer: mesh ? ValueCell.update(mesh.offsetBuffer, ob) : ValueCell.create(ob),
normalsComputed: true,
offsetsComputed: true,
}
}
}
}
}