|
@@ -7,26 +7,27 @@
|
|
|
import { ValueCell } from 'mol-util/value-cell'
|
|
|
|
|
|
import { createMeshRenderObject, MeshRenderObject } from 'mol-gl/render-object'
|
|
|
-import { Unit, Structure } from 'mol-model/structure';
|
|
|
+import { Unit, Structure, StructureElement } from 'mol-model/structure';
|
|
|
import { DefaultStructureProps, StructureVisual } from '..';
|
|
|
import { RuntimeContext } from 'mol-task'
|
|
|
-import { createIdentityTransform } from './util/common';
|
|
|
+import { createIdentityTransform, createColors } from './util/common';
|
|
|
import { MeshValues } from 'mol-gl/renderable';
|
|
|
import { getMeshData } from '../../../util/mesh-data';
|
|
|
import { Mesh } from '../../../shape/mesh';
|
|
|
import { PickingId } from '../../../util/picking';
|
|
|
-import { createMarkers, MarkerAction } from '../../../util/marker-data';
|
|
|
-import { Loci, EmptyLoci } from 'mol-model/loci';
|
|
|
+import { createMarkers, MarkerAction, MarkerData, applyMarkerAction } from '../../../util/marker-data';
|
|
|
+import { Loci, EmptyLoci, isEveryLoci } from 'mol-model/loci';
|
|
|
import { SizeTheme } from '../../../theme';
|
|
|
import { createMeshValues, updateMeshValues, updateRenderableState, createRenderableState, DefaultMeshProps } from '../../util';
|
|
|
import { MeshBuilder } from '../../../shape/mesh-builder';
|
|
|
import { Vec3, Mat4 } from 'mol-math/linear-algebra';
|
|
|
-import { createValueColor } from '../../../util/color-data';
|
|
|
import { getSaccharideShape, SaccharideShapes } from 'mol-model/structure/structure/carbohydrates/constants';
|
|
|
+import { deepEqual } from 'mol-util';
|
|
|
+import { LocationIterator } from './util/location-iterator';
|
|
|
+import { OrderedSet } from 'mol-data/int';
|
|
|
|
|
|
const t = Mat4.identity()
|
|
|
const sVec = Vec3.zero()
|
|
|
-const p = Vec3.zero()
|
|
|
const pd = Vec3.zero()
|
|
|
|
|
|
async function createCarbohydrateSymbolMesh(ctx: RuntimeContext, structure: Structure, mesh?: Mesh) {
|
|
@@ -34,89 +35,74 @@ async function createCarbohydrateSymbolMesh(ctx: RuntimeContext, structure: Stru
|
|
|
|
|
|
const carbohydrates = structure.carbohydrates
|
|
|
|
|
|
- function centerAlign(center: Vec3, normal: Vec3, direction: Vec3) {
|
|
|
- Vec3.add(pd, center, direction)
|
|
|
- Mat4.targetTo(t, center, pd, normal)
|
|
|
- Mat4.setTranslation(t, center)
|
|
|
- }
|
|
|
-
|
|
|
const side = 1.75 * 2 * 0.806; // 0.806 == Math.cos(Math.PI / 4)
|
|
|
const radius = 1.75
|
|
|
|
|
|
- const linkParams = { radiusTop: 0.4, radiusBottom: 0.4 }
|
|
|
-
|
|
|
for (let i = 0, il = carbohydrates.elements.length; i < il; ++i) {
|
|
|
const c = carbohydrates.elements[i];
|
|
|
- if (!c.hasRing) continue;
|
|
|
-
|
|
|
- const cGeo = c.geometry!
|
|
|
const shapeType = getSaccharideShape(c.component.type)
|
|
|
|
|
|
+ const { center, normal, direction } = c.geometry
|
|
|
+ Vec3.add(pd, center, direction)
|
|
|
+ Mat4.targetTo(t, center, pd, normal)
|
|
|
+ Mat4.setTranslation(t, center)
|
|
|
+
|
|
|
+ builder.setId(i)
|
|
|
+
|
|
|
switch (shapeType) {
|
|
|
case SaccharideShapes.FilledSphere:
|
|
|
- builder.addSphere(cGeo.center, radius, 2)
|
|
|
+ builder.addSphere(center, radius, 2)
|
|
|
break;
|
|
|
case SaccharideShapes.FilledCube:
|
|
|
- centerAlign(cGeo.center, cGeo.normal, cGeo.direction)
|
|
|
Mat4.scaleUniformly(t, t, side)
|
|
|
builder.addBox(t)
|
|
|
break;
|
|
|
case SaccharideShapes.CrossedCube:
|
|
|
// TODO split
|
|
|
- centerAlign(cGeo.center, cGeo.normal, cGeo.direction)
|
|
|
Mat4.scaleUniformly(t, t, side)
|
|
|
builder.addBox(t)
|
|
|
break;
|
|
|
case SaccharideShapes.FilledCone:
|
|
|
- centerAlign(cGeo.center, cGeo.normal, cGeo.direction)
|
|
|
Mat4.scaleUniformly(t, t, side * 1.2)
|
|
|
builder.addOctagonalPyramid(t)
|
|
|
break
|
|
|
case SaccharideShapes.DevidedCone:
|
|
|
// TODO split
|
|
|
- centerAlign(cGeo.center, cGeo.normal, cGeo.direction)
|
|
|
Mat4.scaleUniformly(t, t, side * 1.2)
|
|
|
builder.addOctagonalPyramid(t)
|
|
|
break
|
|
|
case SaccharideShapes.FlatBox:
|
|
|
- centerAlign(cGeo.center, cGeo.normal, cGeo.direction)
|
|
|
Mat4.mul(t, t, Mat4.rotZY90)
|
|
|
Mat4.scale(t, t, Vec3.set(sVec, side, side, side / 2))
|
|
|
builder.addBox(t)
|
|
|
break
|
|
|
case SaccharideShapes.FilledStar:
|
|
|
- centerAlign(cGeo.center, cGeo.normal, cGeo.direction)
|
|
|
Mat4.mul(t, t, Mat4.rotZY90)
|
|
|
builder.addStar(t, { outerRadius: side, innerRadius: side / 2, thickness: side / 2, pointCount: 5 })
|
|
|
break
|
|
|
case SaccharideShapes.FilledDiamond:
|
|
|
- centerAlign(cGeo.center, cGeo.normal, cGeo.direction)
|
|
|
Mat4.mul(t, t, Mat4.rotZY90)
|
|
|
Mat4.scale(t, t, Vec3.set(sVec, side * 1.4, side * 1.4, side * 1.4))
|
|
|
builder.addOctahedron(t)
|
|
|
break
|
|
|
case SaccharideShapes.DividedDiamond:
|
|
|
// TODO split
|
|
|
- centerAlign(cGeo.center, cGeo.normal, cGeo.direction)
|
|
|
Mat4.mul(t, t, Mat4.rotZY90)
|
|
|
Mat4.scale(t, t, Vec3.set(sVec, side * 1.4, side * 1.4, side * 1.4))
|
|
|
builder.addOctahedron(t)
|
|
|
break
|
|
|
case SaccharideShapes.FlatDiamond:
|
|
|
- centerAlign(cGeo.center, cGeo.normal, cGeo.direction)
|
|
|
Mat4.mul(t, t, Mat4.rotZY90)
|
|
|
Mat4.scale(t, t, Vec3.set(sVec, side, side / 2, side / 2))
|
|
|
builder.addDiamondPrism(t)
|
|
|
break
|
|
|
case SaccharideShapes.Pentagon:
|
|
|
- centerAlign(cGeo.center, cGeo.normal, cGeo.direction)
|
|
|
Mat4.mul(t, t, Mat4.rotZY90)
|
|
|
Mat4.scale(t, t, Vec3.set(sVec, side, side, side / 2))
|
|
|
builder.addPentagonalPrism(t)
|
|
|
break
|
|
|
case SaccharideShapes.FlatHexagon:
|
|
|
default:
|
|
|
- centerAlign(cGeo.center, cGeo.normal, cGeo.direction)
|
|
|
Mat4.mul(t, t, Mat4.rotZYZ90)
|
|
|
Mat4.scale(t, t, Vec3.set(sVec, side / 1.5, side , side / 2))
|
|
|
builder.addHexagonalPrism(t)
|
|
@@ -124,24 +110,6 @@ async function createCarbohydrateSymbolMesh(ctx: RuntimeContext, structure: Stru
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- for (let i = 0, il = carbohydrates.links.length; i < il; ++i) {
|
|
|
- const l = carbohydrates.links[i]
|
|
|
- const centerA = carbohydrates.elements[l.carbohydrateIndexA].geometry!.center
|
|
|
- const centerB = carbohydrates.elements[l.carbohydrateIndexB].geometry!.center
|
|
|
- builder.addCylinder(centerA, centerB, 0.5, linkParams)
|
|
|
- }
|
|
|
-
|
|
|
- for (let i = 0, il = carbohydrates.terminalLinks.length; i < il; ++i) {
|
|
|
- const tl = carbohydrates.terminalLinks[i]
|
|
|
- const center = carbohydrates.elements[tl.carbohydrateIndex].geometry!.center
|
|
|
- tl.elementUnit.conformation.position(tl.elementUnit.elements[tl.elementIndex], p)
|
|
|
- if (tl.fromCarbohydrate) {
|
|
|
- builder.addCylinder(center, p, 0.5, linkParams)
|
|
|
- } else {
|
|
|
- builder.addCylinder(p, center, 0.5, linkParams)
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
return builder.getMesh()
|
|
|
}
|
|
|
|
|
@@ -166,14 +134,14 @@ export function CarbohydrateSymbolVisual(): StructureVisual<CarbohydrateSymbolPr
|
|
|
currentProps = Object.assign({}, DefaultCarbohydrateSymbolProps, props)
|
|
|
currentStructure = structure
|
|
|
|
|
|
+ const { colorTheme } = { ...DefaultCarbohydrateSymbolProps, ...props }
|
|
|
const instanceCount = 1
|
|
|
const elementCount = currentStructure.elementCount
|
|
|
|
|
|
mesh = await createCarbohydrateSymbolMesh(ctx, currentStructure, mesh)
|
|
|
- // console.log(mesh)
|
|
|
|
|
|
const transforms = createIdentityTransform()
|
|
|
- const color = createValueColor(0x999911) // TODO
|
|
|
+ const color = createColors(createCarbohydrateIterator(structure), colorTheme)
|
|
|
const marker = createMarkers(instanceCount * elementCount)
|
|
|
|
|
|
const counts = { drawCount: mesh.triangleCount * 3, elementCount, instanceCount }
|
|
@@ -196,19 +164,87 @@ export function CarbohydrateSymbolVisual(): StructureVisual<CarbohydrateSymbolPr
|
|
|
|
|
|
if (!renderObject) return false
|
|
|
|
|
|
+ let updateColor = false
|
|
|
+
|
|
|
+ if (!deepEqual(newProps.colorTheme, currentProps.colorTheme)) {
|
|
|
+ updateColor = true
|
|
|
+ }
|
|
|
+
|
|
|
+ if (updateColor) {
|
|
|
+ createColors(createCarbohydrateIterator(currentStructure), newProps.colorTheme, renderObject.values)
|
|
|
+ }
|
|
|
+
|
|
|
updateMeshValues(renderObject.values, newProps)
|
|
|
updateRenderableState(renderObject.state, newProps)
|
|
|
|
|
|
+ currentProps = newProps
|
|
|
return false
|
|
|
},
|
|
|
getLoci(pickingId: PickingId) {
|
|
|
- return EmptyLoci
|
|
|
+ return getCarbohydrateLoci(pickingId, currentStructure, renderObject.id)
|
|
|
},
|
|
|
mark(loci: Loci, action: MarkerAction) {
|
|
|
- // TODO
|
|
|
+ markCarbohydrate(loci, action, currentStructure, renderObject.values)
|
|
|
},
|
|
|
destroy() {
|
|
|
// TODO
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+function createCarbohydrateIterator(structure: Structure): LocationIterator {
|
|
|
+ const carbs = structure.carbohydrates.elements
|
|
|
+ const elementCount = carbs.length
|
|
|
+ const instanceCount = 1
|
|
|
+ const location = StructureElement.create()
|
|
|
+ const getLocation = (elementIndex: number, instanceIndex: number) => {
|
|
|
+ const carb = carbs[elementIndex]
|
|
|
+ location.unit = carb.unit
|
|
|
+ location.element = carb.anomericCarbon
|
|
|
+ return location
|
|
|
+ }
|
|
|
+ return LocationIterator(elementCount, instanceCount, getLocation)
|
|
|
+}
|
|
|
+
|
|
|
+function getCarbohydrateLoci(pickingId: PickingId, structure: Structure, id: number) {
|
|
|
+ const { objectId, elementId } = pickingId
|
|
|
+ if (id === objectId) {
|
|
|
+ const carb = structure.carbohydrates.elements[elementId]
|
|
|
+ const { unit } = carb
|
|
|
+ const index = OrderedSet.findPredecessorIndex(unit.elements, carb.anomericCarbon)
|
|
|
+ const indices = OrderedSet.ofSingleton(index as StructureElement.UnitIndex)
|
|
|
+ return StructureElement.Loci([{ unit, indices }])
|
|
|
+ }
|
|
|
+ return EmptyLoci
|
|
|
+}
|
|
|
+
|
|
|
+function markCarbohydrate(loci: Loci, action: MarkerAction, structure: Structure, values: MarkerData) {
|
|
|
+ const tMarker = values.tMarker
|
|
|
+
|
|
|
+ const { byUnitAndElement } = structure.carbohydrates
|
|
|
+ const elementCount = structure.carbohydrates.elements.length
|
|
|
+
|
|
|
+ let changed = false
|
|
|
+ const array = tMarker.ref.value.array
|
|
|
+ if (isEveryLoci(loci)) {
|
|
|
+ if (applyMarkerAction(array, 0, elementCount, action)) {
|
|
|
+ changed = true
|
|
|
+ }
|
|
|
+ } else if (StructureElement.isLoci(loci)) {
|
|
|
+ for (const e of loci.elements) {
|
|
|
+ OrderedSet.forEach(e.indices, index => {
|
|
|
+ const idx = byUnitAndElement(e.unit, e.unit.elements[index])
|
|
|
+ if (idx !== undefined) {
|
|
|
+ if (applyMarkerAction(array, idx, idx + 1, action) && !changed) {
|
|
|
+ changed = true
|
|
|
+ }
|
|
|
+ }
|
|
|
+ })
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ return
|
|
|
+ }
|
|
|
+ if (changed) {
|
|
|
+ ValueCell.update(tMarker, tMarker.ref.value)
|
|
|
+ }
|
|
|
+}
|