Skip to content
Snippets Groups Projects
Select Git revision
  • 1e7a6daeec920cbece57a28caa7387c542108a87
  • master default protected
  • e-infra2
  • ci-megalinter-speedup
  • egi-fixes
  • e-infra
  • envri-hub-new-aai
  • egi-b2drop-no-collapse
  • lfs
  • gpu_staging
  • resurrect-testing-ownloud
  • experiments/collab
  • update_claim_group_keys
  • envri-hub
  • enable_rtc
  • eosc-ui
  • future/jupyterhub-5.x
  • versioning
  • eosc-templating
  • staging1-raw-image
  • token-exchange
21 results

etc

Blame
  • 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,
                    }
                }
            }
        }
    }