diff --git a/src/extensions/membrane-orientation/representation.ts b/src/extensions/membrane-orientation/representation.ts index c4cba9058dbfe58cdcbc5e5f62de58cb863e0790..dbd22be50782c254e8117105a9614938650d224b 100644 --- a/src/extensions/membrane-orientation/representation.ts +++ b/src/extensions/membrane-orientation/representation.ts @@ -6,7 +6,7 @@ */ import { ParamDefinition as PD } from '../../mol-util/param-definition'; -import { Vec3 } from '../../mol-math/linear-algebra'; +import { Vec3, Mat4 } from '../../mol-math/linear-algebra'; import { Representation, RepresentationContext, RepresentationParamsGetter } from '../../mol-repr/representation'; import { Structure } from '../../mol-model/structure'; import { Spheres } from '../../mol-geo/geometry/spheres/spheres'; @@ -19,13 +19,55 @@ import { ShapeRepresentation } from '../../mol-repr/shape/representation'; import { Shape } from '../../mol-model/shape'; import { ColorNames } from '../../mol-util/color/names'; import { RuntimeContext } from '../../mol-task'; +import { Lines } from '../../mol-geo/geometry/lines/lines'; +import { Mesh } from '../../mol-geo/geometry/mesh/mesh'; +import { LinesBuilder } from '../../mol-geo/geometry/lines/lines-builder'; +import { Circle } from '../../mol-geo/primitive/circle'; +import { transformPrimitive } from '../../mol-geo/primitive/primitive'; +import { MeshBuilder } from '../../mol-geo/geometry/mesh/mesh-builder'; -const MembraneOrientationParams = { +const SharedParams = { + color: PD.Color(ColorNames.lightgrey) +}; + +const BilayerSpheresParams = { + ...SharedParams, ...Spheres.Params, - color: PD.Color(ColorNames.lightgrey), size: PD.Numeric(1, { min: 0.1, max: 10, step: 0.1 }, { description: 'Size of spheres that represent membrane planes' }), density: PD.Numeric(1, { min: 0.25, max: 10, step: 0.25 }, { description: 'Distance between spheres'}) }; +export type BilayerSpheresParams = typeof BilayerSpheresParams +export type BilayerSpheresProps = PD.Values<BilayerSpheresParams> + +const BilayerPlanesParams = { + ...SharedParams, + ...Mesh.Params +}; +export type BilayerPlanesParams = typeof BilayerPlanesParams +export type BilayerPlanesProps = PD.Values<BilayerPlanesParams> + +const BilayerRimsParams = { + ...SharedParams, + ...Lines.Params, + lineSizeAttenuation: PD.Boolean(true), + linesSize: PD.Numeric(0.04, { min: 0.01, max: 5, step: 0.01 }), + dashLength: PD.Numeric(0.04, { min: 0.01, max: 0.2, step: 0.01 }) +}; +export type BilayerRimsParams = typeof BilayerRimsParams +export type BilayerRimsProps = PD.Values<BilayerRimsParams> + +const MembraneOrientationVisuals = { + 'bilayer-spheres': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<MembraneOrientation, BilayerSpheresParams>) => ShapeRepresentation(getBilayerSpheres, Spheres.Utils, { modifyState: s => ({ ...s, pickable: false }) }), + 'bilayer-planes': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<MembraneOrientation, BilayerPlanesParams>) => ShapeRepresentation(getBilayerPlanes, Mesh.Utils, { modifyState: s => ({ ...s, pickable: false }) }), + 'bilayer-rims': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<MembraneOrientation, BilayerRimsParams>) => ShapeRepresentation(getBilayerRims, Lines.Utils, { modifyState: s => ({ ...s, pickable: false }) }) +}; + +export const MembraneOrientationParams = { + ...BilayerSpheresParams, + ...BilayerPlanesParams, + ...BilayerRimsParams, + visuals: PD.MultiSelect(['bilayer-spheres', 'bilayer-planes', 'bilayer-rims'], PD.objectToOptions(MembraneOrientationVisuals)), +}; export type MembraneOrientationParams = typeof MembraneOrientationParams export type MembraneOrientationProps = PD.Values<MembraneOrientationParams> @@ -38,11 +80,6 @@ export function MembraneOrientationRepresentation(ctx: RepresentationContext, ge return Representation.createMulti('Membrane Orientation', ctx, getParams, StructureRepresentationStateBuilder, MembraneOrientationVisuals as unknown as Representation.Def<Structure, MembraneOrientationParams>); } -const MembraneOrientationVisuals = { - 'membrane-orientation': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<MembraneOrientation, MembraneOrientationParams>) => ShapeRepresentation(getMembraneSpheres, Spheres.Utils), -}; - - export const MembraneOrientationRepresentationProvider = StructureRepresentationProvider({ name: 'membrane-orientation', label: 'Membrane Orientation', @@ -55,24 +92,74 @@ export const MembraneOrientationRepresentationProvider = StructureRepresentation isApplicable: (structure: Structure) => structure.elementCount > 0 }); -function getMembraneSpheres(ctx: RuntimeContext, data: Structure, props: MembraneOrientationProps) { +function getBilayerRims(ctx: RuntimeContext, data: Structure, props: BilayerRimsProps): Shape<Lines> { + const { p1, p2, centroid, normal, radius } = MembraneOrientationProvider.get(data).value!; + const builder = LinesBuilder.create(128, 64); + getLayerCircle(builder, p1, centroid, normal, radius); + getLayerCircle(builder, p2, centroid, normal, radius); + return Shape.create(name, data, builder.getLines(), () => props.color, () => /*props.linesSize*/1, () => ''); +} + +function getLayerCircle(builder: LinesBuilder, p: Vec3, centroid: Vec3, normal: Vec3, radius: number) { + const circle = getCircle(p, centroid, normal, radius); + const { indices, vertices } = circle; + for (let j = 0, jl = indices.length; j < jl; j += 3) { + if (j % 2 === 1) continue; // draw every other segment to get dashes + const start = indices[j] * 3; + const end = indices[j + 1] * 3; + const startX = vertices[start]; + const startY = vertices[start + 1]; + const startZ = vertices[start + 2]; + const endX = vertices[end]; + const endY = vertices[end + 1]; + const endZ = vertices[end + 2]; + builder.add(startX, startY, startZ, endX, endY, endZ, 0); + } +} + +const tmpMat = Mat4(); +function getCircle(p: Vec3, centroid: Vec3, normal: Vec3, radius: number) { + Mat4.targetTo(tmpMat, p, centroid, normal); + Mat4.setTranslation(tmpMat, p); + Mat4.mul(tmpMat, tmpMat, Mat4.rotX90); + + const circle = Circle({ radius }); + return transformPrimitive(circle, tmpMat); +} + +function getBilayerPlanes(ctx: RuntimeContext, data: Structure, props: BilayerPlanesProps): Shape<Mesh> { + const { p1, p2, centroid, normal, radius } = MembraneOrientationProvider.get(data).value!; + const state = MeshBuilder.createState(128, 64); + getLayerPlane(state, p1, centroid, normal, radius); + getLayerPlane(state, p2, centroid, normal, radius); + return Shape.create(name, data, MeshBuilder.getMesh(state), () => props.color, () => /*props.size*/1, () => ''); +} + +function getLayerPlane(state: MeshBuilder.State, p: Vec3, centroid: Vec3, normal: Vec3, radius: number) { + const circle = getCircle(p, centroid, normal, radius); + state.currentGroup = 0; + MeshBuilder.addPrimitive(state, Mat4.id, circle); + MeshBuilder.addPrimitiveFlipped(state, Mat4.id, circle); +} + +function getBilayerSpheres(ctx: RuntimeContext, data: Structure, props: BilayerSpheresProps): Shape<Spheres> { const { density } = props; - const { radius, p1, p2, normal } = MembraneOrientationProvider.get(data).value!;; + const { radius, p1, p2, normal } = MembraneOrientationProvider.get(data).value!; const spheresBuilder = SpheresBuilder.create(); - createMembraneLayer(spheresBuilder, p1, normal, density, radius); - createMembraneLayer(spheresBuilder, p2, normal, density, radius); - return Shape.create(name, data, spheresBuilder.getSpheres(), () => props.color, () => props.size, () => 'Membrane Boundaries'); + getLayerSpheres(spheresBuilder, p1, normal, density, radius * radius); + getLayerSpheres(spheresBuilder, p2, normal, density, radius * radius); + return Shape.create(name, data, spheresBuilder.getSpheres(), () => props.color, () => props.size, () => ''); } -function createMembraneLayer(spheresBuilder: SpheresBuilder, point: Vec3, normalVector: Vec3, density: number, radius: number) { +function getLayerSpheres(spheresBuilder: SpheresBuilder, point: Vec3, normalVector: Vec3, density: number, sqRadius: number) { Vec3.normalize(normalVector, normalVector); const d = -Vec3.dot(normalVector, point); const rep = Vec3(); for (let i = -1000, il = 1000; i < il; i += density) { for (let j = -1000, jl = 1000; j < jl; j += density) { Vec3.set(rep, i, j, normalVector[2] === 0 ? 0 : -(d + i * normalVector[0] + j * normalVector[1]) / normalVector[2]); - if (Vec3.squaredDistance(rep, point) < radius) { + if (Vec3.squaredDistance(rep, point) < sqRadius) { spheresBuilder.add(rep[0], rep[1], rep[2], 0); } } diff --git a/src/mol-model-props/computed/membrane-orientation/ANVIL.ts b/src/mol-model-props/computed/membrane-orientation/ANVIL.ts index f46710b1b9a7c30bea15de9c9511d0a5c9fd88c0..2050887f2ee1b8b179ae7ffc281eaf2fe2f0224c 100644 --- a/src/mol-model-props/computed/membrane-orientation/ANVIL.ts +++ b/src/mol-model-props/computed/membrane-orientation/ANVIL.ts @@ -123,7 +123,7 @@ export async function calculate(runtime: RuntimeContext, structure: Structure, p const membrane = initialMembrane.qmax! > alternativeMembrane.qmax! ? initialMembrane : alternativeMembrane; - return MembraneOrientation(membrane.planePoint1, membrane.planePoint2, membrane.normalVector!, ctx.extent * ctx.extent); + return MembraneOrientation(membrane.planePoint1, membrane.planePoint2, membrane.normalVector!, ctx.extent, ctx.centroid); } interface MembraneCandidate { diff --git a/src/mol-model/structure/model/properties/membrane-orientation.ts b/src/mol-model/structure/model/properties/membrane-orientation.ts index eb15083465fd683453b1746892cedb6729c7f652..72a8d0313820b9978600eef50b6842def2aad510 100644 --- a/src/mol-model/structure/model/properties/membrane-orientation.ts +++ b/src/mol-model/structure/model/properties/membrane-orientation.ts @@ -14,11 +14,12 @@ interface MembraneOrientation { // normal vector of membrane layer readonly normal: Vec3, // the radius of the membrane layer - readonly radius: number + readonly radius: number, + readonly centroid: Vec3 } -function MembraneOrientation(p1: Vec3, p2: Vec3, normal: Vec3, radius: number): MembraneOrientation { - return { p1, p2, normal, radius }; +function MembraneOrientation(p1: Vec3, p2: Vec3, normal: Vec3, radius: number, centroid: Vec3): MembraneOrientation { + return { p1, p2, normal, radius, centroid }; } export { MembraneOrientation }; \ No newline at end of file