Skip to content
Snippets Groups Projects
Commit 15782111 authored by Alexander Rose's avatar Alexander Rose
Browse files

add helix profile option to cartoon repr

parent 15932dc5
No related branches found
No related tags found
No related merge requests found
...@@ -9,6 +9,7 @@ Note that since we don't clearly distinguish between a public and private interf ...@@ -9,6 +9,7 @@ Note that since we don't clearly distinguish between a public and private interf
- Added ``ViewerOptions.collapseRightPanel`` - Added ``ViewerOptions.collapseRightPanel``
- Added ``Viewer.loadTrajectory`` to support loading "composed" trajectories (e.g. from gro + xtc) - Added ``Viewer.loadTrajectory`` to support loading "composed" trajectories (e.g. from gro + xtc)
- Fix: handle parent in Structure.remapModel - Fix: handle parent in Structure.remapModel
- Add ``rounded`` and ``square`` helix profile options to Cartoon representation (in addition to the default ``elliptical``)
## [v2.3.6] - 2021-11-8 ## [v2.3.6] - 2021-11-8
......
/** /**
* Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info. * Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
* *
* @author Alexander Rose <alexander.rose@weirdbyte.de> * @author Alexander Rose <alexander.rose@weirdbyte.de>
* @author David Sehnal <david.sehnal@gmail.com> * @author David Sehnal <david.sehnal@gmail.com>
...@@ -40,7 +40,7 @@ const v3set = Vec3.set; ...@@ -40,7 +40,7 @@ const v3set = Vec3.set;
const caAdd3 = ChunkedArray.add3; const caAdd3 = ChunkedArray.add3;
const caAdd = ChunkedArray.add; const caAdd = ChunkedArray.add;
function addCap(offset: number, state: MeshBuilder.State, controlPoints: ArrayLike<number>, normalVectors: ArrayLike<number>, binormalVectors: ArrayLike<number>, width: number, leftHeight: number, rightHeight: number) { function addCap(offset: number, state: MeshBuilder.State, controlPoints: ArrayLike<number>, normalVectors: ArrayLike<number>, binormalVectors: ArrayLike<number>, width: number, leftHeight: number, rightHeight: number, flip: boolean) {
const { vertices, normals, indices } = state; const { vertices, normals, indices } = state;
const vertexCount = vertices.elementCount; const vertexCount = vertices.elementCount;
...@@ -74,11 +74,19 @@ function addCap(offset: number, state: MeshBuilder.State, controlPoints: ArrayLi ...@@ -74,11 +74,19 @@ function addCap(offset: number, state: MeshBuilder.State, controlPoints: ArrayLi
v3copy(verticalVector, verticalLeftVector); v3copy(verticalVector, verticalLeftVector);
} }
for (let i = 0; i < 4; ++i) { if (flip) {
caAdd3(normals, normalVector[0], normalVector[1], normalVector[2]); for (let i = 0; i < 4; ++i) {
caAdd3(normals, -normalVector[0], -normalVector[1], -normalVector[2]);
}
caAdd3(indices, vertexCount, vertexCount + 1, vertexCount + 2);
caAdd3(indices, vertexCount + 2, vertexCount + 3, vertexCount);
} else {
for (let i = 0; i < 4; ++i) {
caAdd3(normals, normalVector[0], normalVector[1], normalVector[2]);
}
caAdd3(indices, vertexCount + 2, vertexCount + 1, vertexCount);
caAdd3(indices, vertexCount, vertexCount + 3, vertexCount + 2);
} }
caAdd3(indices, vertexCount + 2, vertexCount + 1, vertexCount);
caAdd3(indices, vertexCount, vertexCount + 3, vertexCount + 2);
} }
/** set arrowHeight = 0 for no arrow */ /** set arrowHeight = 0 for no arrow */
...@@ -193,19 +201,18 @@ export function addSheet(state: MeshBuilder.State, controlPoints: ArrayLike<numb ...@@ -193,19 +201,18 @@ export function addSheet(state: MeshBuilder.State, controlPoints: ArrayLike<numb
const width = widthValues[0]; const width = widthValues[0];
const height = heightValues[0]; const height = heightValues[0];
const h = arrowHeight === 0 ? height : arrowHeight; const h = arrowHeight === 0 ? height : arrowHeight;
addCap(0, state, controlPoints, normalVectors, binormalVectors, width, h, h); addCap(0, state, controlPoints, normalVectors, binormalVectors, width, h, h, false);
} else if (arrowHeight > 0) { } else if (arrowHeight > 0) {
const width = widthValues[0]; const width = widthValues[0];
const height = heightValues[0]; const height = heightValues[0];
addCap(0, state, controlPoints, normalVectors, binormalVectors, width, arrowHeight, -height); addCap(0, state, controlPoints, normalVectors, binormalVectors, width, arrowHeight, -height, false);
addCap(0, state, controlPoints, normalVectors, binormalVectors, width, -arrowHeight, height); addCap(0, state, controlPoints, normalVectors, binormalVectors, width, -arrowHeight, height, false);
} }
if (endCap && arrowHeight === 0) { if (endCap && arrowHeight === 0) {
const width = widthValues[linearSegments]; const width = widthValues[linearSegments];
const height = heightValues[linearSegments]; const height = heightValues[linearSegments];
// use negative height to flip the direction the cap's triangles are facing addCap(linearSegments * 3, state, controlPoints, normalVectors, binormalVectors, width, height, height, true);
addCap(linearSegments * 3, state, controlPoints, normalVectors, binormalVectors, width, -height, -height);
} }
const addedVertexCount = (linearSegments + 1) * 8 + const addedVertexCount = (linearSegments + 1) * 8 +
......
/** /**
* Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info. * Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
* *
* @author Alexander Rose <alexander.rose@weirdbyte.de> * @author Alexander Rose <alexander.rose@weirdbyte.de>
* @author David Sehnal <david.sehnal@gmail.com> * @author David Sehnal <david.sehnal@gmail.com>
...@@ -30,9 +30,10 @@ function add3AndScale2(out: Vec3, a: Vec3, b: Vec3, c: Vec3, sa: number, sb: num ...@@ -30,9 +30,10 @@ function add3AndScale2(out: Vec3, a: Vec3, b: Vec3, c: Vec3, sa: number, sb: num
// avoiding namespace lookup improved performance in Chrome (Aug 2020) // avoiding namespace lookup improved performance in Chrome (Aug 2020)
const v3fromArray = Vec3.fromArray; const v3fromArray = Vec3.fromArray;
const v3normalize = Vec3.normalize; const v3normalize = Vec3.normalize;
const v3negate = Vec3.negate; const v3scaleAndAdd = Vec3.scaleAndAdd;
const v3copy = Vec3.copy;
const v3cross = Vec3.cross; const v3cross = Vec3.cross;
const v3dot = Vec3.dot;
const v3unitX = Vec3.unitX;
const caAdd3 = ChunkedArray.add3; const caAdd3 = ChunkedArray.add3;
const CosSinCache = new Map<number, { cos: number[], sin: number[] }>(); const CosSinCache = new Map<number, { cos: number[], sin: number[] }>();
...@@ -50,13 +51,16 @@ function getCosSin(radialSegments: number) { ...@@ -50,13 +51,16 @@ function getCosSin(radialSegments: number) {
return CosSinCache.get(radialSegments)!; return CosSinCache.get(radialSegments)!;
} }
export function addTube(state: MeshBuilder.State, controlPoints: ArrayLike<number>, normalVectors: ArrayLike<number>, binormalVectors: ArrayLike<number>, linearSegments: number, radialSegments: number, widthValues: ArrayLike<number>, heightValues: ArrayLike<number>, startCap: boolean, endCap: boolean) { export function addTube(state: MeshBuilder.State, controlPoints: ArrayLike<number>, normalVectors: ArrayLike<number>, binormalVectors: ArrayLike<number>, linearSegments: number, radialSegments: number, widthValues: ArrayLike<number>, heightValues: ArrayLike<number>, startCap: boolean, endCap: boolean, crossSection: 'elliptical' | 'rounded') {
const { currentGroup, vertices, normals, indices, groups } = state; const { currentGroup, vertices, normals, indices, groups } = state;
let vertexCount = vertices.elementCount; let vertexCount = vertices.elementCount;
const { cos, sin } = getCosSin(radialSegments); const { cos, sin } = getCosSin(radialSegments);
const q1 = radialSegments / 4;
const q3 = q1 * 3;
for (let i = 0; i <= linearSegments; ++i) { for (let i = 0; i <= linearSegments; ++i) {
const i3 = i * 3; const i3 = i * 3;
v3fromArray(u, normalVectors, i3); v3fromArray(u, normalVectors, i3);
...@@ -65,14 +69,18 @@ export function addTube(state: MeshBuilder.State, controlPoints: ArrayLike<numbe ...@@ -65,14 +69,18 @@ export function addTube(state: MeshBuilder.State, controlPoints: ArrayLike<numbe
const width = widthValues[i]; const width = widthValues[i];
const height = heightValues[i]; const height = heightValues[i];
const rounded = crossSection === 'rounded' && height > width;
for (let j = 0; j < radialSegments; ++j) { for (let j = 0; j < radialSegments; ++j) {
add3AndScale2(surfacePoint, u, v, controlPoint, height * cos[j], width * sin[j]); if (rounded) {
if (radialSegments === 2) { add3AndScale2(surfacePoint, u, v, controlPoint, width * cos[j], width * sin[j]);
v3copy(normalVector, v); const h = v3dot(v, v3unitX) < 0
v3normalize(normalVector, normalVector); ? (j < q1 || j >= q3) ? height - width : -height + width
if (j !== 0 || i % 2 === 0) v3negate(normalVector, normalVector); : (j >= q1 && j < q3) ? -height + width : height - width;
v3scaleAndAdd(surfacePoint, surfacePoint, u, h);
add2AndScale2(normalVector, u, v, cos[j], sin[j]);
} else { } else {
add3AndScale2(surfacePoint, u, v, controlPoint, height * cos[j], width * sin[j]);
add2AndScale2(normalVector, u, v, width * cos[j], height * sin[j]); add2AndScale2(normalVector, u, v, width * cos[j], height * sin[j]);
} }
v3normalize(normalVector, normalVector); v3normalize(normalVector, normalVector);
...@@ -82,19 +90,37 @@ export function addTube(state: MeshBuilder.State, controlPoints: ArrayLike<numbe ...@@ -82,19 +90,37 @@ export function addTube(state: MeshBuilder.State, controlPoints: ArrayLike<numbe
} }
} }
const radialSegmentsHalf = Math.round(radialSegments / 2);
for (let i = 0; i < linearSegments; ++i) { for (let i = 0; i < linearSegments; ++i) {
for (let j = 0; j < radialSegments; ++j) { // the triangles are arranged such that opposing triangles of the sheet align
// which prevents triangle intersection within tight curves
for (let j = 0; j < radialSegmentsHalf; ++j) {
caAdd3(
indices,
vertexCount + i * radialSegments + (j + 1) % radialSegments, // a
vertexCount + (i + 1) * radialSegments + (j + 1) % radialSegments, // c
vertexCount + i * radialSegments + j // b
);
caAdd3(
indices,
vertexCount + (i + 1) * radialSegments + (j + 1) % radialSegments, // c
vertexCount + (i + 1) * radialSegments + j, // d
vertexCount + i * radialSegments + j // b
);
}
for (let j = radialSegmentsHalf; j < radialSegments; ++j) {
caAdd3( caAdd3(
indices, indices,
vertexCount + i * radialSegments + (j + 1) % radialSegments, vertexCount + i * radialSegments + (j + 1) % radialSegments, // a
vertexCount + (i + 1) * radialSegments + (j + 1) % radialSegments, vertexCount + (i + 1) * radialSegments + j, // d
vertexCount + i * radialSegments + j vertexCount + i * radialSegments + j // b
); );
caAdd3( caAdd3(
indices, indices,
vertexCount + (i + 1) * radialSegments + (j + 1) % radialSegments, vertexCount + (i + 1) * radialSegments + (j + 1) % radialSegments, // c
vertexCount + (i + 1) * radialSegments + j, vertexCount + (i + 1) * radialSegments + j, // d
vertexCount + i * radialSegments + j vertexCount + i * radialSegments + (j + 1) % radialSegments, // a
); );
} }
} }
...@@ -111,11 +137,18 @@ export function addTube(state: MeshBuilder.State, controlPoints: ArrayLike<numbe ...@@ -111,11 +137,18 @@ export function addTube(state: MeshBuilder.State, controlPoints: ArrayLike<numbe
caAdd3(normals, normalVector[0], normalVector[1], normalVector[2]); caAdd3(normals, normalVector[0], normalVector[1], normalVector[2]);
const width = widthValues[0]; const width = widthValues[0];
const height = heightValues[0]; let height = heightValues[0];
const rounded = crossSection === 'rounded' && height > width;
if (rounded) height -= width;
vertexCount = vertices.elementCount; vertexCount = vertices.elementCount;
for (let i = 0; i < radialSegments; ++i) { for (let i = 0; i < radialSegments; ++i) {
add3AndScale2(surfacePoint, u, v, controlPoint, height * cos[i], width * sin[i]); if (rounded) {
add3AndScale2(surfacePoint, u, v, controlPoint, width * cos[i], width * sin[i]);
v3scaleAndAdd(surfacePoint, surfacePoint, u, (i < q1 || i >= q3) ? height : -height);
} else {
add3AndScale2(surfacePoint, u, v, controlPoint, height * cos[i], width * sin[i]);
}
caAdd3(vertices, surfacePoint[0], surfacePoint[1], surfacePoint[2]); caAdd3(vertices, surfacePoint[0], surfacePoint[1], surfacePoint[2]);
caAdd3(normals, normalVector[0], normalVector[1], normalVector[2]); caAdd3(normals, normalVector[0], normalVector[1], normalVector[2]);
...@@ -141,11 +174,18 @@ export function addTube(state: MeshBuilder.State, controlPoints: ArrayLike<numbe ...@@ -141,11 +174,18 @@ export function addTube(state: MeshBuilder.State, controlPoints: ArrayLike<numbe
caAdd3(normals, normalVector[0], normalVector[1], normalVector[2]); caAdd3(normals, normalVector[0], normalVector[1], normalVector[2]);
const width = widthValues[linearSegments]; const width = widthValues[linearSegments];
const height = heightValues[linearSegments]; let height = heightValues[linearSegments];
const rounded = crossSection === 'rounded' && height > width;
if (rounded) height -= width;
vertexCount = vertices.elementCount; vertexCount = vertices.elementCount;
for (let i = 0; i < radialSegments; ++i) { for (let i = 0; i < radialSegments; ++i) {
add3AndScale2(surfacePoint, u, v, controlPoint, height * cos[i], width * sin[i]); if (rounded) {
add3AndScale2(surfacePoint, u, v, controlPoint, width * cos[i], width * sin[i]);
v3scaleAndAdd(surfacePoint, surfacePoint, u, (i < q1 || i >= q3) ? height : -height);
} else {
add3AndScale2(surfacePoint, u, v, controlPoint, height * cos[i], width * sin[i]);
}
caAdd3(vertices, surfacePoint[0], surfacePoint[1], surfacePoint[2]); caAdd3(vertices, surfacePoint[0], surfacePoint[1], surfacePoint[2]);
caAdd3(normals, normalVector[0], normalVector[1], normalVector[2]); caAdd3(normals, normalVector[0], normalVector[1], normalVector[2]);
......
...@@ -27,8 +27,9 @@ import { StructureGroup } from './util/common'; ...@@ -27,8 +27,9 @@ import { StructureGroup } from './util/common';
export const PolymerTraceMeshParams = { export const PolymerTraceMeshParams = {
sizeFactor: PD.Numeric(0.2, { min: 0, max: 10, step: 0.01 }), sizeFactor: PD.Numeric(0.2, { min: 0, max: 10, step: 0.01 }),
aspectRatio: PD.Numeric(5, { min: 0.1, max: 10, step: 0.1 }), aspectRatio: PD.Numeric(5, { min: 0.1, max: 10, step: 0.1 }),
arrowFactor: PD.Numeric(1.5, { min: 0, max: 3, step: 0.1 }), arrowFactor: PD.Numeric(1.5, { min: 0, max: 3, step: 0.1 }, { description: 'Size factor for sheet arrows' }),
tubularHelices: PD.Boolean(false), tubularHelices: PD.Boolean(false, { description: 'Draw alpha helices as tubes' }),
helixProfile: PD.Select('elliptical', PD.arrayToOptions(['elliptical', 'rounded', 'square'] as const), { description: 'Protein and nucleic helix trace profile' }),
detail: PD.Numeric(0, { min: 0, max: 3, step: 1 }, BaseGeometry.CustomQualityParamInfo), detail: PD.Numeric(0, { min: 0, max: 3, step: 1 }, BaseGeometry.CustomQualityParamInfo),
linearSegments: PD.Numeric(8, { min: 1, max: 48, step: 1 }, BaseGeometry.CustomQualityParamInfo), linearSegments: PD.Numeric(8, { min: 1, max: 48, step: 1 }, BaseGeometry.CustomQualityParamInfo),
radialSegments: PD.Numeric(16, { min: 2, max: 56, step: 2 }, BaseGeometry.CustomQualityParamInfo) radialSegments: PD.Numeric(16, { min: 2, max: 56, step: 2 }, BaseGeometry.CustomQualityParamInfo)
...@@ -42,7 +43,7 @@ function createPolymerTraceMesh(ctx: VisualContext, unit: Unit, structure: Struc ...@@ -42,7 +43,7 @@ function createPolymerTraceMesh(ctx: VisualContext, unit: Unit, structure: Struc
const polymerElementCount = unit.polymerElements.length; const polymerElementCount = unit.polymerElements.length;
if (!polymerElementCount) return Mesh.createEmpty(mesh); if (!polymerElementCount) return Mesh.createEmpty(mesh);
const { sizeFactor, detail, linearSegments, radialSegments, aspectRatio, arrowFactor, tubularHelices } = props; const { sizeFactor, detail, linearSegments, radialSegments, aspectRatio, arrowFactor, tubularHelices, helixProfile } = props;
const vertexCount = linearSegments * radialSegments * polymerElementCount + (radialSegments + 1) * polymerElementCount * 2; const vertexCount = linearSegments * radialSegments * polymerElementCount + (radialSegments + 1) * polymerElementCount * 2;
const builderState = MeshBuilder.createState(vertexCount, vertexCount / 10, mesh); const builderState = MeshBuilder.createState(vertexCount, vertexCount / 10, mesh);
...@@ -131,9 +132,6 @@ function createPolymerTraceMesh(ctx: VisualContext, unit: Unit, structure: Struc ...@@ -131,9 +132,6 @@ function createPolymerTraceMesh(ctx: VisualContext, unit: Unit, structure: Struc
h0 = w0 * aspectRatio; h0 = w0 * aspectRatio;
h1 = w1 * aspectRatio; h1 = w1 * aspectRatio;
h2 = w2 * aspectRatio; h2 = w2 * aspectRatio;
[w0, h0] = [h0, w0];
[w1, h1] = [h1, w1];
[w2, h2] = [h2, w2];
} else { } else {
h0 = w0; h0 = w0;
h1 = w1; h1 = w1;
...@@ -142,18 +140,26 @@ function createPolymerTraceMesh(ctx: VisualContext, unit: Unit, structure: Struc ...@@ -142,18 +140,26 @@ function createPolymerTraceMesh(ctx: VisualContext, unit: Unit, structure: Struc
interpolateSizes(state, w0, w1, w2, h0, h1, h2, shift); interpolateSizes(state, w0, w1, w2, h0, h1, h2, shift);
const [normals, binormals] = isNucleicType && !v.isCoarseBackbone ? [binormalVectors, normalVectors] : [normalVectors, binormalVectors];
if (isNucleicType && !v.isCoarseBackbone) {
// TODO: find a cleaner way to swap normal and binormal for nucleic types
for (let i = 0, il = normals.length; i < il; i++) normals[i] *= -1;
}
if (radialSegments === 2) { if (radialSegments === 2) {
if (isNucleicType && !v.isCoarseBackbone) { if (isNucleicType && !v.isCoarseBackbone) {
// TODO find a cleaner way to swap normal and binormal for nucleic types addRibbon(builderState, curvePoints, normals, binormals, segmentCount, heightValues, widthValues, 0);
for (let i = 0, il = binormalVectors.length; i < il; i++) binormalVectors[i] *= -1;
addRibbon(builderState, curvePoints, binormalVectors, normalVectors, segmentCount, heightValues, widthValues, 0);
} else { } else {
addRibbon(builderState, curvePoints, normalVectors, binormalVectors, segmentCount, widthValues, heightValues, 0); addRibbon(builderState, curvePoints, normals, binormals, segmentCount, widthValues, heightValues, 0);
} }
} else if (radialSegments === 4) { } else if (radialSegments === 4) {
addSheet(builderState, curvePoints, normalVectors, binormalVectors, segmentCount, widthValues, heightValues, 0, startCap, endCap); addSheet(builderState, curvePoints, normals, binormals, segmentCount, widthValues, heightValues, 0, startCap, endCap);
} else if (h1 === w1) {
addTube(builderState, curvePoints, normals, binormals, segmentCount, radialSegments, widthValues, heightValues, startCap, endCap, 'elliptical');
} else if (helixProfile === 'square') {
addSheet(builderState, curvePoints, normals, binormals, segmentCount, widthValues, heightValues, 0, startCap, endCap);
} else { } else {
addTube(builderState, curvePoints, normalVectors, binormalVectors, segmentCount, radialSegments, widthValues, heightValues, startCap, endCap); addTube(builderState, curvePoints, normals, binormals, segmentCount, radialSegments, widthValues, heightValues, startCap, endCap, helixProfile);
} }
} }
...@@ -189,7 +195,8 @@ export function PolymerTraceVisual(materialId: number): UnitsVisual<PolymerTrace ...@@ -189,7 +195,8 @@ export function PolymerTraceVisual(materialId: number): UnitsVisual<PolymerTrace
newProps.linearSegments !== currentProps.linearSegments || newProps.linearSegments !== currentProps.linearSegments ||
newProps.radialSegments !== currentProps.radialSegments || newProps.radialSegments !== currentProps.radialSegments ||
newProps.aspectRatio !== currentProps.aspectRatio || newProps.aspectRatio !== currentProps.aspectRatio ||
newProps.arrowFactor !== currentProps.arrowFactor newProps.arrowFactor !== currentProps.arrowFactor ||
newProps.helixProfile !== currentProps.helixProfile
); );
const secondaryStructureHash = SecondaryStructureProvider.get(newStructureGroup.structure).version; const secondaryStructureHash = SecondaryStructureProvider.get(newStructureGroup.structure).version;
......
...@@ -93,7 +93,7 @@ function createPolymerTubeMesh(ctx: VisualContext, unit: Unit, structure: Struct ...@@ -93,7 +93,7 @@ function createPolymerTubeMesh(ctx: VisualContext, unit: Unit, structure: Struct
} else if (radialSegments === 4) { } else if (radialSegments === 4) {
addSheet(builderState, curvePoints, normalVectors, binormalVectors, segmentCount, widthValues, heightValues, 0, startCap, endCap); addSheet(builderState, curvePoints, normalVectors, binormalVectors, segmentCount, widthValues, heightValues, 0, startCap, endCap);
} else { } else {
addTube(builderState, curvePoints, normalVectors, binormalVectors, segmentCount, radialSegments, widthValues, heightValues, startCap, endCap); addTube(builderState, curvePoints, normalVectors, binormalVectors, segmentCount, radialSegments, widthValues, heightValues, startCap, endCap, 'elliptical');
} }
++i; ++i;
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment