|
@@ -6,7 +6,7 @@
|
|
|
*/
|
|
|
|
|
|
import { UniqueArray } from '../../../mol-data/generic';
|
|
|
-import { OrderedSet, SortedArray } from '../../../mol-data/int';
|
|
|
+import { OrderedSet, SortedArray, Interval } from '../../../mol-data/int';
|
|
|
import { BoundaryHelper } from '../../../mol-math/geometry/boundary-helper';
|
|
|
import { Vec3 } from '../../../mol-math/linear-algebra';
|
|
|
import { MolScriptBuilder as MS } from '../../../mol-script/language/builder';
|
|
@@ -16,8 +16,9 @@ import Structure from './structure';
|
|
|
import Unit from './unit';
|
|
|
import { Boundary } from './util/boundary';
|
|
|
import { StructureProperties } from '../structure';
|
|
|
-import { sortArray, hashFnv32a, hash2 } from '../../../mol-data/util';
|
|
|
+import { sortArray, hashFnv32a, hash2, hash3 } from '../../../mol-data/util';
|
|
|
import Expression from '../../../mol-script/language/expression';
|
|
|
+import SortedRanges from '../../../mol-data/int/sorted-ranges';
|
|
|
|
|
|
interface StructureElement<U = Unit> {
|
|
|
readonly kind: 'element-location',
|
|
@@ -115,7 +116,7 @@ namespace StructureElement {
|
|
|
|
|
|
export function entityIndex(l: StructureElement) {
|
|
|
return StructureProperties.entity.key(l)
|
|
|
- }
|
|
|
+ }
|
|
|
|
|
|
export namespace Loci {
|
|
|
export function size(loci: Loci) {
|
|
@@ -461,6 +462,177 @@ namespace StructureElement {
|
|
|
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+ interface QueryElement {
|
|
|
+ /** Array of `Unit.id`s that share the same `Unit.invariantId` */
|
|
|
+ units: SortedArray<number>,
|
|
|
+ set: SortedArray<UnitIndex>
|
|
|
+ ranges: SortedRanges<UnitIndex>
|
|
|
+ }
|
|
|
+ export interface Query {
|
|
|
+ readonly elements: ReadonlyArray<Readonly<QueryElement>>
|
|
|
+ }
|
|
|
+
|
|
|
+ export namespace Query {
|
|
|
+ export const Empty: Query = { elements: [] }
|
|
|
+
|
|
|
+ export function fromLoci(loci: StructureElement.Loci): Query {
|
|
|
+ const _elements: {
|
|
|
+ unit: Unit
|
|
|
+ set: SortedArray<UnitIndex>
|
|
|
+ ranges: SortedRanges<UnitIndex>
|
|
|
+ }[] = []
|
|
|
+ for (const e of loci.elements) {
|
|
|
+ const { unit, indices } = e
|
|
|
+
|
|
|
+ const ranges: UnitIndex[] = [];
|
|
|
+ const set: UnitIndex[] = [];
|
|
|
+
|
|
|
+ if (OrderedSet.isInterval(indices)) {
|
|
|
+ ranges[ranges.length] = Interval.min(indices)
|
|
|
+ ranges[ranges.length] = Interval.max(indices)
|
|
|
+ } else {
|
|
|
+ let i = 0, len = indices.length;
|
|
|
+ while (i < len) {
|
|
|
+ const start = i;
|
|
|
+ i++;
|
|
|
+ while (i < len && indices[i - 1] + 1 === indices[i]) i++;
|
|
|
+ const end = i;
|
|
|
+ // TODO: is this a good value?
|
|
|
+ if (end - start > 12) {
|
|
|
+ ranges[ranges.length] = indices[start];
|
|
|
+ ranges[ranges.length] = indices[end - 1];
|
|
|
+ } else {
|
|
|
+ for (let j = start; j < end; j++) {
|
|
|
+ set[set.length] = indices[j];
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ _elements.push({
|
|
|
+ unit,
|
|
|
+ set: SortedArray.ofSortedArray(set),
|
|
|
+ ranges: SortedRanges.ofSortedRanges(ranges)
|
|
|
+ })
|
|
|
+ }
|
|
|
+
|
|
|
+ const elementGroups = new Map<number, {
|
|
|
+ units: number[]
|
|
|
+ set: SortedArray<UnitIndex>
|
|
|
+ ranges: SortedRanges<UnitIndex>
|
|
|
+ }>();
|
|
|
+ for (let i = 0, il = _elements.length; i < il; ++i) {
|
|
|
+ const e = _elements[i]
|
|
|
+ const key = hash3(hashFnv32a(e.ranges), hashFnv32a(e.set), e.unit.invariantId)
|
|
|
+ if (elementGroups.has(key)) {
|
|
|
+ elementGroups.get(key)!.units.push(e.unit.id)
|
|
|
+ } else {
|
|
|
+ elementGroups.set(key, {
|
|
|
+ units: [e.unit.id],
|
|
|
+ set: e.set,
|
|
|
+ ranges: e.ranges
|
|
|
+ })
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ const elements: QueryElement[] = []
|
|
|
+ elementGroups.forEach(e => {
|
|
|
+ elements.push({
|
|
|
+ units: SortedArray.ofUnsortedArray(e.units),
|
|
|
+ set: e.set,
|
|
|
+ ranges: e.ranges
|
|
|
+ })
|
|
|
+ })
|
|
|
+
|
|
|
+ return { elements }
|
|
|
+ }
|
|
|
+
|
|
|
+ function getUnitsFromIds(unitIds: ArrayLike<number>, structure: Structure) {
|
|
|
+ const units: Unit[] = []
|
|
|
+ for (let i = 0, il = unitIds.length; i < il; ++i) {
|
|
|
+ const unitId = unitIds[i]
|
|
|
+ if (structure.unitMap.has(unitId)) units.push(structure.unitMap.get(unitId))
|
|
|
+ }
|
|
|
+ return units
|
|
|
+ }
|
|
|
+
|
|
|
+ export function toLoci(query: Query, parent: Structure): Loci {
|
|
|
+ const elements: Loci['elements'][0][] = []
|
|
|
+ for (const e of query.elements) {
|
|
|
+ const units = getUnitsFromIds(e.units, parent)
|
|
|
+ if (units.length === 0) continue
|
|
|
+
|
|
|
+ let indices: OrderedSet<UnitIndex>
|
|
|
+ if (e.ranges.length === 0) {
|
|
|
+ indices = e.set
|
|
|
+ } else if (e.set.length === 0) {
|
|
|
+ if (e.ranges.length === 2) {
|
|
|
+ indices = Interval.ofRange(e.ranges[0], e.ranges[1])
|
|
|
+ } else {
|
|
|
+ const _indices = new Int32Array(SortedRanges.size(e.ranges))
|
|
|
+ SortedRanges.forEach(e.ranges, (v, i) => _indices[i] = v)
|
|
|
+ indices = SortedArray.ofSortedArray(_indices)
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ const rangesSize = SortedRanges.size(e.ranges)
|
|
|
+ const _indices = new Int32Array(e.set.length + rangesSize)
|
|
|
+ SortedRanges.forEach(e.ranges, (v, i) => _indices[i] = v)
|
|
|
+ _indices.set(e.set, rangesSize)
|
|
|
+ indices = SortedArray.ofUnsortedArray(_indices) // requires sort
|
|
|
+ }
|
|
|
+
|
|
|
+ for (const unit of units) {
|
|
|
+ elements.push({ unit, indices })
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return Loci(parent, elements)
|
|
|
+ }
|
|
|
+
|
|
|
+ export function toStructure(query: Query, parent: Structure): Structure {
|
|
|
+ const units: Unit[] = []
|
|
|
+ for (const e of query.elements) {
|
|
|
+ const _units = getUnitsFromIds(e.units, parent)
|
|
|
+ if (_units.length === 0) continue
|
|
|
+
|
|
|
+ const unit = _units[0] // the elements are grouped by unit.invariantId
|
|
|
+ const rangesSize = SortedRanges.size(e.ranges)
|
|
|
+ const _indices = new Int32Array(e.set.length + rangesSize)
|
|
|
+ let indices: SortedArray<ElementIndex>
|
|
|
+ if (e.ranges.length === 0) {
|
|
|
+ for (let i = 0, il = e.set.length; i < il; ++i) {
|
|
|
+ _indices[i] = unit.elements[e.set[i]]
|
|
|
+ }
|
|
|
+ indices = SortedArray.ofSortedArray(_indices)
|
|
|
+ } else if (e.set.length === 0) {
|
|
|
+ SortedRanges.forEach(e.ranges, (v, i) => _indices[i] = unit.elements[v])
|
|
|
+ indices = SortedArray.ofSortedArray(_indices)
|
|
|
+ } else {
|
|
|
+ const rangesSize = SortedRanges.size(e.ranges)
|
|
|
+ SortedRanges.forEach(e.ranges, (v, i) => _indices[i] = unit.elements[v])
|
|
|
+ SortedRanges.forEach(e.set, (v, i) => _indices[i + rangesSize] = unit.elements[v])
|
|
|
+ indices = SortedArray.ofUnsortedArray(_indices) // requires sort
|
|
|
+ }
|
|
|
+
|
|
|
+ for (const unit of _units) {
|
|
|
+ units.push(unit.getChild(indices))
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return Structure.create(units, parent)
|
|
|
+ }
|
|
|
+
|
|
|
+ export function areEqual(a: Query, b: Query) {
|
|
|
+ if (a.elements.length !== b.elements.length) return false
|
|
|
+ for (let i = 0, il = a.elements.length; i < il; ++i) {
|
|
|
+ const elementA = a.elements[i]
|
|
|
+ const elementB = b.elements[i]
|
|
|
+ if (!SortedArray.areEqual(elementA.units, elementB.units)) return false
|
|
|
+ if (!SortedArray.areEqual(elementA.set, elementB.set)) return false
|
|
|
+ if (!SortedRanges.areEqual(elementA.ranges, elementB.ranges)) return false
|
|
|
+ }
|
|
|
+ return true
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
export default StructureElement
|