ソースを参照

geometry building improements

- add and use PrimitiveBuilder.addQuad
- avoid namespace lookups for ribbon building
- allow triangluar prism
Alexander Rose 4 年 前
コミット
0892bb24d0

+ 3 - 2
src/mol-geo/geometry/mesh/builder/cylinder.ts

@@ -56,8 +56,9 @@ function getCylinder(props: CylinderProps) {
     let cylinder = cylinderMap.get(key);
     if (cylinder === undefined) {
         if (props.radialSegments && props.radialSegments <= 4) {
-            const box = Prism(polygon(4, true, props.radiusTop), props);
-            cylinder = transformPrimitive(box, Mat4.rotX90);
+            const sideCount = Math.max(3, props.radialSegments);
+            const prism = Prism(polygon(sideCount, true, props.radiusTop), props);
+            cylinder = transformPrimitive(prism, Mat4.rotX90);
         } else {
             cylinder = Cylinder(props);
         }

+ 55 - 43
src/mol-geo/geometry/mesh/builder/ribbon.ts

@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  * @author David Sehnal <david.sehnal@gmail.com>
@@ -9,16 +9,28 @@ import { Vec3 } from '../../../../mol-math/linear-algebra';
 import { ChunkedArray } from '../../../../mol-data/util';
 import { MeshBuilder } from '../mesh-builder';
 
-const tA = Vec3.zero();
-const tB = Vec3.zero();
-const tV = Vec3.zero();
-
-const horizontalVector = Vec3.zero();
-const verticalVector = Vec3.zero();
-const normalOffset = Vec3.zero();
-const positionVector = Vec3.zero();
-const normalVector = Vec3.zero();
-const torsionVector = Vec3.zero();
+// avoiding namespace lookup improved performance in Chrome (Aug 2020)
+const v3fromArray = Vec3.fromArray;
+const v3magnitude = Vec3.magnitude;
+const v3sub = Vec3.sub;
+const v3add = Vec3.add;
+const v3scale = Vec3.scale;
+const v3negate = Vec3.negate;
+const v3copy = Vec3.copy;
+const v3cross = Vec3.cross;
+const caAdd3 = ChunkedArray.add3;
+const caAdd = ChunkedArray.add;
+
+const tA = Vec3();
+const tB = Vec3();
+const tV = Vec3();
+
+const horizontalVector = Vec3();
+const verticalVector = Vec3();
+const normalOffset = Vec3();
+const positionVector = Vec3();
+const normalVector = Vec3();
+const torsionVector = Vec3();
 
 /** set arrowHeight = 0 for no arrow */
 export function addRibbon(state: MeshBuilder.State, controlPoints: ArrayLike<number>, normalVectors: ArrayLike<number>, binormalVectors: ArrayLike<number>, linearSegments: number, widthValues: ArrayLike<number>, heightValues: ArrayLike<number>, arrowHeight: number) {
@@ -28,9 +40,9 @@ export function addRibbon(state: MeshBuilder.State, controlPoints: ArrayLike<num
     let offsetLength = 0;
 
     if (arrowHeight > 0) {
-        Vec3.fromArray(tA, controlPoints, 0);
-        Vec3.fromArray(tB, controlPoints, linearSegments * 3);
-        offsetLength = arrowHeight / Vec3.magnitude(Vec3.sub(tV, tB, tA));
+        v3fromArray(tA, controlPoints, 0);
+        v3fromArray(tB, controlPoints, linearSegments * 3);
+        offsetLength = arrowHeight / v3magnitude(v3sub(tV, tB, tA));
     }
 
     for (let i = 0; i <= linearSegments; ++i) {
@@ -40,62 +52,62 @@ export function addRibbon(state: MeshBuilder.State, controlPoints: ArrayLike<num
         const actualHeight = arrowHeight === 0 ? height : arrowHeight * (1 - i / linearSegments);
         const i3 = i * 3;
 
-        Vec3.fromArray(verticalVector, normalVectors, i3);
-        Vec3.scale(verticalVector, verticalVector, actualHeight);
+        v3fromArray(verticalVector, normalVectors, i3);
+        v3scale(verticalVector, verticalVector, actualHeight);
 
-        Vec3.fromArray(horizontalVector, binormalVectors, i3);
-        Vec3.scale(horizontalVector, horizontalVector, width);
+        v3fromArray(horizontalVector, binormalVectors, i3);
+        v3scale(horizontalVector, horizontalVector, width);
 
         if (arrowHeight > 0) {
-            Vec3.fromArray(tA, normalVectors, i3);
-            Vec3.fromArray(tB, binormalVectors, i3);
-            Vec3.scale(normalOffset, Vec3.cross(normalOffset, tA, tB), offsetLength);
+            v3fromArray(tA, normalVectors, i3);
+            v3fromArray(tB, binormalVectors, i3);
+            v3scale(normalOffset, v3cross(normalOffset, tA, tB), offsetLength);
         }
 
-        Vec3.fromArray(positionVector, controlPoints, i3);
-        Vec3.fromArray(normalVector, normalVectors, i3);
-        Vec3.fromArray(torsionVector, binormalVectors, i3);
+        v3fromArray(positionVector, controlPoints, i3);
+        v3fromArray(normalVector, normalVectors, i3);
+        v3fromArray(torsionVector, binormalVectors, i3);
 
-        Vec3.add(tA, positionVector, verticalVector);
-        Vec3.negate(tB, torsionVector);
-        ChunkedArray.add3(vertices, tA[0], tA[1], tA[2]);
-        ChunkedArray.add3(normals, tB[0], tB[1], tB[2]);
+        v3add(tA, positionVector, verticalVector);
+        v3negate(tB, torsionVector);
+        caAdd3(vertices, tA[0], tA[1], tA[2]);
+        caAdd3(normals, tB[0], tB[1], tB[2]);
 
-        Vec3.sub(tA, positionVector, verticalVector);
-        ChunkedArray.add3(vertices, tA[0], tA[1], tA[2]);
-        ChunkedArray.add3(normals, tB[0], tB[1], tB[2]);
+        v3sub(tA, positionVector, verticalVector);
+        caAdd3(vertices, tA[0], tA[1], tA[2]);
+        caAdd3(normals, tB[0], tB[1], tB[2]);
 
-        Vec3.add(tA, positionVector, verticalVector);
-        Vec3.copy(tB, torsionVector);
-        ChunkedArray.add3(vertices, tA[0], tA[1], tA[2]);
-        ChunkedArray.add3(normals, tB[0], tB[1], tB[2]);
+        v3add(tA, positionVector, verticalVector);
+        v3copy(tB, torsionVector);
+        caAdd3(vertices, tA[0], tA[1], tA[2]);
+        caAdd3(normals, tB[0], tB[1], tB[2]);
 
-        Vec3.sub(tA, positionVector, verticalVector);
-        ChunkedArray.add3(vertices, tA[0], tA[1], tA[2]);
-        ChunkedArray.add3(normals, tB[0], tB[1], tB[2]);
+        v3sub(tA, positionVector, verticalVector);
+        caAdd3(vertices, tA[0], tA[1], tA[2]);
+        caAdd3(normals, tB[0], tB[1], tB[2]);
     }
 
     for (let i = 0; i < linearSegments; ++i) {
-        ChunkedArray.add3(
+        caAdd3(
             indices,
             vertexCount + i * 4,
             vertexCount + (i + 1) * 4 + 1,
             vertexCount + i * 4 + 1
         );
-        ChunkedArray.add3(
+        caAdd3(
             indices,
             vertexCount + i * 4,
             vertexCount + (i + 1) * 4,
             vertexCount + (i + 1) * 4 + 1
         );
 
-        ChunkedArray.add3(
+        caAdd3(
             indices,
             vertexCount + i * 4 + 2 + 1,
             vertexCount + (i + 1) * 4 + 2 + 1,
             vertexCount + i * 4 + 2
         );
-        ChunkedArray.add3(
+        caAdd3(
             indices,
             vertexCount + i * 4 + 2,
             vertexCount + (i + 1) * 4 + 2 + 1,
@@ -104,5 +116,5 @@ export function addRibbon(state: MeshBuilder.State, controlPoints: ArrayLike<num
     }
 
     const addedVertexCount = (linearSegments + 1) * 4;
-    for (let i = 0, il = addedVertexCount; i < il; ++i) ChunkedArray.add(groups, currentGroup);
+    for (let i = 0, il = addedVertexCount; i < il; ++i) caAdd(groups, currentGroup);
 }

+ 20 - 9
src/mol-geo/primitive/box.ts

@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
@@ -9,14 +9,16 @@ import { Primitive, PrimitiveBuilder } from './primitive';
 import { polygon } from './polygon';
 import { Cage, createCage } from './cage';
 
-const a = Vec3.zero(), b = Vec3.zero(), c = Vec3.zero(), d = Vec3.zero();
+const a = Vec3(), b = Vec3(), c = Vec3(), d = Vec3();
 const points = polygon(4, true);
 
 /**
  * Create a box
  */
 function createBox(perforated: boolean): Primitive {
-    const builder = PrimitiveBuilder(12);
+    const triangleCount = 12;
+    const vertexCount = perforated ? 12 * 3 : 6 * 4;
+    const builder = PrimitiveBuilder(triangleCount, vertexCount);
 
     // create sides
     for (let i = 0; i < 4; ++i) {
@@ -25,8 +27,11 @@ function createBox(perforated: boolean): Primitive {
         Vec3.set(b, points[ni * 3], points[ni * 3 + 1], -0.5);
         Vec3.set(c, points[ni * 3], points[ni * 3 + 1], 0.5);
         Vec3.set(d, points[i * 3], points[i * 3 + 1], 0.5);
-        builder.add(a, b, c);
-        if (!perforated) builder.add(c, d, a);
+        if (perforated) {
+            builder.add(a, b, c);
+        } else {
+            builder.addQuad(a, b, c, d);
+        }
     }
 
     // create bases
@@ -34,14 +39,20 @@ function createBox(perforated: boolean): Primitive {
     Vec3.set(b, points[3], points[4], -0.5);
     Vec3.set(c, points[6], points[7], -0.5);
     Vec3.set(d, points[9], points[10], -0.5);
-    builder.add(c, b, a);
-    if (!perforated) builder.add(a, d, c);
+    if (perforated) {
+        builder.add(c, b, a);
+    } else {
+        builder.addQuad(d, c, b, a);
+    }
     Vec3.set(a, points[0], points[1], 0.5);
     Vec3.set(b, points[3], points[4], 0.5);
     Vec3.set(c, points[6], points[7], 0.5);
     Vec3.set(d, points[9], points[10], 0.5);
-    builder.add(a, b, c);
-    if (!perforated) builder.add(c, d, a);
+    if (perforated) {
+        builder.add(a, b, c);
+    } else {
+        builder.addQuad(a, b, c, d);
+    }
 
     return builder.getPrimitive();
 }

+ 43 - 15
src/mol-geo/primitive/primitive.ts

@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
@@ -13,7 +13,7 @@ export interface Primitive {
     indices: ArrayLike<number>
 }
 
-const a = Vec3.zero(), b = Vec3.zero(), c = Vec3.zero();
+const a = Vec3(), b = Vec3(), c = Vec3();
 
 /** Create primitive with face normals from vertices and indices */
 export function createPrimitive(vertices: ArrayLike<number>, indices: ArrayLike<number>): Primitive {
@@ -39,36 +39,64 @@ export function copyPrimitive(primitive: Primitive): Primitive {
 
 export interface PrimitiveBuilder {
     add(a: Vec3, b: Vec3, c: Vec3): void
+    /** Shared vertices and normals, must be flat */
+    addQuad(a: Vec3, b: Vec3, c: Vec3, d: Vec3): void
     getPrimitive(): Primitive
 }
 
-const vn = Vec3.zero();
+const vn = Vec3();
 
 /** Builder to create primitive with face normals */
-export function PrimitiveBuilder(triangleCount: number): PrimitiveBuilder {
-    const vertices = new Float32Array(triangleCount * 3 * 3);
-    const normals = new Float32Array(triangleCount * 3 * 3);
+export function PrimitiveBuilder(triangleCount: number, vertexCount?: number): PrimitiveBuilder {
+    if (vertexCount === undefined) vertexCount = triangleCount * 3;
+
+    const vertices = new Float32Array(vertexCount * 3);
+    const normals = new Float32Array(vertexCount * 3);
     const indices = new Uint32Array(triangleCount * 3);
-    let offset = 0;
+
+    let vOffset = 0;
+    let iOffset = 0;
 
     return {
         add: (a: Vec3, b: Vec3, c: Vec3) => {
-            Vec3.toArray(a, vertices, offset);
-            Vec3.toArray(b, vertices, offset + 3);
-            Vec3.toArray(c, vertices, offset + 6);
+            Vec3.toArray(a, vertices, vOffset);
+            Vec3.toArray(b, vertices, vOffset + 3);
+            Vec3.toArray(c, vertices, vOffset + 6);
             Vec3.triangleNormal(vn, a, b, c);
             for (let j = 0; j < 3; ++j) {
-                Vec3.toArray(vn, normals, offset + 3 * j);
-                indices[offset / 3 + j] = offset / 3 + j;
+                Vec3.toArray(vn, normals, vOffset + 3 * j);
+                indices[iOffset + j] = vOffset / 3 + j;
+            }
+            vOffset += 9;
+            iOffset += 3;
+        },
+        addQuad: (a: Vec3, b: Vec3, c: Vec3, d: Vec3) => {
+            Vec3.toArray(a, vertices, vOffset);
+            Vec3.toArray(b, vertices, vOffset + 3);
+            Vec3.toArray(c, vertices, vOffset + 6);
+            Vec3.toArray(d, vertices, vOffset + 9);
+            Vec3.triangleNormal(vn, a, b, c);
+            for (let j = 0; j < 4; ++j) {
+                Vec3.toArray(vn, normals, vOffset + 3 * j);
             }
-            offset += 9;
+            const vOffset3 = vOffset / 3;
+            // a, b, c
+            indices[iOffset] = vOffset3;
+            indices[iOffset + 1] = vOffset3 + 1;
+            indices[iOffset + 2] = vOffset3 + 2;
+            // a, b, c
+            indices[iOffset + 3] = vOffset3 + 2;
+            indices[iOffset + 4] = vOffset3 + 3;
+            indices[iOffset + 5] = vOffset3;
+            vOffset += 12;
+            iOffset += 6;
         },
         getPrimitive: () => ({ vertices, normals, indices })
     };
 }
 
-const tmpV = Vec3.zero();
-const tmpMat3 = Mat3.zero();
+const tmpV = Vec3();
+const tmpMat3 = Mat3();
 
 /** Transform primitive in-place */
 export function transformPrimitive(primitive: Primitive, t: Mat4) {

+ 58 - 15
src/mol-geo/primitive/prism.ts

@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
@@ -20,16 +20,30 @@ export const DefaultPrismProps = {
 export type PrismProps = Partial<typeof DefaultPrismProps>
 
 /**
- * Create a prism with a base of 4 or more points
+ * Create a prism with a base of 3 or more points
  */
 export function Prism(points: ArrayLike<number>, props?: PrismProps): Primitive {
     const sideCount = points.length / 3;
-    if (sideCount < 4) throw new Error('need at least 4 points to build a prism');
+    if (sideCount < 3) throw new Error('need at least 3 points to build a prism');
 
     const { height, topCap, bottomCap } = { ...DefaultPrismProps, ...props };
 
-    const count = 4 * sideCount;
-    const builder = PrimitiveBuilder(count);
+    let triangleCount = sideCount * 2;
+    let vertexCount = sideCount * 4;
+
+    const capCount = (topCap ? 1 : 0) + (bottomCap ? 1 : 0);
+    if (sideCount === 3) {
+        triangleCount += capCount;
+        vertexCount += capCount * 3;
+    } else if (sideCount === 4) {
+        triangleCount += capCount * 2;
+        vertexCount += capCount * 4;
+    } else {
+        triangleCount += capCount * sideCount;
+        vertexCount += capCount * sideCount * 3;
+    }
+
+    const builder = PrimitiveBuilder(triangleCount, vertexCount);
     const halfHeight = height * 0.5;
 
     Vec3.set(on, 0, 0, -halfHeight);
@@ -42,22 +56,51 @@ export function Prism(points: ArrayLike<number>, props?: PrismProps): Primitive
         Vec3.set(b, points[ni * 3], points[ni * 3 + 1], -halfHeight);
         Vec3.set(c, points[ni * 3], points[ni * 3 + 1], halfHeight);
         Vec3.set(d, points[i * 3], points[i * 3 + 1], halfHeight);
-        builder.add(a, b, c);
-        builder.add(c, d, a);
+        builder.addQuad(a, b, c, d);
     }
 
     // create bases
-    for (let i = 0; i < sideCount; ++i) {
-        const ni = (i + 1) % sideCount;
+    if (sideCount === 3) {
         if (topCap) {
-            Vec3.set(a, points[i * 3], points[i * 3 + 1], -halfHeight);
-            Vec3.set(b, points[ni * 3], points[ni * 3 + 1], -halfHeight);
-            builder.add(on, b, a);
+            Vec3.set(a, points[0], points[1], -halfHeight);
+            Vec3.set(b, points[3], points[4], -halfHeight);
+            Vec3.set(c, points[6], points[7], -halfHeight);
+            builder.add(c, b, a);
         }
         if (bottomCap) {
-            Vec3.set(a, points[i * 3], points[i * 3 + 1], halfHeight);
-            Vec3.set(b, points[ni * 3], points[ni * 3 + 1], halfHeight);
-            builder.add(a, b, op);
+            Vec3.set(a, points[0], points[1], halfHeight);
+            Vec3.set(b, points[3], points[4], halfHeight);
+            Vec3.set(c, points[6], points[7], halfHeight);
+            builder.add(a, b, c);
+        }
+    } else if (sideCount === 4) {
+        if (topCap) {
+            Vec3.set(a, points[0], points[1], -halfHeight);
+            Vec3.set(b, points[3], points[4], -halfHeight);
+            Vec3.set(c, points[6], points[7], -halfHeight);
+            Vec3.set(d, points[9], points[10], -halfHeight);
+            builder.addQuad(d, c, b, a);
+        }
+        if (bottomCap) {
+            Vec3.set(a, points[0], points[1], halfHeight);
+            Vec3.set(b, points[3], points[4], halfHeight);
+            Vec3.set(c, points[6], points[7], halfHeight);
+            Vec3.set(d, points[9], points[10], halfHeight);
+            builder.addQuad(a, b, c, d);
+        }
+    } else {
+        for (let i = 0; i < sideCount; ++i) {
+            const ni = (i + 1) % sideCount;
+            if (topCap) {
+                Vec3.set(a, points[i * 3], points[i * 3 + 1], -halfHeight);
+                Vec3.set(b, points[ni * 3], points[ni * 3 + 1], -halfHeight);
+                builder.add(on, b, a);
+            }
+            if (bottomCap) {
+                Vec3.set(a, points[i * 3], points[i * 3 + 1], halfHeight);
+                Vec3.set(b, points[ni * 3], points[ni * 3 + 1], halfHeight);
+                builder.add(a, b, op);
+            }
         }
     }
 

+ 6 - 6
src/mol-geo/primitive/pyramid.ts

@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
@@ -10,7 +10,7 @@ import { polygon } from './polygon';
 import { Cage } from './cage';
 
 const on = Vec3.create(0, 0, -0.5), op = Vec3.create(0, 0, 0.5);
-const a = Vec3.zero(), b = Vec3.zero(), c = Vec3.zero(), d = Vec3.zero();
+const a = Vec3(), b = Vec3(), c = Vec3(), d = Vec3();
 
 /**
  * Create a pyramid with a polygonal base
@@ -18,8 +18,9 @@ const a = Vec3.zero(), b = Vec3.zero(), c = Vec3.zero(), d = Vec3.zero();
 export function Pyramid(points: ArrayLike<number>): Primitive {
     const sideCount = points.length / 3;
     const baseCount = sideCount === 3 ? 1 : sideCount === 4 ? 2 : sideCount;
-    const count = 2 * baseCount + 2 * sideCount;
-    const builder = PrimitiveBuilder(count);
+    const triangleCount = baseCount + sideCount;
+    const vertexCount = sideCount === 4 ? (sideCount * 3 + 4) : triangleCount * 3;
+    const builder = PrimitiveBuilder(triangleCount, vertexCount);
 
     // create sides
     for (let i = 0; i < sideCount; ++i) {
@@ -40,8 +41,7 @@ export function Pyramid(points: ArrayLike<number>): Primitive {
         Vec3.set(b, points[3], points[4], -0.5);
         Vec3.set(c, points[6], points[7], -0.5);
         Vec3.set(d, points[9], points[10], -0.5);
-        builder.add(c, b, a);
-        builder.add(a, d, c);
+        builder.addQuad(d, c, b, a);
     } else {
         for (let i = 0; i < sideCount; ++i) {
             const ni = (i + 1) % sideCount;