diff --git a/src/mol-geo/geometry/lines/lines-builder.ts b/src/mol-geo/geometry/lines/lines-builder.ts index db6ae4b45f0e5162cf755fff1f3aad23522c3830..70d994dbbed725b91fb0a01836df7b5759ce7ec1 100644 --- a/src/mol-geo/geometry/lines/lines-builder.ts +++ b/src/mol-geo/geometry/lines/lines-builder.ts @@ -7,12 +7,18 @@ import { ValueCell } from 'mol-util/value-cell' import { ChunkedArray } from 'mol-data/util'; import { Lines } from './lines'; +import { Mat4, Vec3 } from 'mol-math/linear-algebra'; +import { Cage } from 'mol-geo/primitive/cage'; export interface LinesBuilder { add(startX: number, startY: number, startZ: number, endX: number, endY: number, endZ: number, group: number): void + addCage(t: Mat4, cage: Cage, group: number): void getLines(): Lines } +const tmpVecA = Vec3.zero() +const tmpVecB = Vec3.zero() + export namespace LinesBuilder { export function create(initialCount = 2048, chunkSize = 1024, lines?: Lines): LinesBuilder { const mappings = ChunkedArray.create(Float32Array, 2, chunkSize, lines ? lines.mappingBuffer.ref.value : initialCount); @@ -21,20 +27,32 @@ export namespace LinesBuilder { const starts = ChunkedArray.create(Float32Array, 3, chunkSize, lines ? lines.startBuffer.ref.value : initialCount); const ends = ChunkedArray.create(Float32Array, 3, chunkSize, lines ? lines.endBuffer.ref.value : initialCount); + const add = (startX: number, startY: number, startZ: number, endX: number, endY: number, endZ: number, group: number) => { + const offset = mappings.elementCount + for (let i = 0; i < 4; ++i) { + ChunkedArray.add3(starts, startX, startY, startZ); + ChunkedArray.add3(ends, endX, endY, endZ); + ChunkedArray.add(groups, group); + } + ChunkedArray.add2(mappings, -1, 1); + ChunkedArray.add2(mappings, -1, -1); + ChunkedArray.add2(mappings, 1, 1); + ChunkedArray.add2(mappings, 1, -1); + ChunkedArray.add3(indices, offset, offset + 1, offset + 2); + ChunkedArray.add3(indices, offset + 1, offset + 3, offset + 2); + } + return { - add: (startX: number, startY: number, startZ: number, endX: number, endY: number, endZ: number, group: number) => { - const offset = mappings.elementCount - for (let i = 0; i < 4; ++i) { - ChunkedArray.add3(starts, startX, startY, startZ); - ChunkedArray.add3(ends, endX, endY, endZ); - ChunkedArray.add(groups, group); + add, + addCage: (t: Mat4, cage: Cage, group: number) => { + const { vertices, edges } = cage + for (let i = 0, il = edges.length; i < il; i += 2) { + Vec3.fromArray(tmpVecA, vertices, edges[i] * 3) + Vec3.fromArray(tmpVecB, vertices, edges[i + 1] * 3) + Vec3.transformMat4(tmpVecA, tmpVecA, t) + Vec3.transformMat4(tmpVecB, tmpVecB, t) + add(tmpVecA[0], tmpVecA[1], tmpVecA[2], tmpVecB[0], tmpVecB[1], tmpVecB[2], group) } - ChunkedArray.add2(mappings, -1, 1); - ChunkedArray.add2(mappings, -1, -1); - ChunkedArray.add2(mappings, 1, 1); - ChunkedArray.add2(mappings, 1, -1); - ChunkedArray.add3(indices, offset, offset + 1, offset + 2); - ChunkedArray.add3(indices, offset + 1, offset + 3, offset + 2); }, getLines: () => { const mb = ChunkedArray.compact(mappings, true) as Float32Array diff --git a/src/mol-geo/geometry/lines/lines.ts b/src/mol-geo/geometry/lines/lines.ts index ae67ba72846425707ac4b24f91740c0b111820b5..a2795a9c8646390fd2fc0455ba9a779429ac9870 100644 --- a/src/mol-geo/geometry/lines/lines.ts +++ b/src/mol-geo/geometry/lines/lines.ts @@ -174,8 +174,8 @@ export namespace Lines { } function getBoundingSphere(lineStart: Float32Array, lineEnd: Float32Array, lineCount: number, transform: Float32Array, transformCount: number) { - const start = calculateBoundingSphere(lineStart, lineCount, transform, transformCount) - const end = calculateBoundingSphere(lineEnd, lineCount, transform, transformCount) + const start = calculateBoundingSphere(lineStart, lineCount * 4, transform, transformCount) + const end = calculateBoundingSphere(lineEnd, lineCount * 4, transform, transformCount) return { boundingSphere: Sphere3D.addSphere(start.boundingSphere, end.boundingSphere), invariantBoundingSphere: Sphere3D.addSphere(start.invariantBoundingSphere, end.invariantBoundingSphere) diff --git a/src/mol-geo/geometry/mesh/mesh-builder.ts b/src/mol-geo/geometry/mesh/mesh-builder.ts index 742f41e719a5114e7b0862e61f3816086b79d89e..2bcaf085159f4597b6ee0c0d915b754c6aeac46c 100644 --- a/src/mol-geo/geometry/mesh/mesh-builder.ts +++ b/src/mol-geo/geometry/mesh/mesh-builder.ts @@ -1,5 +1,5 @@ /** - * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author Alexander Rose <alexander.rose@weirdbyte.de> */ @@ -10,9 +10,16 @@ import { ChunkedArray } from 'mol-data/util'; import { Mesh } from './mesh'; import { getNormalMatrix } from '../../util'; import { Primitive } from '../../primitive/primitive'; +import { Cage } from 'mol-geo/primitive/cage'; +import { addSphere } from './builder/sphere'; +import { addCylinder } from './builder/cylinder'; const tmpV = Vec3.zero() const tmpMat3 = Mat3.zero() +const tmpVecA = Vec3.zero() +const tmpVecB = Vec3.zero() +const tmpVecC = Vec3.zero() +const tmpVecD = Vec3.zero() export namespace MeshBuilder { export interface State { @@ -35,6 +42,45 @@ export namespace MeshBuilder { } } + export function addTriangle(state: State, a: Vec3, b: Vec3, c: Vec3) { + const { vertices, normals, indices, groups, currentGroup } = state + const offset = vertices.elementCount + + // positions + ChunkedArray.add3(vertices, a[0], a[1], a[2]); + ChunkedArray.add3(vertices, b[0], b[1], b[2]); + ChunkedArray.add3(vertices, c[0], c[1], c[2]); + + Vec3.triangleNormal(tmpV, a, b, c) + for (let i = 0; i < 3; ++i) { + ChunkedArray.add3(normals, tmpV[0], tmpV[1], tmpV[2]); // normal + ChunkedArray.add(groups, currentGroup); // group + } + ChunkedArray.add3(indices, offset, offset + 1, offset + 2); + } + + export function addTriangleStrip(state: State, vertices: ArrayLike<number>, indices: ArrayLike<number>) { + Vec3.fromArray(tmpVecC, vertices, indices[0] * 3) + Vec3.fromArray(tmpVecD, vertices, indices[1] * 3) + for (let i = 2, il = indices.length; i < il; i += 2) { + Vec3.copy(tmpVecA, tmpVecC) + Vec3.copy(tmpVecB, tmpVecD) + Vec3.fromArray(tmpVecC, vertices, indices[i] * 3) + Vec3.fromArray(tmpVecD, vertices, indices[i + 1] * 3) + addTriangle(state, tmpVecA, tmpVecB, tmpVecC) + addTriangle(state, tmpVecB, tmpVecD, tmpVecC) + } + } + + export function addTriangleFan(state: State, vertices: ArrayLike<number>, indices: ArrayLike<number>) { + Vec3.fromArray(tmpVecA, vertices, indices[0] * 3) + for (let i = 2, il = indices.length; i < il; ++i) { + Vec3.fromArray(tmpVecB, vertices, indices[i - 1] * 3) + Vec3.fromArray(tmpVecC, vertices, indices[i] * 3) + addTriangle(state, tmpVecA, tmpVecC, tmpVecB) + } + } + export function addPrimitive(state: State, t: Mat4, primitive: Primitive) { const { vertices: va, normals: na, indices: ia } = primitive const { vertices, normals, indices, groups, currentGroup } = state @@ -55,6 +101,20 @@ export namespace MeshBuilder { } } + export function addCage(state: State, t: Mat4, cage: Cage, radius: number, detail: number) { + const { vertices: va, edges: ea } = cage + const cylinderProps = { radiusTop: radius, radiusBottom: radius } + for (let i = 0, il = ea.length; i < il; i += 2) { + Vec3.fromArray(tmpVecA, va, ea[i] * 3) + Vec3.fromArray(tmpVecB, va, ea[i + 1] * 3) + Vec3.transformMat4(tmpVecA, tmpVecA, t) + Vec3.transformMat4(tmpVecB, tmpVecB, t) + addSphere(state, tmpVecA, radius, detail) + addSphere(state, tmpVecB, radius, detail) + addCylinder(state, tmpVecA, tmpVecB, 1, cylinderProps) + } + } + export function getMesh (state: State): Mesh { const { vertices, normals, indices, groups, mesh } = state const vb = ChunkedArray.compact(vertices, true) as Float32Array diff --git a/src/mol-geo/primitive/box.ts b/src/mol-geo/primitive/box.ts index d3fa7f74dd3bf158dd0756d2101aca4921e66931..5ce466fe7188a9543f5f033e875b0b5b3679e409 100644 --- a/src/mol-geo/primitive/box.ts +++ b/src/mol-geo/primitive/box.ts @@ -1,5 +1,5 @@ /** - * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author Alexander Rose <alexander.rose@weirdbyte.de> */ @@ -7,6 +7,7 @@ import { Vec3 } from 'mol-math/linear-algebra' 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 points = polygon(4, true) @@ -20,25 +21,25 @@ function createBox(perforated: boolean): Primitive { // create sides for (let i = 0; i < 4; ++i) { const ni = (i + 1) % 4 - Vec3.set(a, points[i * 2], points[i * 2 + 1], -0.5) - Vec3.set(b, points[ni * 2], points[ni * 2 + 1], -0.5) - Vec3.set(c, points[ni * 2], points[ni * 2 + 1], 0.5) - Vec3.set(d, points[i * 2], points[i * 2 + 1], 0.5) + Vec3.set(a, points[i * 3], points[i * 3 + 1], -0.5) + 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) } // create bases Vec3.set(a, points[0], points[1], -0.5) - Vec3.set(b, points[2], points[3], -0.5) - Vec3.set(c, points[4], points[5], -0.5) - Vec3.set(d, points[6], points[7], -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(c, b, a) if (!perforated) builder.add(a, d, c) Vec3.set(a, points[0], points[1], 0.5) - Vec3.set(b, points[2], points[3], 0.5) - Vec3.set(c, points[4], points[5], 0.5) - Vec3.set(d, points[6], points[7], 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) @@ -55,4 +56,28 @@ let perforatedBox: Primitive export function PerforatedBox() { if (!perforatedBox) perforatedBox = createBox(true) return perforatedBox +} + +let boxCage: Cage +export function BoxCage() { + if (!boxCage) { + boxCage = createCage( + [ + 0.5, 0.5, -0.5, // bottom + -0.5, 0.5, -0.5, + -0.5, -0.5, -0.5, + 0.5, -0.5, -0.5, + 0.5, 0.5, 0.5, // top + -0.5, 0.5, 0.5, + -0.5, -0.5, 0.5, + 0.5, -0.5, 0.5 + ], + [ + 0, 4, 1, 5, 2, 6, 3, 7, // sides + 0, 1, 1, 2, 2, 3, 3, 0, // bottom base + 4, 5, 5, 6, 6, 7, 7, 4 // top base + ] + ) + } + return boxCage } \ No newline at end of file diff --git a/src/mol-geo/primitive/cage.ts b/src/mol-geo/primitive/cage.ts new file mode 100644 index 0000000000000000000000000000000000000000..6c235c61f28ff2fecfd317bad4568f99c3233800 --- /dev/null +++ b/src/mol-geo/primitive/cage.ts @@ -0,0 +1,14 @@ +/** + * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +export interface Cage { + readonly vertices: ArrayLike<number> + readonly edges: ArrayLike<number> +} + +export function createCage(vertices: ArrayLike<number>, edges: ArrayLike<number>): Cage { + return { vertices, edges } +} \ No newline at end of file diff --git a/src/mol-geo/primitive/dodecahedron.ts b/src/mol-geo/primitive/dodecahedron.ts new file mode 100644 index 0000000000000000000000000000000000000000..d54ada78f4b1a5008525b71f91378dcd14304fb1 --- /dev/null +++ b/src/mol-geo/primitive/dodecahedron.ts @@ -0,0 +1,69 @@ +/** + * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +import { createPrimitive, Primitive } from './primitive'; +import { Cage, createCage } from './cage'; + +const t = (1 + Math.sqrt(5)) / 2; + +const a = 1; +const b = 1 / t; +const c = 2 - t; + +export const dodecahedronVertices: ReadonlyArray<number> = [ + c, 0, a, -c, 0, a, -b, b, b, 0, a, c, b, b, b, + b, -b, b, 0, -a, c, -b, -b, b, c, 0, -a, -c, 0, -a, + -b, -b, -b, 0, -a, -c, b, -b, -b, b, b, -b, 0, a, -c, + -b, b, -b, a, c, 0, -a, c, 0, -a, -c, 0, a, -c, 0 +]; + +/** indices of pentagonal faces, groups of five */ +export const dodecahedronFaces: ReadonlyArray<number> = [ + 4, 3, 2, 1, 0, + 7, 6, 5, 0, 1, + 12, 11, 10, 9, 8, + 15, 14, 13, 8, 9, + 14, 3, 4, 16, 13, + 3, 14, 15, 17, 2, + 11, 6, 7, 18, 10, + 6, 11, 12, 19, 5, + 4, 0, 5, 19, 16, + 12, 8, 13, 16, 19, + 15, 9, 10, 18, 17, + 7, 1, 2, 17, 18 +]; + +const dodecahedronIndices: ReadonlyArray<number> = [ // pentagonal faces + 4, 3, 2, 2, 1, 0, 4, 2, 0, // 4, 3, 2, 1, 0 + 7, 6, 5, 5, 0, 1, 7, 5, 1, // 7, 6, 5, 0, 1 + 12, 11, 10, 10, 9, 8, 12, 10, 8, // 12, 11, 10, 9, 8 + 15, 14, 13, 13, 8, 9, 15, 13, 9, // 15, 14, 13, 8, 9 + 14, 3, 4, 4, 16, 13, 14, 4, 13, // 14, 3, 4, 16, 13 + 3, 14, 15, 15, 17, 2, 3, 15, 2, // 3, 14, 15, 17, 2 + 11, 6, 7, 7, 18, 10, 11, 7, 10, // 11, 6, 7, 18, 10 + 6, 11, 12, 12, 19, 5, 6, 12, 5, // 6, 11, 12, 19, 5 + 4, 0, 5, 5, 19, 16, 4, 5, 16, // 4, 0, 5, 19, 16 + 12, 8, 13, 13, 16, 19, 12, 13, 19, // 12, 8, 13, 16, 19 + 15, 9, 10, 10, 18, 17, 15, 10, 17, // 15, 9, 10, 18, 17 + 7, 1, 2, 2, 17, 18, 7, 2, 18, // 7, 1, 2, 17, 18 +]; + +const dodecahedronEdges: ReadonlyArray<number> = [ + 0, 1, 0, 4, 0, 5, 1, 2, 1, 7, 2, 3, 2, 17, 3, 4, 3, 14, 4, 16, + 5, 6, 5, 19, 6, 7, 6, 11, 7, 18, 8, 9, 8, 12, 8, 13, 9, 10, 9, 15, + 10, 11, 10, 18, 11, 12, 12, 19, 13, 14, 13, 16, 14, 15, 15, 17, 16, 19, 17, 18, +] + +let dodecahedron: Primitive +export function Dodecahedron(): Primitive { + if (!dodecahedron) dodecahedron = createPrimitive(dodecahedronVertices, dodecahedronIndices) + return dodecahedron +} + +const dodecahedronCage = createCage(dodecahedronVertices, dodecahedronEdges) +export function DodecahedronCage(): Cage { + return dodecahedronCage +} \ No newline at end of file diff --git a/src/mol-geo/primitive/icosahedron.ts b/src/mol-geo/primitive/icosahedron.ts index 096945994adb3b20a1bc5680814117cbecc7a694..4be044a7dd0f8f3b80f8168ab02ea3437844b77f 100644 --- a/src/mol-geo/primitive/icosahedron.ts +++ b/src/mol-geo/primitive/icosahedron.ts @@ -5,8 +5,9 @@ */ import { createPrimitive, Primitive } from './primitive'; +import { Cage, createCage } from './cage'; -const t = ( 1 + Math.sqrt( 5 ) ) / 2; +const t = (1 + Math.sqrt(5)) / 2; const icosahedronVertices: ReadonlyArray<number> = [ -1, t, 0, 1, t, 0, -1, -t, 0, 1, -t, 0, @@ -21,6 +22,19 @@ const icosahedronIndices: ReadonlyArray<number> = [ 4, 9, 5, 2, 4, 11, 6, 2, 10, 8, 6, 7, 9, 8, 1 ]; -const icosahedron = createPrimitive(icosahedronVertices, icosahedronIndices) +const icosahedronEdges: ReadonlyArray<number> = [ + 0, 11, 5, 11, 0, 5, 1, 5, 0, 1, 1, 7, 0, 7, 7, 10, 0, 10, 10, 11, + 5, 9, 4, 11, 2, 10, 6, 7, 1, 8, 3, 9, 4, 9, 3, 4, 2, 4, 2, 3, + 2, 6, 3, 6, 6, 8, 3, 8, 8, 9, 4, 5, 2, 11, 6, 10, 7, 8, 1, 9 +] -export function Icosahedron(): Primitive { return icosahedron } \ No newline at end of file +let icosahedron: Primitive +export function Icosahedron(): Primitive { + if (!icosahedron) icosahedron = createPrimitive(icosahedronVertices, icosahedronIndices) + return icosahedron +} + +const icosahedronCage = createCage(icosahedronVertices, icosahedronEdges) +export function IcosahedronCage(): Cage { + return icosahedronCage +} \ No newline at end of file diff --git a/src/mol-geo/primitive/octahedron.ts b/src/mol-geo/primitive/octahedron.ts index cb23691c9cb84b24b83677026009649e1a462453..989526767fff67b6824c9ebb412f32742434904e 100644 --- a/src/mol-geo/primitive/octahedron.ts +++ b/src/mol-geo/primitive/octahedron.ts @@ -1,20 +1,23 @@ /** - * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author Alexander Rose <alexander.rose@weirdbyte.de> */ import { createPrimitive, Primitive } from './primitive'; +import { createCage, Cage } from './cage'; export const octahedronVertices: ReadonlyArray<number> = [ 0.5, 0, 0, -0.5, 0, 0, 0, 0.5, 0, - 0, -0.5, 0, 0, 0, 0.5, 0, 0, -0.5 + 0, -0.5, 0, 0, 0, 0.5, 0, 0, -0.5 ]; + export const octahedronIndices: ReadonlyArray<number> = [ 0, 2, 4, 0, 4, 3, 0, 3, 5, 0, 5, 2, 1, 2, 5, 1, 5, 3, 1, 3, 4, 1, 4, 2 ]; + export const perforatedOctahedronIndices: ReadonlyArray<number> = [ 0, 2, 4, 0, 4, 3, // 0, 3, 5, 0, 5, 2, @@ -22,8 +25,25 @@ export const perforatedOctahedronIndices: ReadonlyArray<number> = [ // 1, 3, 4, 1, 4, 2 ]; -const octahedron = createPrimitive(octahedronVertices, octahedronIndices) -const perforatedOctahedron = createPrimitive(octahedronVertices, perforatedOctahedronIndices) +const octahedronEdges: ReadonlyArray<number> = [ + 0, 2, 1, 3, 2, 1, 3, 0, + 0, 4, 1, 4, 2, 4, 3, 4, + 0, 5, 1, 5, 2, 5, 3, 5, +] + +let octahedron: Primitive +export function Octahedron(): Primitive { + if (!octahedron) octahedron = createPrimitive(octahedronVertices, octahedronIndices) + return octahedron +} + +let perforatedOctahedron: Primitive +export function PerforatedOctahedron(): Primitive { + if (!perforatedOctahedron) perforatedOctahedron = createPrimitive(octahedronVertices, perforatedOctahedronIndices) + return perforatedOctahedron +} -export function Octahedron(): Primitive { return octahedron } -export function PerforatedOctahedron(): Primitive { return perforatedOctahedron } \ No newline at end of file +const octahedronCage = createCage(octahedronVertices, octahedronEdges) +export function OctahedronCage(): Cage { + return octahedronCage +} \ No newline at end of file diff --git a/src/mol-geo/primitive/plane.ts b/src/mol-geo/primitive/plane.ts index 2f30c427d4fd4ccf7794b135ae2169aa3793e917..f0886e6270d1a6b8b29a95a87c66bb5f1db50aa4 100644 --- a/src/mol-geo/primitive/plane.ts +++ b/src/mol-geo/primitive/plane.ts @@ -1,4 +1,5 @@ import { Primitive } from './primitive'; +import { Cage } from './cage'; /** * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. @@ -25,6 +26,15 @@ const plane: Primitive = { ]) } +const planeCage: Cage = { + vertices: plane.vertices, + edges: new Uint32Array([ 0, 1, 2, 3, 3, 1, 2, 0 ]) +} + export function Plane(): Primitive { return plane +} + +export function PlaneCage(): Cage { + return planeCage } \ No newline at end of file diff --git a/src/mol-geo/primitive/polygon.ts b/src/mol-geo/primitive/polygon.ts index b7db9972eb6511dc1cab248e47c658594985bb8d..d26aaed98f05b0abc48cdb0817962b522422f77b 100644 --- a/src/mol-geo/primitive/polygon.ts +++ b/src/mol-geo/primitive/polygon.ts @@ -1,23 +1,24 @@ /** - * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author Alexander Rose <alexander.rose@weirdbyte.de> */ /** - * Create points for a polygon: + * Create 3d points for a polygon: * 3 for a triangle, 4 for a rectangle, 5 for a pentagon, 6 for a hexagon... */ export function polygon(sideCount: number, shift: boolean) { - const points = new Float32Array(sideCount * 2) + const points = new Float32Array(sideCount * 3) const radius = sideCount <= 4 ? Math.sqrt(2) / 2 : 0.6 const offset = shift ? 1 : 0 - for (let i = 0, il = 2 * sideCount; i < il; i += 2) { - const c = (i + offset) / sideCount * Math.PI - points[i] = Math.cos(c) * radius - points[i + 1] = Math.sin(c) * radius + for (let i = 0, il = sideCount; i < il; ++i) { + const c = (i * 2 + offset) / sideCount * Math.PI + points[i * 3] = Math.cos(c) * radius + points[i * 3 + 1] = Math.sin(c) * radius + points[i * 3 + 2] = 0 } return points } \ No newline at end of file diff --git a/src/mol-geo/primitive/prism.ts b/src/mol-geo/primitive/prism.ts index 02bbee0b26176c27c36d6502cd83523fba8b3ace..d848f2d6168121b0d77f0389b43602372f5a4eb4 100644 --- a/src/mol-geo/primitive/prism.ts +++ b/src/mol-geo/primitive/prism.ts @@ -1,5 +1,5 @@ /** - * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author Alexander Rose <alexander.rose@weirdbyte.de> */ @@ -7,16 +7,17 @@ import { Vec3 } from 'mol-math/linear-algebra' import { Primitive, PrimitiveBuilder } from './primitive'; 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() /** - * Create a prism with a poligonal base of 5 or more points + * Create a prism with a base of 4 or more points */ export function Prism(points: ArrayLike<number>): Primitive { - const sideCount = points.length / 2 - if (sideCount < 4) throw new Error('need at least 5 points to build a prism') + const sideCount = points.length / 3 + if (sideCount < 4) throw new Error('need at least 4 points to build a prism') const count = 4 * sideCount const builder = PrimitiveBuilder(count) @@ -24,10 +25,10 @@ export function Prism(points: ArrayLike<number>): Primitive { // create sides for (let i = 0; i < sideCount; ++i) { const ni = (i + 1) % sideCount - Vec3.set(a, points[i * 2], points[i * 2 + 1], -0.5) - Vec3.set(b, points[ni * 2], points[ni * 2 + 1], -0.5) - Vec3.set(c, points[ni * 2], points[ni * 2 + 1], 0.5) - Vec3.set(d, points[i * 2], points[i * 2 + 1], 0.5) + Vec3.set(a, points[i * 3], points[i * 3 + 1], -0.5) + 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) builder.add(c, d, a) } @@ -35,11 +36,11 @@ export function Prism(points: ArrayLike<number>): Primitive { // create bases for (let i = 0; i < sideCount; ++i) { const ni = (i + 1) % sideCount - Vec3.set(a, points[i * 2], points[i * 2 + 1], -0.5) - Vec3.set(b, points[ni * 2], points[ni * 2 + 1], -0.5) + Vec3.set(a, points[i * 3], points[i * 3 + 1], -0.5) + Vec3.set(b, points[ni * 3], points[ni * 3 + 1], -0.5) builder.add(on, b, a) - Vec3.set(a, points[i * 2], points[i * 2 + 1], 0.5) - Vec3.set(b, points[ni * 2], points[ni * 2 + 1], 0.5) + Vec3.set(a, points[i * 3], points[i * 3 + 1], 0.5) + Vec3.set(b, points[ni * 3], points[ni * 3 + 1], 0.5) builder.add(a, b, op) } @@ -62,4 +63,58 @@ let hexagonalPrism: Primitive export function HexagonalPrism() { if (!hexagonalPrism) hexagonalPrism = Prism(polygon(6, true)) return hexagonalPrism +} + +// + +/** + * Create a prism cage + */ +export function PrismCage(points: ArrayLike<number>): Cage { + const sideCount = points.length / 3 + + // const count = 4 * sideCount + const vertices: number[] = [] + const edges: number[] = [] + + let offset = 0 + + // vertices and side edges + for (let i = 0; i < sideCount; ++i) { + vertices.push( + points[i * 3], points[i * 3 + 1], -0.5, + points[i * 3], points[i * 3 + 1], 0.5 + ) + edges.push(offset, offset + 1) + offset += 2 + } + + // bases edges + for (let i = 0; i < sideCount; ++i) { + const ni = (i + 1) % sideCount + edges.push( + i * 2, ni * 2, + i * 2 + 1, ni * 2 + 1 + ) + } + + return { vertices, edges } +} + +let diamondCage: Cage +export function DiamondPrismCage() { + if (!diamondCage) diamondCage = PrismCage(polygon(4, false)) + return diamondCage +} + +let pentagonalPrismCage: Cage +export function PentagonalPrismCage() { + if (!pentagonalPrismCage) pentagonalPrismCage = PrismCage(polygon(5, false)) + return pentagonalPrismCage +} + +let hexagonalPrismCage: Cage +export function HexagonalPrismCage() { + if (!hexagonalPrismCage) hexagonalPrismCage = PrismCage(polygon(6, true)) + return hexagonalPrismCage } \ No newline at end of file diff --git a/src/mol-geo/primitive/pyramid.ts b/src/mol-geo/primitive/pyramid.ts index c00b84320cd714f967c8a89519fef216f5b95487..242dcbae5a131f5cf61acd06a3b0bf2e18de81ed 100644 --- a/src/mol-geo/primitive/pyramid.ts +++ b/src/mol-geo/primitive/pyramid.ts @@ -1,5 +1,5 @@ /** - * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author Alexander Rose <alexander.rose@weirdbyte.de> */ @@ -7,15 +7,16 @@ import { Vec3 } from 'mol-math/linear-algebra' import { Primitive, PrimitiveBuilder, createPrimitive } from './primitive'; 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() /** - * Create a pyramid with a poligonal base + * Create a pyramid with a polygonal base */ export function Pyramid(points: ArrayLike<number>): Primitive { - const sideCount = points.length / 2 + const sideCount = points.length / 3 const baseCount = sideCount === 3 ? 1 : sideCount === 4 ? 2 : sideCount const count = 2 * baseCount + 2 * sideCount const builder = PrimitiveBuilder(count) @@ -23,29 +24,29 @@ export function Pyramid(points: ArrayLike<number>): Primitive { // create sides for (let i = 0; i < sideCount; ++i) { const ni = (i + 1) % sideCount - Vec3.set(a, points[i * 2], points[i * 2 + 1], -0.5) - Vec3.set(b, points[ni * 2], points[ni * 2 + 1], -0.5) + Vec3.set(a, points[i * 3], points[i * 3 + 1], -0.5) + Vec3.set(b, points[ni * 3], points[ni * 3 + 1], -0.5) builder.add(a, b, op) } // create base if (sideCount === 3) { Vec3.set(a, points[0], points[1], -0.5) - Vec3.set(b, points[2], points[3], -0.5) - Vec3.set(c, points[4], points[5], -0.5) + Vec3.set(b, points[3], points[4], -0.5) + Vec3.set(c, points[6], points[7], -0.5) builder.add(c, b, a) } else if (sideCount === 4) { Vec3.set(a, points[0], points[1], -0.5) - Vec3.set(b, points[2], points[3], -0.5) - Vec3.set(c, points[4], points[5], -0.5) - Vec3.set(d, points[6], points[7], -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(c, b, a) builder.add(a, d, c) } else { for (let i = 0; i < sideCount; ++i) { const ni = (i + 1) % sideCount - Vec3.set(a, points[i * 2], points[i * 2 + 1], -0.5) - Vec3.set(b, points[ni * 2], points[ni * 2 + 1], -0.5) + Vec3.set(a, points[i * 3], points[i * 3 + 1], -0.5) + Vec3.set(b, points[ni * 3], points[ni * 3 + 1], -0.5) builder.add(on, b, a) } } @@ -59,16 +60,14 @@ export function OctagonalPyramid() { return octagonalPyramid } -// - let perforatedOctagonalPyramid: Primitive export function PerforatedOctagonalPyramid() { if (!perforatedOctagonalPyramid) { const points = polygon(8, true) const vertices = new Float32Array(8 * 3 + 6) for (let i = 0; i < 8; ++i) { - vertices[i * 3] = points[i * 2] - vertices[i * 3 + 1] = points[i * 2 + 1] + vertices[i * 3] = points[i * 3] + vertices[i * 3 + 1] = points[i * 3 + 1] vertices[i * 3 + 2] = -0.5 } vertices[8 * 3] = 0 @@ -84,4 +83,41 @@ export function PerforatedOctagonalPyramid() { perforatedOctagonalPyramid = createPrimitive(vertices, indices) } return perforatedOctagonalPyramid +} + +// + +/** + * Create a prism cage + */ +export function PyramidCage(points: ArrayLike<number>): Cage { + const sideCount = points.length / 3 + + // const count = 4 * sideCount + const vertices: number[] = [] + const edges: number[] = [] + + let offset = 1 + vertices.push(op[0], op[1], op[2]) + + // vertices and side edges + for (let i = 0; i < sideCount; ++i) { + vertices.push(points[i * 3], points[i * 3 + 1], -0.5) + edges.push(0, offset) + offset += 1 + } + + // bases edges + for (let i = 0; i < sideCount; ++i) { + const ni = (i + 1) % sideCount + edges.push(i + 1, ni + 1) + } + + return { vertices, edges } +} + +let octagonalPyramidCage: Cage +export function OctagonalPyramidCage() { + if (!octagonalPyramidCage) octagonalPyramidCage = PyramidCage(polygon(8, true)) + return octagonalPyramidCage } \ No newline at end of file diff --git a/src/mol-geo/primitive/spiked-ball.ts b/src/mol-geo/primitive/spiked-ball.ts new file mode 100644 index 0000000000000000000000000000000000000000..dd2534f7327fe5ab576c16f4712a43c0edf71a93 --- /dev/null +++ b/src/mol-geo/primitive/spiked-ball.ts @@ -0,0 +1,62 @@ +/** + * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +import { createPrimitive, Primitive } from './primitive'; +import { dodecahedronVertices, dodecahedronFaces } from './dodecahedron'; +import { Vec3 } from 'mol-math/linear-algebra'; + +function calcCenter(out: Vec3, ...vec3s: Vec3[]) { + Vec3.set(out, 0, 0, 0) + for (let i = 0, il = vec3s.length; i < il; ++i) { + Vec3.add(out, out, vec3s[i]) + } + Vec3.scale(out, out, 1 / vec3s.length) + return out +} + +const center = Vec3.zero() +const dir = Vec3.zero() +const tip = Vec3.zero() + +const vecA = Vec3.zero() +const vecB = Vec3.zero() +const vecC = Vec3.zero() +const vecD = Vec3.zero() +const vecE = Vec3.zero() + +/** + * Create a spiked ball derived from a dodecahedron + * @param radiusRatio ratio between inner radius (dodecahedron) and outher radius (spikes) + */ +export function SpikedBall(radiusRatio = 1): Primitive { + const vertices = dodecahedronVertices.slice(0) + const indices: number[] = [] + + let offset = vertices.length / 3 + + for (let i = 0, il = dodecahedronFaces.length; i < il; i += 5) { + Vec3.fromArray(vecA, dodecahedronVertices, dodecahedronFaces[i] * 3) + Vec3.fromArray(vecB, dodecahedronVertices, dodecahedronFaces[i + 1] * 3) + Vec3.fromArray(vecC, dodecahedronVertices, dodecahedronFaces[i + 2] * 3) + Vec3.fromArray(vecD, dodecahedronVertices, dodecahedronFaces[i + 3] * 3) + Vec3.fromArray(vecE, dodecahedronVertices, dodecahedronFaces[i + 4] * 3) + + calcCenter(center, vecA, vecB, vecC, vecD, vecE) + Vec3.triangleNormal(dir, vecA, vecB, vecC) + Vec3.scaleAndAdd(tip, center, dir, radiusRatio) + + Vec3.toArray(tip, vertices, offset * 3) + indices.push(offset, dodecahedronFaces[i], dodecahedronFaces[i + 1]) + indices.push(offset, dodecahedronFaces[i + 1], dodecahedronFaces[i + 2]) + indices.push(offset, dodecahedronFaces[i + 2], dodecahedronFaces[i + 3]) + indices.push(offset, dodecahedronFaces[i + 3], dodecahedronFaces[i + 4]) + indices.push(offset, dodecahedronFaces[i + 4], dodecahedronFaces[i]) + + offset += 1 + } + + return createPrimitive(vertices, indices) +} \ No newline at end of file diff --git a/src/mol-geo/primitive/tetrahedron.ts b/src/mol-geo/primitive/tetrahedron.ts new file mode 100644 index 0000000000000000000000000000000000000000..fd89bf8e118636b3b7efa934461e57eca1549dd9 --- /dev/null +++ b/src/mol-geo/primitive/tetrahedron.ts @@ -0,0 +1,36 @@ +/** + * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +import { createPrimitive, Primitive } from './primitive'; +import { createCage, Cage } from './cage'; + +export const tetrahedronVertices: ReadonlyArray<number> = [ + 0.7071, 0, 0, -0.3535, 0.6123, 0, -0.3535, -0.6123, 0, + 0, 0, 0.7071, 0, 0, -0.7071 + +]; + +export const tetrahedronIndices: ReadonlyArray<number> = [ + 4, 1, 0, 4, 2, 1, 4, 0, 2, + 0, 1, 3, 1, 2, 3, 2, 0, 3, +]; + +const tetrahedronEdges: ReadonlyArray<number> = [ + 0, 1, 1, 2, 2, 0, + 0, 3, 1, 3, 2, 3, + 0, 4, 1, 4, 2, 4, +] + +let tetrahedron: Primitive +export function Tetrahedron(): Primitive { + if (!tetrahedron) tetrahedron = createPrimitive(tetrahedronVertices, tetrahedronIndices) + return tetrahedron +} + +const tetrahedronCage = createCage(tetrahedronVertices, tetrahedronEdges) +export function TetrahedronCage(): Cage { + return tetrahedronCage +} \ No newline at end of file diff --git a/src/mol-geo/primitive/wedge.ts b/src/mol-geo/primitive/wedge.ts index cfd912b53533d0b1283af25f257d3d72c88a8030..a39125f4fd7b37dc9138abddf1e70fd640801835 100644 --- a/src/mol-geo/primitive/wedge.ts +++ b/src/mol-geo/primitive/wedge.ts @@ -1,5 +1,5 @@ /** - * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author Alexander Rose <alexander.rose@weirdbyte.de> */ @@ -7,12 +7,14 @@ import { Vec3 } from 'mol-math/linear-algebra' import { Primitive, PrimitiveBuilder } from './primitive'; import { polygon } from './polygon' +import { PrismCage } from './prism'; +import { Cage } from './cage'; const a = Vec3.zero(), b = Vec3.zero(), c = Vec3.zero(), d = Vec3.zero() const points = polygon(3, false) /** - * Create a prism with a poligonal base + * Create a prism with a triangular base */ export function createWedge(): Primitive { const builder = PrimitiveBuilder(8) @@ -20,22 +22,22 @@ export function createWedge(): Primitive { // create sides for (let i = 0; i < 3; ++i) { const ni = (i + 1) % 3 - Vec3.set(a, points[i * 2], points[i * 2 + 1], -0.5) - Vec3.set(b, points[ni * 2], points[ni * 2 + 1], -0.5) - Vec3.set(c, points[ni * 2], points[ni * 2 + 1], 0.5) - Vec3.set(d, points[i * 2], points[i * 2 + 1], 0.5) + Vec3.set(a, points[i * 3], points[i * 3 + 1], -0.5) + 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) builder.add(c, d, a) } // create bases Vec3.set(a, points[0], points[1], -0.5) - Vec3.set(b, points[2], points[3], -0.5) - Vec3.set(c, points[4], points[5], -0.5) + Vec3.set(b, points[3], points[4], -0.5) + Vec3.set(c, points[6], points[7], -0.5) builder.add(c, b, a) Vec3.set(a, points[0], points[1], 0.5) - Vec3.set(b, points[2], points[3], 0.5) - Vec3.set(c, points[4], points[5], 0.5) + Vec3.set(b, points[3], points[4], 0.5) + Vec3.set(c, points[6], points[7], 0.5) builder.add(a, b, c) return builder.getPrimitive() @@ -45,4 +47,10 @@ let wedge: Primitive export function Wedge() { if (!wedge) wedge = createWedge() return wedge +} + +let wedgeCage: Cage +export function WedgeCage() { + if (!wedgeCage) wedgeCage = PrismCage(points) + return wedgeCage } \ No newline at end of file diff --git a/src/mol-repr/structure/representation/cartoon.ts b/src/mol-repr/structure/representation/cartoon.ts index fa1a4f32aa77c5a530a663aead2f7c41da25066c..1fef6f9ef60ba99e322b3acc418ebfc9577c9434 100644 --- a/src/mol-repr/structure/representation/cartoon.ts +++ b/src/mol-repr/structure/representation/cartoon.ts @@ -14,11 +14,13 @@ import { Representation, RepresentationParamsGetter, RepresentationContext } fro import { PolymerDirectionVisual, PolymerDirectionParams } from '../visual/polymer-direction-wedge'; import { Structure, Unit } from 'mol-model/structure'; import { ThemeRegistryContext } from 'mol-theme/theme'; +import { NucleotideRingParams, NucleotideRingVisual } from '../visual/nucleotide-ring-mesh'; const CartoonVisuals = { 'polymer-trace': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<Structure, PolymerTraceParams>) => UnitsRepresentation('Polymer trace mesh', ctx, getParams, PolymerTraceVisual), 'polymer-gap': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<Structure, PolymerGapParams>) => UnitsRepresentation('Polymer gap cylinder', ctx, getParams, PolymerGapVisual), 'nucleotide-block': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<Structure, NucleotideBlockParams>) => UnitsRepresentation('Nucleotide block mesh', ctx, getParams, NucleotideBlockVisual), + 'nucleotide-ring': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<Structure, NucleotideRingParams>) => UnitsRepresentation('Nucleotide ring mesh', ctx, getParams, NucleotideRingVisual), 'direction-wedge': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<Structure, PolymerDirectionParams>) => UnitsRepresentation('Polymer direction wedge', ctx, getParams, PolymerDirectionVisual) } type CartoonVisualName = keyof typeof CartoonVisuals @@ -28,6 +30,7 @@ export const CartoonParams = { ...PolymerTraceParams, ...PolymerGapParams, ...NucleotideBlockParams, + ...NucleotideRingParams, ...PolymerDirectionParams, sizeFactor: PD.Numeric(0.2, { min: 0, max: 10, step: 0.01 }), visuals: PD.MultiSelect<CartoonVisualName>(['polymer-trace', 'polymer-gap', 'nucleotide-block'], CartoonVisualOptions), diff --git a/src/mol-repr/structure/visual/nucleotide-ring-mesh.ts b/src/mol-repr/structure/visual/nucleotide-ring-mesh.ts new file mode 100644 index 0000000000000000000000000000000000000000..6baef02237f105ec1392ceb42465fa160525e571 --- /dev/null +++ b/src/mol-repr/structure/visual/nucleotide-ring-mesh.ts @@ -0,0 +1,192 @@ +/** + * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +import { Unit, Structure, ElementIndex } from 'mol-model/structure'; +import { UnitsVisual } from '../representation'; +import { Vec3 } from 'mol-math/linear-algebra'; +import { Segmentation } from 'mol-data/int'; +import { isNucleic, isPurinBase, isPyrimidineBase } from 'mol-model/structure/model/types'; +import { UnitsMeshVisual, UnitsMeshParams } from '../units-visual'; +import { NucleotideLocationIterator, eachNucleotideElement, getNucleotideElementLoci } from './util/nucleotide'; +import { ParamDefinition as PD } from 'mol-util/param-definition'; +import { Mesh } from 'mol-geo/geometry/mesh/mesh'; +import { MeshBuilder } from 'mol-geo/geometry/mesh/mesh-builder'; +import { addCylinder } from 'mol-geo/geometry/mesh/builder/cylinder'; +import { VisualContext } from 'mol-repr/visual'; +import { Theme } from 'mol-theme/theme'; +import { VisualUpdateState } from 'mol-repr/util'; +import { CylinderProps } from 'mol-geo/primitive/cylinder'; +import { NumberArray } from 'mol-util/type-helpers'; +import { addSphere } from 'mol-geo/geometry/mesh/builder/sphere'; + +const pTrace = Vec3.zero() +const pN1 = Vec3.zero() +const pC2 = Vec3.zero() +const pN3 = Vec3.zero() +const pC4 = Vec3.zero() +const pC5 = Vec3.zero() +const pC6 = Vec3.zero() +const pN7 = Vec3.zero() +const pC8 = Vec3.zero() +const pN9 = Vec3.zero() +const normal = Vec3.zero() + +export const NucleotideRingMeshParams = { + sizeFactor: PD.Numeric(0.2, { min: 0, max: 10, step: 0.01 }), + radialSegments: PD.Numeric(16, { min: 3, max: 56, step: 1 }), + detail: PD.Numeric(0, { min: 0, max: 3, step: 1 }), +} +export const DefaultNucleotideRingMeshProps = PD.getDefaultValues(NucleotideRingMeshParams) +export type NucleotideRingProps = typeof DefaultNucleotideRingMeshProps + +const positionsRing5_6 = new Float32Array(2 * 9 * 3) +const stripIndicesRing5_6 = new Uint32Array([0, 1, 2, 3, 4, 5, 6, 7, 16, 17, 14, 15, 12, 13, 8, 9, 10, 11, 0, 1]) +const fanIndicesTopRing5_6 = new Uint32Array([8, 12, 14, 16, 6, 4, 2, 0, 10]) +const fanIndicesBottomRing5_6 = new Uint32Array([9, 11, 1, 3, 5, 7, 17, 15, 13]) + +const positionsRing6 = new Float32Array(2 * 6 * 3) +const stripIndicesRing6 = new Uint32Array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 0, 1]) +const fanIndicesTopRing6 = new Uint32Array([0, 10, 8, 6, 4, 2]) +const fanIndicesBottomRing6 = new Uint32Array([1, 3, 5, 7, 9, 11]) + +const tmpShiftV = Vec3.zero() +function shiftPositions(out: NumberArray, dir: Vec3, ...positions: Vec3[]) { + for (let i = 0, il = positions.length; i < il; ++i) { + const v = positions[i] + Vec3.toArray(Vec3.add(tmpShiftV, v, dir), out, (i * 2) * 3) + Vec3.toArray(Vec3.sub(tmpShiftV, v, dir), out, (i * 2 + 1) * 3) + } +} + +function createNucleotideRingMesh(ctx: VisualContext, unit: Unit, structure: Structure, theme: Theme, props: NucleotideRingProps, mesh?: Mesh) { + if (!Unit.isAtomic(unit)) return Mesh.createEmpty(mesh) + + const nucleotideElementCount = unit.nucleotideElements.length + if (!nucleotideElementCount) return Mesh.createEmpty(mesh) + + const { sizeFactor, radialSegments, detail } = props + + const vertexCount = nucleotideElementCount * (26 + radialSegments * 2) + const builderState = MeshBuilder.createState(vertexCount, vertexCount / 4, mesh) + + const { elements, model } = unit + const { modifiedResidues } = model.properties + const { chainAtomSegments, residueAtomSegments, residues, index: atomicIndex } = model.atomicHierarchy + const { moleculeType, traceElementIndex } = model.atomicHierarchy.derived.residue + const { label_comp_id } = residues + const pos = unit.conformation.invariantPosition + + const chainIt = Segmentation.transientSegments(chainAtomSegments, elements) + const residueIt = Segmentation.transientSegments(residueAtomSegments, elements) + + const radius = 1 * sizeFactor + const halfThickness = 1.25 * sizeFactor + const cylinderProps: CylinderProps = { radiusTop: 1 * sizeFactor, radiusBottom: 1 * sizeFactor, radialSegments } + + let i = 0 + while (chainIt.hasNext) { + residueIt.setSegment(chainIt.move()); + + while (residueIt.hasNext) { + const { index: residueIndex } = residueIt.move(); + + if (isNucleic(moleculeType[residueIndex])) { + let compId = label_comp_id.value(residueIndex) + const parentId = modifiedResidues.parentId.get(compId) + if (parentId !== undefined) compId = parentId + + let idxTrace: ElementIndex | -1 = -1, idxN1: ElementIndex | -1 = -1, idxC2: ElementIndex | -1 = -1, idxN3: ElementIndex | -1 = -1, idxC4: ElementIndex | -1 = -1, idxC5: ElementIndex | -1 = -1, idxC6: ElementIndex | -1 = -1, idxN7: ElementIndex | -1 = -1, idxC8: ElementIndex | -1 = -1, idxN9: ElementIndex | -1 = -1 + + builderState.currentGroup = i + + if (isPurinBase(compId)) { + idxTrace = traceElementIndex[residueIndex] + idxN1 = atomicIndex.findAtomOnResidue(residueIndex, 'N1') + idxC2 = atomicIndex.findAtomOnResidue(residueIndex, 'C2') + idxN3 = atomicIndex.findAtomOnResidue(residueIndex, 'N3') + idxC4 = atomicIndex.findAtomOnResidue(residueIndex, 'C4') + idxC5 = atomicIndex.findAtomOnResidue(residueIndex, 'C5') + idxC6 = atomicIndex.findAtomOnResidue(residueIndex, 'C6') + idxN7 = atomicIndex.findAtomOnResidue(residueIndex, 'N7') + idxC8 = atomicIndex.findAtomOnResidue(residueIndex, 'C8') + idxN9 = atomicIndex.findAtomOnResidue(residueIndex, 'N9') + + if (idxN9 !== -1 && idxTrace !== -1) { + pos(idxN9, pN9); pos(idxTrace, pTrace) + builderState.currentGroup = i + addCylinder(builderState, pN9, pTrace, 1, cylinderProps) + addSphere(builderState, pN9, radius, detail) + } + + if (idxN1 !== -1 && idxC2 !== -1 && idxN3 !== -1 && idxC4 !== -1 && idxC5 !== -1 && idxC6 !== -1 && idxN7 !== -1 && idxC8 !== -1 && idxN9 !== -1 ) { + pos(idxN1, pN1); pos(idxC2, pC2); pos(idxN3, pN3); pos(idxC4, pC4); pos(idxC5, pC5); pos(idxC6, pC6); pos(idxN7, pN7); pos(idxC8, pC8) + + Vec3.triangleNormal(normal, pN1, pC4, pC5) + Vec3.scale(normal, normal, halfThickness) + shiftPositions(positionsRing5_6, normal, pN1, pC2, pN3, pC4, pC5, pC6, pN7, pC8, pN9) + + MeshBuilder.addTriangleStrip(builderState, positionsRing5_6, stripIndicesRing5_6) + MeshBuilder.addTriangleFan(builderState, positionsRing5_6, fanIndicesTopRing5_6) + MeshBuilder.addTriangleFan(builderState, positionsRing5_6, fanIndicesBottomRing5_6) + } + } else if (isPyrimidineBase(compId)) { + idxTrace = traceElementIndex[residueIndex] + idxN1 = atomicIndex.findAtomOnResidue(residueIndex, 'N1') + idxC2 = atomicIndex.findAtomOnResidue(residueIndex, 'C2') + idxN3 = atomicIndex.findAtomOnResidue(residueIndex, 'N3') + idxC4 = atomicIndex.findAtomOnResidue(residueIndex, 'C4') + idxC5 = atomicIndex.findAtomOnResidue(residueIndex, 'C5') + idxC6 = atomicIndex.findAtomOnResidue(residueIndex, 'C6') + + if (idxN1 !== -1 && idxTrace !== -1) { + pos(idxN1, pN1); pos(idxTrace, pTrace) + builderState.currentGroup = i + addCylinder(builderState, pN1, pTrace, 1, cylinderProps) + addSphere(builderState, pN1, radius, detail) + } + + if (idxN1 !== -1 && idxC2 !== -1 && idxN3 !== -1 && idxC4 !== -1 && idxC5 !== -1 && idxC6 !== -1) { + pos(idxC2, pC2); pos(idxN3, pN3); pos(idxC4, pC4); pos(idxC5, pC5); pos(idxC6, pC6); + + Vec3.triangleNormal(normal, pN1, pC4, pC5) + Vec3.scale(normal, normal, halfThickness) + shiftPositions(positionsRing6, normal, pN1, pC2, pN3, pC4, pC5, pC6) + + MeshBuilder.addTriangleStrip(builderState, positionsRing6, stripIndicesRing6) + MeshBuilder.addTriangleFan(builderState, positionsRing6, fanIndicesTopRing6) + MeshBuilder.addTriangleFan(builderState, positionsRing6, fanIndicesBottomRing6) + } + } + + ++i + } + } + } + + return MeshBuilder.getMesh(builderState) +} + +export const NucleotideRingParams = { + ...UnitsMeshParams, + ...NucleotideRingMeshParams +} +export type NucleotideRingParams = typeof NucleotideRingParams + +export function NucleotideRingVisual(): UnitsVisual<NucleotideRingParams> { + return UnitsMeshVisual<NucleotideRingParams>({ + defaultProps: PD.getDefaultValues(NucleotideRingParams), + createGeometry: createNucleotideRingMesh, + createLocationIterator: NucleotideLocationIterator.fromGroup, + getLoci: getNucleotideElementLoci, + eachLocation: eachNucleotideElement, + setUpdateState: (state: VisualUpdateState, newProps: PD.Values<NucleotideRingParams>, currentProps: PD.Values<NucleotideRingParams>) => { + state.createGeometry = ( + newProps.sizeFactor !== currentProps.sizeFactor || + newProps.radialSegments !== currentProps.radialSegments + ) + } + }) +} \ No newline at end of file diff --git a/src/tests/browser/render-lines.ts b/src/tests/browser/render-lines.ts new file mode 100644 index 0000000000000000000000000000000000000000..9eeecd9a090d762b12fb75c563488aad41d40da6 --- /dev/null +++ b/src/tests/browser/render-lines.ts @@ -0,0 +1,44 @@ +/** + * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +import './index.html' +import { Canvas3D } from 'mol-canvas3d/canvas3d'; +import { Mat4 } from 'mol-math/linear-algebra'; +import { Representation } from 'mol-repr/representation'; +import { Color } from 'mol-util/color'; +import { createRenderObject } from 'mol-gl/render-object'; +import { Lines } from 'mol-geo/geometry/lines/lines'; +import { LinesBuilder } from 'mol-geo/geometry/lines/lines-builder'; +import { DodecahedronCage } from 'mol-geo/primitive/dodecahedron'; + +const parent = document.getElementById('app')! +parent.style.width = '100%' +parent.style.height = '100%' + +const canvas = document.createElement('canvas') +canvas.style.width = '100%' +canvas.style.height = '100%' +parent.appendChild(canvas) + +const canvas3d = Canvas3D.create(canvas, parent) +canvas3d.animate() + +function linesRepr() { + const linesBuilder = LinesBuilder.create() + const t = Mat4.identity() + const dodecahedronCage = DodecahedronCage() + linesBuilder.addCage(t, dodecahedronCage, 0) + const lines = linesBuilder.getLines() + + const values = Lines.Utils.createValuesSimple(lines, {}, Color(0xFF0000), 3) + const state = Lines.Utils.createRenderableState({}) + const renderObject = createRenderObject('lines', values, state) + const repr = Representation.fromRenderObject('cage-lines', renderObject) + return repr +} + +canvas3d.add(linesRepr()) +canvas3d.resetCamera() \ No newline at end of file diff --git a/src/tests/browser/render-mesh.ts b/src/tests/browser/render-mesh.ts index 2e50761d2c91009dee24c80adfbcf7369fc66233..4c63a2eb90fbef2500adcdcfea766fc5d045e153 100644 --- a/src/tests/browser/render-mesh.ts +++ b/src/tests/browser/render-mesh.ts @@ -7,12 +7,13 @@ import './index.html' import { Canvas3D } from 'mol-canvas3d/canvas3d'; import { MeshBuilder } from 'mol-geo/geometry/mesh/mesh-builder'; -import { Sphere } from 'mol-geo/primitive/sphere'; import { Mat4 } from 'mol-math/linear-algebra'; import { Mesh } from 'mol-geo/geometry/mesh/mesh'; import { Representation } from 'mol-repr/representation'; import { Color } from 'mol-util/color'; import { createRenderObject } from 'mol-gl/render-object'; +import { SpikedBall } from 'mol-geo/primitive/spiked-ball'; +import { HexagonalPrismCage } from 'mol-geo/primitive/prism'; const parent = document.getElementById('app')! parent.style.width = '100%' @@ -28,15 +29,20 @@ canvas3d.animate() function meshRepr() { const builderState = MeshBuilder.createState() + const t = Mat4.identity() - const sphere = Sphere(2) - MeshBuilder.addPrimitive(builderState, t, sphere) + MeshBuilder.addCage(builderState, t, HexagonalPrismCage(), 0.005, 2) + + const t2 = Mat4.identity() + Mat4.scaleUniformly(t2, t2, 0.1) + MeshBuilder.addPrimitive(builderState, t2, SpikedBall(3)) + const mesh = MeshBuilder.getMesh(builderState) const values = Mesh.Utils.createValuesSimple(mesh, {}, Color(0xFF0000), 1) const state = Mesh.Utils.createRenderableState({}) const renderObject = createRenderObject('mesh', values, state) - const repr = Representation.fromRenderObject('sphere-mesh', renderObject) + const repr = Representation.fromRenderObject('mesh', renderObject) return repr } diff --git a/webpack.config.js b/webpack.config.js index 0298bc8f7b726c2d45c12a3d1263e698b47d26d6..bda3612a92f256206750a7c20e5422ed44d57fc9 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -102,6 +102,7 @@ module.exports = [ createApp('model-server-query'), createBrowserTest('font-atlas'), + createBrowserTest('render-lines'), createBrowserTest('render-mesh'), createBrowserTest('render-shape'), createBrowserTest('render-spheres'),