Explorar o código

Merge branch 'master' into mol-script

David Sehnal %!s(int64=6) %!d(string=hai) anos
pai
achega
8175430fe5
Modificáronse 100 ficheiros con 7890 adicións e 1274 borrados
  1. 25 3
      README.md
  2. 19 0
      data/mmcif-field-names.csv
  3. 18 0
      data/rcsb-graphql/codegen.js
  4. 8 0
      data/rcsb-graphql/codegen.json
  5. 1038 0
      examples/1crn.cif
  6. 422 271
      package-lock.json
  7. 31 26
      package.json
  8. 46 21
      src/apps/cif2bcif/converter.ts
  9. 174 3
      src/apps/cif2bcif/field-classifier.ts
  10. 4 4
      src/apps/domain-annotation-server/mapping.ts
  11. 0 37
      src/apps/domain-annotation-server/utils.ts
  12. 8 0
      src/apps/schema-generator/util/cif-dic.ts
  13. 143 71
      src/apps/structure-info/model.ts
  14. 25 0
      src/apps/viewer/index.tsx
  15. 3 0
      src/helpers.d.ts
  16. 7 1
      src/mol-app/context/context.ts
  17. 21 0
      src/mol-app/controller/visualization/sequence-view.ts
  18. 7 0
      src/mol-app/event/basic.ts
  19. 9 0
      src/mol-app/skin/components/sequence-view.scss
  20. 1 1
      src/mol-app/skin/layout/common.scss
  21. 2 1
      src/mol-app/skin/ui.scss
  22. 6 0
      src/mol-app/ui/controls/slider.tsx
  23. 15 3
      src/mol-app/ui/entity/tree.tsx
  24. 243 0
      src/mol-app/ui/transform/backbone.tsx
  25. 234 0
      src/mol-app/ui/transform/ball-and-stick.tsx
  26. 243 0
      src/mol-app/ui/transform/cartoon.tsx
  27. 234 0
      src/mol-app/ui/transform/distance-restraint.tsx
  28. 46 4
      src/mol-app/ui/transform/file-loader.tsx
  29. 15 0
      src/mol-app/ui/transform/list.tsx
  30. 33 2
      src/mol-app/ui/transform/spacefill.tsx
  31. 124 0
      src/mol-app/ui/transform/url-loader.tsx
  32. 6 0
      src/mol-app/ui/visualization/image-canvas.tsx
  33. 85 0
      src/mol-app/ui/visualization/sequence-view.tsx
  34. 23 5
      src/mol-app/ui/visualization/viewport.tsx
  35. 12 2
      src/mol-data/db/column.ts
  36. 13 0
      src/mol-data/db/table.ts
  37. 6 0
      src/mol-data/int/_spec/interval.spec.ts
  38. 25 0
      src/mol-data/int/_spec/ordered-set.spec.ts
  39. 2 2
      src/mol-data/int/_spec/segmentation.spec.ts
  40. 6 0
      src/mol-data/int/_spec/sorted-array.spec.ts
  41. 72 0
      src/mol-data/int/_spec/sorted-ranges.spec.ts
  42. 4 0
      src/mol-data/int/impl/interval.ts
  43. 31 1
      src/mol-data/int/impl/ordered-set.ts
  44. 18 19
      src/mol-data/int/impl/segmentation.ts
  45. 60 20
      src/mol-data/int/impl/sorted-array.ts
  46. 28 26
      src/mol-data/int/interval.ts
  47. 47 21
      src/mol-data/int/ordered-set.ts
  48. 14 10
      src/mol-data/int/segmentation.ts
  49. 38 33
      src/mol-data/int/sorted-array.ts
  50. 86 0
      src/mol-data/int/sorted-ranges.ts
  51. 1 0
      src/mol-data/util.ts
  52. 43 0
      src/mol-data/util/_spec/buckets.spec.ts
  53. 29 0
      src/mol-data/util/_spec/combination.spec.ts
  54. 33 0
      src/mol-data/util/_spec/interval-iterator.spec.ts
  55. 24 0
      src/mol-data/util/array.ts
  56. 104 0
      src/mol-data/util/buckets.ts
  57. 65 0
      src/mol-data/util/combination.ts
  58. 8 0
      src/mol-data/util/hash-functions.ts
  59. 45 0
      src/mol-data/util/interval-iterator.ts
  60. 45 77
      src/mol-geo/primitive/box.ts
  61. 7 7
      src/mol-geo/primitive/cylinder.ts
  62. 12 24
      src/mol-geo/primitive/icosahedron.ts
  63. 22 0
      src/mol-geo/primitive/octahedron.ts
  64. 36 0
      src/mol-geo/primitive/plane.ts
  65. 23 0
      src/mol-geo/primitive/polygon.ts
  66. 2 16
      src/mol-geo/primitive/polyhedron.ts
  67. 59 0
      src/mol-geo/primitive/primitive.ts
  68. 100 0
      src/mol-geo/primitive/prism.ts
  69. 60 0
      src/mol-geo/primitive/pyramid.ts
  70. 208 0
      src/mol-geo/primitive/sheet.ts
  71. 27 0
      src/mol-geo/primitive/sphere.ts
  72. 55 0
      src/mol-geo/primitive/star.ts
  73. 154 0
      src/mol-geo/primitive/tube.ts
  74. 122 0
      src/mol-geo/primitive/wedge.ts
  75. 19 5
      src/mol-geo/representation/index.ts
  76. 52 0
      src/mol-geo/representation/structure/backbone.ts
  77. 80 0
      src/mol-geo/representation/structure/ball-and-stick.ts
  78. 88 0
      src/mol-geo/representation/structure/cartoon.ts
  79. 55 0
      src/mol-geo/representation/structure/distance-restraint.ts
  80. 169 103
      src/mol-geo/representation/structure/index.ts
  81. 0 169
      src/mol-geo/representation/structure/point.ts
  82. 6 153
      src/mol-geo/representation/structure/spacefill.ts
  83. 0 133
      src/mol-geo/representation/structure/utils.ts
  84. 214 0
      src/mol-geo/representation/structure/visual/carbohydrate-symbol-mesh.ts
  85. 165 0
      src/mol-geo/representation/structure/visual/cross-link-restraint-cylinder.ts
  86. 141 0
      src/mol-geo/representation/structure/visual/element-point.ts
  87. 116 0
      src/mol-geo/representation/structure/visual/element-sphere.ts
  88. 158 0
      src/mol-geo/representation/structure/visual/inter-unit-link-cylinder.ts
  89. 179 0
      src/mol-geo/representation/structure/visual/intra-unit-link-cylinder.ts
  90. 206 0
      src/mol-geo/representation/structure/visual/nucleotide-block-mesh.ts
  91. 152 0
      src/mol-geo/representation/structure/visual/polymer-backbone-cylinder.ts
  92. 0 0
      src/mol-geo/representation/structure/visual/polymer-backbone-sphere.ts
  93. 184 0
      src/mol-geo/representation/structure/visual/polymer-direction-wedge.ts
  94. 158 0
      src/mol-geo/representation/structure/visual/polymer-gap-cylinder.ts
  95. 177 0
      src/mol-geo/representation/structure/visual/polymer-trace-mesh.ts
  96. 69 0
      src/mol-geo/representation/structure/visual/util/common.ts
  97. 103 0
      src/mol-geo/representation/structure/visual/util/element.ts
  98. 122 0
      src/mol-geo/representation/structure/visual/util/link.ts
  99. 69 0
      src/mol-geo/representation/structure/visual/util/polymer.ts
  100. 144 0
      src/mol-geo/representation/structure/visual/util/polymer/backbone-iterator.ts

+ 25 - 3
README.md

@@ -1,5 +1,6 @@
 [![License](http://img.shields.io/badge/license-MIT-blue.svg?style=flat)](./LICENSE)
 [![License](http://img.shields.io/badge/license-MIT-blue.svg?style=flat)](./LICENSE)
-[![Build Status](https://travis-ci.org/mol-star/mol-star-proto.svg?branch=master)](https://travis-ci.org/mol-star/mol-star-proto)
+[![Build Status](https://travis-ci.org/molstar/molstar-proto.svg?branch=master)](https://travis-ci.org/mol-star/mol-star-proto)
+[![Gitter](https://badges.gitter.im/molstar/Lobby.svg)](https://gitter.im/molstar/Lobby)
 
 
 # Mol*
 # Mol*
 
 
@@ -51,19 +52,40 @@ This project builds on experience from previous solutions:
     npm run watch-extra
     npm run watch-extra
 
 
 ### Build/watch mol-viewer
 ### Build/watch mol-viewer
-Build:
+**Build**
 
 
     npm run build
     npm run build
     npm run build-viewer
     npm run build-viewer
 
 
-Watch:
+**Watch**
 
 
     npm run watch
     npm run watch
     npm run watch-extra
     npm run watch-extra
     npm run watch-viewer
     npm run watch-viewer
 
 
+**Run**
+
+If not installed previously:
+
+    npm install -g http-server
+
+...or a similar solution.
+
+From the root of the project:
+
+    http-server -p PORT-NUMBER
+
+and navigate to `build/viewer`
+
+
+
+
 ## Contributing
 ## Contributing
 Just open an issue or make a pull request. All contributions are welcome.
 Just open an issue or make a pull request. All contributions are welcome.
 
 
 ## Roadmap
 ## Roadmap
 Continually develop this prototype project. As individual modules become stable, make them into standalone libraries.
 Continually develop this prototype project. As individual modules become stable, make them into standalone libraries.
+
+## Funding
+Funding sources include but are not limted to:
+* [RCSB PDB](https://www.rcsb.org) funding by a grant [DBI-1338415; PI: SK Burley] from the NSF, the NIH, and the US DoE

+ 19 - 0
data/mmcif-field-names.csv

@@ -62,6 +62,15 @@ entity.pdbx_mutation
 entity.pdbx_fragment
 entity.pdbx_fragment
 entity.pdbx_ec
 entity.pdbx_ec
 
 
+entity_poly.entity_id
+entity_poly.type
+entity_poly.nstd_linkage
+entity_poly.nstd_monomer
+entity_poly.pdbx_seq_one_letter_code  
+entity_poly.pdbx_seq_one_letter_code_can 
+entity_poly.pdbx_strand_id
+entity_poly.pdbx_target_identifier
+
 entity_poly_seq.entity_id
 entity_poly_seq.entity_id
 entity_poly_seq.num
 entity_poly_seq.num
 entity_poly_seq.mon_id
 entity_poly_seq.mon_id
@@ -144,6 +153,12 @@ struct_keywords.entry_id
 struct_keywords.pdbx_keywords
 struct_keywords.pdbx_keywords
 struct_keywords.text
 struct_keywords.text
 
 
+struct_ncs_oper.id
+struct_ncs_oper.code
+struct_ncs_oper.matrix
+struct_ncs_oper.vector
+struct_ncs_oper.details
+
 struct_sheet_range.sheet_id
 struct_sheet_range.sheet_id
 struct_sheet_range.id
 struct_sheet_range.id
 struct_sheet_range.beg_label_comp_id
 struct_sheet_range.beg_label_comp_id
@@ -359,10 +374,12 @@ ihm_cross_link_restraint.group_id
 ihm_cross_link_restraint.entity_id_1
 ihm_cross_link_restraint.entity_id_1
 ihm_cross_link_restraint.asym_id_1
 ihm_cross_link_restraint.asym_id_1
 ihm_cross_link_restraint.seq_id_1
 ihm_cross_link_restraint.seq_id_1
+ihm_cross_link_restraint.atom_id_1
 ihm_cross_link_restraint.comp_id_1
 ihm_cross_link_restraint.comp_id_1
 ihm_cross_link_restraint.entity_id_2
 ihm_cross_link_restraint.entity_id_2
 ihm_cross_link_restraint.asym_id_2
 ihm_cross_link_restraint.asym_id_2
 ihm_cross_link_restraint.seq_id_2
 ihm_cross_link_restraint.seq_id_2
+ihm_cross_link_restraint.atom_id_2
 ihm_cross_link_restraint.comp_id_2
 ihm_cross_link_restraint.comp_id_2
 ihm_cross_link_restraint.restraint_type
 ihm_cross_link_restraint.restraint_type
 ihm_cross_link_restraint.conditional_crosslink_flag
 ihm_cross_link_restraint.conditional_crosslink_flag
@@ -428,6 +445,7 @@ ihm_3dem_restraint.model_id
 ihm_3dem_restraint.cross_correlation_coefficient
 ihm_3dem_restraint.cross_correlation_coefficient
 
 
 ihm_predicted_contact_restraint.id
 ihm_predicted_contact_restraint.id
+ihm_predicted_contact_restraint.group_id
 ihm_predicted_contact_restraint.entity_id_1
 ihm_predicted_contact_restraint.entity_id_1
 ihm_predicted_contact_restraint.asym_id_1
 ihm_predicted_contact_restraint.asym_id_1
 ihm_predicted_contact_restraint.seq_id_1
 ihm_predicted_contact_restraint.seq_id_1
@@ -439,6 +457,7 @@ ihm_predicted_contact_restraint.seq_id_2
 ihm_predicted_contact_restraint.comp_id_2
 ihm_predicted_contact_restraint.comp_id_2
 ihm_predicted_contact_restraint.atom_id_2
 ihm_predicted_contact_restraint.atom_id_2
 ihm_predicted_contact_restraint.restraint_type
 ihm_predicted_contact_restraint.restraint_type
+ihm_predicted_contact_restraint.distance_lower_limit
 ihm_predicted_contact_restraint.distance_upper_limit
 ihm_predicted_contact_restraint.distance_upper_limit
 ihm_predicted_contact_restraint.probability
 ihm_predicted_contact_restraint.probability
 ihm_predicted_contact_restraint.model_granularity
 ihm_predicted_contact_restraint.model_granularity

+ 18 - 0
data/rcsb-graphql/codegen.js

@@ -0,0 +1,18 @@
+const { generate } = require('graphql-code-generator')
+const path = require('path')
+
+const basePath = path.join(__dirname, '..', '..', 'src', 'servers', 'model', 'properties', 'rcsb', 'graphql')
+
+generate({
+    args: [
+        path.join(basePath, 'symmetry.gql.ts')
+    ],
+    schema: 'http://rest-experimental.rcsb.org/graphql',
+    template: 'typescript',
+    out: path.join(basePath),
+    skipSchema: true,
+    overwrite: true,
+    config: path.join(__dirname, 'codegen.json')
+}, true).then(
+    console.log('done')
+).catch(e => console.error(e))

+ 8 - 0
data/rcsb-graphql/codegen.json

@@ -0,0 +1,8 @@
+{
+    "flattenTypes": false,
+    "generatorConfig": {
+        "printTime": true,
+        "immutableTypes": true,
+        "resolvers": false
+    }
+}

+ 1038 - 0
examples/1crn.cif

@@ -0,0 +1,1038 @@
+data_1CRN
+# 
+_entry.id   1CRN 
+# 
+_audit_conform.dict_name       mmcif_pdbx.dic 
+_audit_conform.dict_version    4.024 
+_audit_conform.dict_location   http://mmcif.pdb.org/dictionaries/ascii/mmcif_pdbx.dic 
+# 
+_database_2.database_id     PDB 
+_database_2.database_code   1CRN 
+# 
+loop_
+_database_PDB_rev.num 
+_database_PDB_rev.date 
+_database_PDB_rev.date_original 
+_database_PDB_rev.status 
+_database_PDB_rev.replaces 
+_database_PDB_rev.mod_type 
+1 1981-07-28 1981-04-30 ? 1CRN 0 
+2 1981-12-03 ?          ? 1CRN 1 
+3 1983-09-30 ?          ? 1CRN 1 
+4 1985-03-04 ?          ? 1CRN 1 
+5 1987-04-16 ?          ? 1CRN 1 
+6 2009-02-24 ?          ? 1CRN 1 
+7 2012-07-11 ?          ? 1CRN 1 
+# 
+loop_
+_database_PDB_rev_record.rev_num 
+_database_PDB_rev_record.type 
+_database_PDB_rev_record.details 
+2 SHEET  ? 
+3 REVDAT ? 
+4 REMARK ? 
+5 HEADER ? 
+6 VERSN  ? 
+7 SCALE1 ? 
+7 VERSN  ? 
+7 HEADER ? 
+# 
+_pdbx_database_status.status_code                    REL 
+_pdbx_database_status.entry_id                       1CRN 
+_pdbx_database_status.deposit_site                   ? 
+_pdbx_database_status.process_site                   ? 
+_pdbx_database_status.SG_entry                       . 
+_pdbx_database_status.status_code_sf                 ? 
+_pdbx_database_status.status_code_mr                 ? 
+_pdbx_database_status.status_code_cs                 ? 
+_pdbx_database_status.methods_development_category   ? 
+# 
+loop_
+_audit_author.name 
+_audit_author.pdbx_ordinal 
+'Hendrickson, W.A.' 1 
+'Teeter, M.M.'      2 
+# 
+loop_
+_citation.id 
+_citation.title 
+_citation.journal_abbrev 
+_citation.journal_volume 
+_citation.page_first 
+_citation.page_last 
+_citation.year 
+_citation.journal_id_ASTM 
+_citation.country 
+_citation.journal_id_ISSN 
+_citation.journal_id_CSD 
+_citation.book_publisher 
+_citation.pdbx_database_id_PubMed 
+_citation.pdbx_database_id_DOI 
+primary 'Water structure of a hydrophobic protein at atomic resolution: Pentagon rings of water molecules in crystals of crambin.' 
+Proc.Natl.Acad.Sci.Usa 81  6014 6018 1984 PNASA6 US 0027-8424 0040 ? 16593516 10.1073/pnas.81.19.6014 
+1       'Structure of the Hydrophobic Protein Crambin Determined Directly from the Anomalous Scattering of Sulphur'                
+Nature                 290 107  ?    1981 NATUAS UK 0028-0836 0006 ? ?        ?                       
+2       'Highly Ordered Crystals of the Plant Seed Protein Crambin'                                                                
+J.Mol.Biol.            127 219  ?    1979 JMOBAK UK 0022-2836 0070 ? ?        ?                       
+# 
+loop_
+_citation_author.citation_id 
+_citation_author.name 
+_citation_author.ordinal 
+primary 'Teeter, M.M.'      1 
+1       'Hendrickson, W.A.' 2 
+1       'Teeter, M.M.'      3 
+2       'Teeter, M.M.'      4 
+2       'Hendrickson, W.A.' 5 
+# 
+_cell.entry_id           1CRN 
+_cell.length_a           40.960 
+_cell.length_b           18.650 
+_cell.length_c           22.520 
+_cell.angle_alpha        90.00 
+_cell.angle_beta         90.77 
+_cell.angle_gamma        90.00 
+_cell.Z_PDB              2 
+_cell.pdbx_unique_axis   ? 
+_cell.length_a_esd       ? 
+_cell.length_b_esd       ? 
+_cell.length_c_esd       ? 
+_cell.angle_alpha_esd    ? 
+_cell.angle_beta_esd     ? 
+_cell.angle_gamma_esd    ? 
+# 
+_symmetry.entry_id                         1CRN 
+_symmetry.space_group_name_H-M             'P 1 21 1' 
+_symmetry.pdbx_full_space_group_name_H-M   ? 
+_symmetry.cell_setting                     ? 
+_symmetry.Int_Tables_number                ? 
+_symmetry.space_group_name_Hall            ? 
+# 
+_entity.id                         1 
+_entity.type                       polymer 
+_entity.src_method                 man 
+_entity.pdbx_description           CRAMBIN 
+_entity.formula_weight             4738.464 
+_entity.pdbx_number_of_molecules   1 
+_entity.details                    ? 
+# 
+_entity_poly.entity_id                      1 
+_entity_poly.type                           'polypeptide(L)' 
+_entity_poly.nstd_linkage                   no 
+_entity_poly.nstd_monomer                   no 
+_entity_poly.pdbx_seq_one_letter_code       TTCCPSIVARSNFNVCRLPGTPEAICATYTGCIIIPGATCPGDYAN 
+_entity_poly.pdbx_seq_one_letter_code_can   TTCCPSIVARSNFNVCRLPGTPEAICATYTGCIIIPGATCPGDYAN 
+_entity_poly.pdbx_strand_id                 A 
+# 
+loop_
+_entity_poly_seq.entity_id 
+_entity_poly_seq.num 
+_entity_poly_seq.mon_id 
+_entity_poly_seq.hetero 
+1 1  THR n 
+1 2  THR n 
+1 3  CYS n 
+1 4  CYS n 
+1 5  PRO n 
+1 6  SER n 
+1 7  ILE n 
+1 8  VAL n 
+1 9  ALA n 
+1 10 ARG n 
+1 11 SER n 
+1 12 ASN n 
+1 13 PHE n 
+1 14 ASN n 
+1 15 VAL n 
+1 16 CYS n 
+1 17 ARG n 
+1 18 LEU n 
+1 19 PRO n 
+1 20 GLY n 
+1 21 THR n 
+1 22 PRO n 
+1 23 GLU n 
+1 24 ALA n 
+1 25 ILE n 
+1 26 CYS n 
+1 27 ALA n 
+1 28 THR n 
+1 29 TYR n 
+1 30 THR n 
+1 31 GLY n 
+1 32 CYS n 
+1 33 ILE n 
+1 34 ILE n 
+1 35 ILE n 
+1 36 PRO n 
+1 37 GLY n 
+1 38 ALA n 
+1 39 THR n 
+1 40 CYS n 
+1 41 PRO n 
+1 42 GLY n 
+1 43 ASP n 
+1 44 TYR n 
+1 45 ALA n 
+1 46 ASN n 
+# 
+_entity_src_gen.entity_id                          1 
+_entity_src_gen.gene_src_common_name               ? 
+_entity_src_gen.gene_src_genus                     Crambe 
+_entity_src_gen.pdbx_gene_src_gene                 ? 
+_entity_src_gen.gene_src_species                   'Crambe hispanica' 
+_entity_src_gen.gene_src_strain                    'subsp. abyssinica' 
+_entity_src_gen.gene_src_tissue                    ? 
+_entity_src_gen.gene_src_tissue_fraction           ? 
+_entity_src_gen.gene_src_details                   ? 
+_entity_src_gen.pdbx_gene_src_fragment             ? 
+_entity_src_gen.pdbx_gene_src_scientific_name      'Crambe hispanica subsp. abyssinica' 
+_entity_src_gen.pdbx_gene_src_ncbi_taxonomy_id     3721 
+_entity_src_gen.pdbx_gene_src_variant              ? 
+_entity_src_gen.pdbx_gene_src_cell_line            ? 
+_entity_src_gen.pdbx_gene_src_atcc                 ? 
+_entity_src_gen.pdbx_gene_src_organ                ? 
+_entity_src_gen.pdbx_gene_src_organelle            ? 
+_entity_src_gen.pdbx_gene_src_cell                 ? 
+_entity_src_gen.pdbx_gene_src_cellular_location    ? 
+_entity_src_gen.host_org_common_name               ? 
+_entity_src_gen.pdbx_host_org_scientific_name      ? 
+_entity_src_gen.pdbx_host_org_ncbi_taxonomy_id     ? 
+_entity_src_gen.host_org_genus                     ? 
+_entity_src_gen.pdbx_host_org_gene                 ? 
+_entity_src_gen.pdbx_host_org_organ                ? 
+_entity_src_gen.host_org_species                   ? 
+_entity_src_gen.pdbx_host_org_tissue               ? 
+_entity_src_gen.pdbx_host_org_tissue_fraction      ? 
+_entity_src_gen.pdbx_host_org_strain               ? 
+_entity_src_gen.pdbx_host_org_variant              ? 
+_entity_src_gen.pdbx_host_org_cell_line            ? 
+_entity_src_gen.pdbx_host_org_atcc                 ? 
+_entity_src_gen.pdbx_host_org_culture_collection   ? 
+_entity_src_gen.pdbx_host_org_cell                 ? 
+_entity_src_gen.pdbx_host_org_organelle            ? 
+_entity_src_gen.pdbx_host_org_cellular_location    ? 
+_entity_src_gen.pdbx_host_org_vector_type          ? 
+_entity_src_gen.pdbx_host_org_vector               ? 
+_entity_src_gen.plasmid_name                       ? 
+_entity_src_gen.plasmid_details                    ? 
+_entity_src_gen.pdbx_description                   ? 
+# 
+_struct_ref.id                         1 
+_struct_ref.db_name                    UNP 
+_struct_ref.db_code                    CRAM_CRAAB 
+_struct_ref.entity_id                  1 
+_struct_ref.pdbx_db_accession          P01542 
+_struct_ref.pdbx_align_begin           1 
+_struct_ref.pdbx_seq_one_letter_code   TTCCPSIVARSNFNVCRLPGTPEAICATYTGCIIIPGATCPGDYAN 
+_struct_ref.biol_id                    . 
+# 
+_struct_ref_seq.align_id                      1 
+_struct_ref_seq.ref_id                        1 
+_struct_ref_seq.pdbx_PDB_id_code              1CRN 
+_struct_ref_seq.pdbx_strand_id                A 
+_struct_ref_seq.seq_align_beg                 1 
+_struct_ref_seq.pdbx_seq_align_beg_ins_code   ? 
+_struct_ref_seq.seq_align_end                 46 
+_struct_ref_seq.pdbx_seq_align_end_ins_code   ? 
+_struct_ref_seq.pdbx_db_accession             P01542 
+_struct_ref_seq.db_align_beg                  1 
+_struct_ref_seq.pdbx_db_align_beg_ins_code    ? 
+_struct_ref_seq.db_align_end                  46 
+_struct_ref_seq.pdbx_db_align_end_ins_code    ? 
+_struct_ref_seq.pdbx_auth_seq_align_beg       1 
+_struct_ref_seq.pdbx_auth_seq_align_end       46 
+# 
+loop_
+_chem_comp.id 
+_chem_comp.type 
+_chem_comp.mon_nstd_flag 
+_chem_comp.name 
+_chem_comp.pdbx_synonyms 
+_chem_comp.formula 
+_chem_comp.formula_weight 
+THR 'L-peptide linking' y THREONINE       ? 'C4 H9 N O3'     119.120 
+CYS 'L-peptide linking' y CYSTEINE        ? 'C3 H7 N O2 S'   121.154 
+PRO 'L-peptide linking' y PROLINE         ? 'C5 H9 N O2'     115.132 
+SER 'L-peptide linking' y SERINE          ? 'C3 H7 N O3'     105.093 
+ILE 'L-peptide linking' y ISOLEUCINE      ? 'C6 H13 N O2'    131.174 
+VAL 'L-peptide linking' y VALINE          ? 'C5 H11 N O2'    117.147 
+ALA 'L-peptide linking' y ALANINE         ? 'C3 H7 N O2'     89.094  
+ARG 'L-peptide linking' y ARGININE        ? 'C6 H15 N4 O2 1' 175.210 
+ASN 'L-peptide linking' y ASPARAGINE      ? 'C4 H8 N2 O3'    132.119 
+PHE 'L-peptide linking' y PHENYLALANINE   ? 'C9 H11 N O2'    165.191 
+LEU 'L-peptide linking' y LEUCINE         ? 'C6 H13 N O2'    131.174 
+GLY 'PEPTIDE LINKING'   y GLYCINE         ? 'C2 H5 N O2'     75.067  
+GLU 'L-peptide linking' y 'GLUTAMIC ACID' ? 'C5 H9 N O4'     147.130 
+TYR 'L-peptide linking' y TYROSINE        ? 'C9 H11 N O3'    181.191 
+ASP 'L-peptide linking' y 'ASPARTIC ACID' ? 'C4 H7 N O4'     133.104 
+# 
+_exptl.entry_id          1CRN 
+_exptl.method            'X-RAY DIFFRACTION' 
+_exptl.crystals_number   ? 
+# 
+_exptl_crystal.id                    1 
+_exptl_crystal.density_meas          ? 
+_exptl_crystal.density_Matthews      1.81 
+_exptl_crystal.density_percent_sol   32.16 
+_exptl_crystal.description           ? 
+_exptl_crystal.F_000                 ? 
+_exptl_crystal.preparation           ? 
+# 
+_diffrn.id                     1 
+_diffrn.ambient_temp           ? 
+_diffrn.ambient_temp_details   ? 
+_diffrn.crystal_id             1 
+# 
+_diffrn_radiation.diffrn_id                        1 
+_diffrn_radiation.wavelength_id                    1 
+_diffrn_radiation.pdbx_monochromatic_or_laue_m_l   ? 
+_diffrn_radiation.monochromator                    ? 
+_diffrn_radiation.pdbx_diffrn_protocol             ? 
+_diffrn_radiation.pdbx_scattering_type             x-ray 
+# 
+_diffrn_radiation_wavelength.id           1 
+_diffrn_radiation_wavelength.wavelength   . 
+_diffrn_radiation_wavelength.wt           1.0 
+# 
+_computing.entry_id                           1CRN 
+_computing.pdbx_data_reduction_ii             ? 
+_computing.pdbx_data_reduction_ds             ? 
+_computing.data_collection                    ? 
+_computing.structure_solution                 ? 
+_computing.structure_refinement               PROLSQ 
+_computing.pdbx_structure_refinement_method   ? 
+# 
+_refine.entry_id                               1CRN 
+_refine.ls_number_reflns_obs                   ? 
+_refine.ls_number_reflns_all                   ? 
+_refine.pdbx_ls_sigma_I                        ? 
+_refine.pdbx_ls_sigma_F                        ? 
+_refine.pdbx_data_cutoff_high_absF             ? 
+_refine.pdbx_data_cutoff_low_absF              ? 
+_refine.pdbx_data_cutoff_high_rms_absF         ? 
+_refine.ls_d_res_low                           ? 
+_refine.ls_d_res_high                          1.5 
+_refine.ls_percent_reflns_obs                  ? 
+_refine.ls_R_factor_obs                        ? 
+_refine.ls_R_factor_all                        ? 
+_refine.ls_R_factor_R_work                     ? 
+_refine.ls_R_factor_R_free                     ? 
+_refine.ls_R_factor_R_free_error               ? 
+_refine.ls_R_factor_R_free_error_details       ? 
+_refine.ls_percent_reflns_R_free               ? 
+_refine.ls_number_reflns_R_free                ? 
+_refine.ls_number_parameters                   ? 
+_refine.ls_number_restraints                   ? 
+_refine.occupancy_min                          ? 
+_refine.occupancy_max                          ? 
+_refine.B_iso_mean                             ? 
+_refine.aniso_B[1][1]                          ? 
+_refine.aniso_B[2][2]                          ? 
+_refine.aniso_B[3][3]                          ? 
+_refine.aniso_B[1][2]                          ? 
+_refine.aniso_B[1][3]                          ? 
+_refine.aniso_B[2][3]                          ? 
+_refine.solvent_model_details                  ? 
+_refine.solvent_model_param_ksol               ? 
+_refine.solvent_model_param_bsol               ? 
+_refine.pdbx_ls_cross_valid_method             ? 
+_refine.details                                ? 
+_refine.pdbx_starting_model                    ? 
+_refine.pdbx_method_to_determine_struct        ? 
+_refine.pdbx_isotropic_thermal_model           ? 
+_refine.pdbx_stereochemistry_target_values     ? 
+_refine.pdbx_stereochem_target_val_spec_case   ? 
+_refine.pdbx_R_Free_selection_details          ? 
+_refine.pdbx_overall_ESU_R                     ? 
+_refine.pdbx_overall_ESU_R_Free                ? 
+_refine.overall_SU_ML                          ? 
+_refine.overall_SU_B                           ? 
+_refine.pdbx_refine_id                         'X-RAY DIFFRACTION' 
+_refine.pdbx_diffrn_id                         1 
+_refine.ls_redundancy_reflns_obs               ? 
+_refine.pdbx_overall_phase_error               ? 
+_refine.B_iso_min                              ? 
+_refine.B_iso_max                              ? 
+_refine.correlation_coeff_Fo_to_Fc             ? 
+_refine.correlation_coeff_Fo_to_Fc_free        ? 
+_refine.pdbx_solvent_vdw_probe_radii           ? 
+_refine.pdbx_solvent_ion_probe_radii           ? 
+_refine.pdbx_solvent_shrinkage_radii           ? 
+_refine.overall_SU_R_Cruickshank_DPI           ? 
+_refine.overall_SU_R_free                      ? 
+_refine.ls_wR_factor_R_free                    ? 
+_refine.ls_wR_factor_R_work                    ? 
+_refine.overall_FOM_free_R_set                 ? 
+_refine.overall_FOM_work_R_set                 ? 
+# 
+_refine_hist.pdbx_refine_id                   'X-RAY DIFFRACTION' 
+_refine_hist.cycle_id                         LAST 
+_refine_hist.pdbx_number_atoms_protein        327 
+_refine_hist.pdbx_number_atoms_nucleic_acid   0 
+_refine_hist.pdbx_number_atoms_ligand         0 
+_refine_hist.number_atoms_solvent             0 
+_refine_hist.number_atoms_total               327 
+_refine_hist.d_res_high                       1.5 
+_refine_hist.d_res_low                        . 
+# 
+_struct.entry_id                  1CRN 
+_struct.title                     
+'WATER STRUCTURE OF A HYDROPHOBIC PROTEIN AT ATOMIC RESOLUTION. PENTAGON RINGS OF WATER MOLECULES IN CRYSTALS OF CRAMBIN' 
+_struct.pdbx_descriptor           CRAMBIN 
+_struct.pdbx_model_details        ? 
+_struct.pdbx_CASP_flag            ? 
+_struct.pdbx_model_type_details   ? 
+# 
+_struct_keywords.entry_id        1CRN 
+_struct_keywords.pdbx_keywords   'PLANT PROTEIN' 
+_struct_keywords.text            'PLANT SEED PROTEIN, PLANT PROTEIN' 
+# 
+_struct_asym.id                            A 
+_struct_asym.pdbx_blank_PDB_chainid_flag   N 
+_struct_asym.pdbx_modified                 N 
+_struct_asym.entity_id                     1 
+_struct_asym.details                       ? 
+# 
+_struct_biol.id        1 
+_struct_biol.details   ? 
+# 
+loop_
+_struct_conf.conf_type_id 
+_struct_conf.id 
+_struct_conf.pdbx_PDB_helix_id 
+_struct_conf.beg_label_comp_id 
+_struct_conf.beg_label_asym_id 
+_struct_conf.beg_label_seq_id 
+_struct_conf.pdbx_beg_PDB_ins_code 
+_struct_conf.end_label_comp_id 
+_struct_conf.end_label_asym_id 
+_struct_conf.end_label_seq_id 
+_struct_conf.pdbx_end_PDB_ins_code 
+_struct_conf.beg_auth_comp_id 
+_struct_conf.beg_auth_asym_id 
+_struct_conf.beg_auth_seq_id 
+_struct_conf.end_auth_comp_id 
+_struct_conf.end_auth_asym_id 
+_struct_conf.end_auth_seq_id 
+_struct_conf.pdbx_PDB_helix_class 
+_struct_conf.details 
+_struct_conf.pdbx_PDB_helix_length 
+HELX_P HELX_P1 H1 ILE A 7  ? PRO A 19 ? ILE A 7  PRO A 19 1 '3/10 CONFORMATION RES 17,19' 13 
+HELX_P HELX_P2 H2 GLU A 23 ? THR A 30 ? GLU A 23 THR A 30 1 'DISTORTED 3/10 AT RES 30'    8  
+TURN_P TURN_P1 T1 PRO A 41 ? TYR A 44 ? PRO A 41 TYR A 44 ? ?                             ?  
+# 
+loop_
+_struct_conf_type.id 
+_struct_conf_type.criteria 
+_struct_conf_type.reference 
+HELX_P ? ? 
+TURN_P ? ? 
+# 
+loop_
+_struct_conn.id 
+_struct_conn.conn_type_id 
+_struct_conn.pdbx_PDB_id 
+_struct_conn.ptnr1_label_asym_id 
+_struct_conn.ptnr1_label_comp_id 
+_struct_conn.ptnr1_label_seq_id 
+_struct_conn.ptnr1_label_atom_id 
+_struct_conn.pdbx_ptnr1_label_alt_id 
+_struct_conn.pdbx_ptnr1_PDB_ins_code 
+_struct_conn.pdbx_ptnr1_standard_comp_id 
+_struct_conn.ptnr1_symmetry 
+_struct_conn.ptnr2_label_asym_id 
+_struct_conn.ptnr2_label_comp_id 
+_struct_conn.ptnr2_label_seq_id 
+_struct_conn.ptnr2_label_atom_id 
+_struct_conn.pdbx_ptnr2_label_alt_id 
+_struct_conn.pdbx_ptnr2_PDB_ins_code 
+_struct_conn.ptnr1_auth_asym_id 
+_struct_conn.ptnr1_auth_comp_id 
+_struct_conn.ptnr1_auth_seq_id 
+_struct_conn.ptnr2_auth_asym_id 
+_struct_conn.ptnr2_auth_comp_id 
+_struct_conn.ptnr2_auth_seq_id 
+_struct_conn.ptnr2_symmetry 
+_struct_conn.pdbx_ptnr3_label_atom_id 
+_struct_conn.pdbx_ptnr3_label_seq_id 
+_struct_conn.pdbx_ptnr3_label_comp_id 
+_struct_conn.pdbx_ptnr3_label_asym_id 
+_struct_conn.pdbx_ptnr3_label_alt_id 
+_struct_conn.pdbx_ptnr3_PDB_ins_code 
+_struct_conn.details 
+_struct_conn.pdbx_dist_value 
+_struct_conn.pdbx_value_order 
+disulf1 disulf ? A CYS 3  SG ? ? ? 1_555 A CYS 40 SG ? ? A CYS 3  A CYS 40 1_555 ? ? ? ? ? ? ? 2.004 ? 
+disulf2 disulf ? A CYS 4  SG ? ? ? 1_555 A CYS 32 SG ? ? A CYS 4  A CYS 32 1_555 ? ? ? ? ? ? ? 2.035 ? 
+disulf3 disulf ? A CYS 16 SG ? ? ? 1_555 A CYS 26 SG ? ? A CYS 16 A CYS 26 1_555 ? ? ? ? ? ? ? 2.051 ? 
+# 
+_struct_conn_type.id          disulf 
+_struct_conn_type.criteria    ? 
+_struct_conn_type.reference   ? 
+# 
+_struct_sheet.id               S1 
+_struct_sheet.type             ? 
+_struct_sheet.number_strands   2 
+_struct_sheet.details          ? 
+# 
+_struct_sheet_order.sheet_id     S1 
+_struct_sheet_order.range_id_1   1 
+_struct_sheet_order.range_id_2   2 
+_struct_sheet_order.offset       ? 
+_struct_sheet_order.sense        anti-parallel 
+# 
+loop_
+_struct_sheet_range.sheet_id 
+_struct_sheet_range.id 
+_struct_sheet_range.beg_label_comp_id 
+_struct_sheet_range.beg_label_asym_id 
+_struct_sheet_range.beg_label_seq_id 
+_struct_sheet_range.pdbx_beg_PDB_ins_code 
+_struct_sheet_range.end_label_comp_id 
+_struct_sheet_range.end_label_asym_id 
+_struct_sheet_range.end_label_seq_id 
+_struct_sheet_range.pdbx_end_PDB_ins_code 
+_struct_sheet_range.symmetry 
+_struct_sheet_range.beg_auth_comp_id 
+_struct_sheet_range.beg_auth_asym_id 
+_struct_sheet_range.beg_auth_seq_id 
+_struct_sheet_range.end_auth_comp_id 
+_struct_sheet_range.end_auth_asym_id 
+_struct_sheet_range.end_auth_seq_id 
+S1 1 THR A 1  ? CYS A 4  ? ? THR A 1  CYS A 4  
+S1 2 CYS A 32 ? ILE A 35 ? ? CYS A 32 ILE A 35 
+# 
+_database_PDB_matrix.entry_id          1CRN 
+_database_PDB_matrix.origx[1][1]       1.000000 
+_database_PDB_matrix.origx[1][2]       0.000000 
+_database_PDB_matrix.origx[1][3]       0.000000 
+_database_PDB_matrix.origx[2][1]       0.000000 
+_database_PDB_matrix.origx[2][2]       1.000000 
+_database_PDB_matrix.origx[2][3]       0.000000 
+_database_PDB_matrix.origx[3][1]       0.000000 
+_database_PDB_matrix.origx[3][2]       0.000000 
+_database_PDB_matrix.origx[3][3]       1.000000 
+_database_PDB_matrix.origx_vector[1]   0.00000 
+_database_PDB_matrix.origx_vector[2]   0.00000 
+_database_PDB_matrix.origx_vector[3]   0.00000 
+# 
+_atom_sites.entry_id                    1CRN 
+_atom_sites.Cartn_transform_axes        ? 
+_atom_sites.fract_transf_matrix[1][1]   0.024414 
+_atom_sites.fract_transf_matrix[1][2]   0.000000 
+_atom_sites.fract_transf_matrix[1][3]   0.000328 
+_atom_sites.fract_transf_matrix[2][1]   0.000000 
+_atom_sites.fract_transf_matrix[2][2]   0.053619 
+_atom_sites.fract_transf_matrix[2][3]   0.000000 
+_atom_sites.fract_transf_matrix[3][1]   0.000000 
+_atom_sites.fract_transf_matrix[3][2]   0.000000 
+_atom_sites.fract_transf_matrix[3][3]   0.044409 
+_atom_sites.fract_transf_vector[1]      0.00000 
+_atom_sites.fract_transf_vector[2]      0.00000 
+_atom_sites.fract_transf_vector[3]      0.00000 
+# 
+loop_
+_atom_type.symbol 
+N 
+C 
+O 
+S 
+# 
+loop_
+_atom_site.group_PDB 
+_atom_site.id 
+_atom_site.type_symbol 
+_atom_site.label_atom_id 
+_atom_site.label_alt_id 
+_atom_site.label_comp_id 
+_atom_site.label_asym_id 
+_atom_site.label_entity_id 
+_atom_site.label_seq_id 
+_atom_site.pdbx_PDB_ins_code 
+_atom_site.Cartn_x 
+_atom_site.Cartn_y 
+_atom_site.Cartn_z 
+_atom_site.occupancy 
+_atom_site.B_iso_or_equiv 
+_atom_site.Cartn_x_esd 
+_atom_site.Cartn_y_esd 
+_atom_site.Cartn_z_esd 
+_atom_site.occupancy_esd 
+_atom_site.B_iso_or_equiv_esd 
+_atom_site.pdbx_formal_charge 
+_atom_site.auth_seq_id 
+_atom_site.auth_comp_id 
+_atom_site.auth_asym_id 
+_atom_site.auth_atom_id 
+_atom_site.pdbx_PDB_model_num 
+ATOM 1   N N   . THR A 1 1  ? 17.047 14.099 3.625  1.00 13.79 ? ? ? ? ? ? 1  THR A N   1 
+ATOM 2   C CA  . THR A 1 1  ? 16.967 12.784 4.338  1.00 10.80 ? ? ? ? ? ? 1  THR A CA  1 
+ATOM 3   C C   . THR A 1 1  ? 15.685 12.755 5.133  1.00 9.19  ? ? ? ? ? ? 1  THR A C   1 
+ATOM 4   O O   . THR A 1 1  ? 15.268 13.825 5.594  1.00 9.85  ? ? ? ? ? ? 1  THR A O   1 
+ATOM 5   C CB  . THR A 1 1  ? 18.170 12.703 5.337  1.00 13.02 ? ? ? ? ? ? 1  THR A CB  1 
+ATOM 6   O OG1 . THR A 1 1  ? 19.334 12.829 4.463  1.00 15.06 ? ? ? ? ? ? 1  THR A OG1 1 
+ATOM 7   C CG2 . THR A 1 1  ? 18.150 11.546 6.304  1.00 14.23 ? ? ? ? ? ? 1  THR A CG2 1 
+ATOM 8   N N   . THR A 1 2  ? 15.115 11.555 5.265  1.00 7.81  ? ? ? ? ? ? 2  THR A N   1 
+ATOM 9   C CA  . THR A 1 2  ? 13.856 11.469 6.066  1.00 8.31  ? ? ? ? ? ? 2  THR A CA  1 
+ATOM 10  C C   . THR A 1 2  ? 14.164 10.785 7.379  1.00 5.80  ? ? ? ? ? ? 2  THR A C   1 
+ATOM 11  O O   . THR A 1 2  ? 14.993 9.862  7.443  1.00 6.94  ? ? ? ? ? ? 2  THR A O   1 
+ATOM 12  C CB  . THR A 1 2  ? 12.732 10.711 5.261  1.00 10.32 ? ? ? ? ? ? 2  THR A CB  1 
+ATOM 13  O OG1 . THR A 1 2  ? 13.308 9.439  4.926  1.00 12.81 ? ? ? ? ? ? 2  THR A OG1 1 
+ATOM 14  C CG2 . THR A 1 2  ? 12.484 11.442 3.895  1.00 11.90 ? ? ? ? ? ? 2  THR A CG2 1 
+ATOM 15  N N   . CYS A 1 3  ? 13.488 11.241 8.417  1.00 5.24  ? ? ? ? ? ? 3  CYS A N   1 
+ATOM 16  C CA  . CYS A 1 3  ? 13.660 10.707 9.787  1.00 5.39  ? ? ? ? ? ? 3  CYS A CA  1 
+ATOM 17  C C   . CYS A 1 3  ? 12.269 10.431 10.323 1.00 4.45  ? ? ? ? ? ? 3  CYS A C   1 
+ATOM 18  O O   . CYS A 1 3  ? 11.393 11.308 10.185 1.00 6.54  ? ? ? ? ? ? 3  CYS A O   1 
+ATOM 19  C CB  . CYS A 1 3  ? 14.368 11.748 10.691 1.00 5.99  ? ? ? ? ? ? 3  CYS A CB  1 
+ATOM 20  S SG  . CYS A 1 3  ? 15.885 12.426 10.016 1.00 7.01  ? ? ? ? ? ? 3  CYS A SG  1 
+ATOM 21  N N   . CYS A 1 4  ? 12.019 9.272  10.928 1.00 3.90  ? ? ? ? ? ? 4  CYS A N   1 
+ATOM 22  C CA  . CYS A 1 4  ? 10.646 8.991  11.408 1.00 4.24  ? ? ? ? ? ? 4  CYS A CA  1 
+ATOM 23  C C   . CYS A 1 4  ? 10.654 8.793  12.919 1.00 3.72  ? ? ? ? ? ? 4  CYS A C   1 
+ATOM 24  O O   . CYS A 1 4  ? 11.659 8.296  13.491 1.00 5.30  ? ? ? ? ? ? 4  CYS A O   1 
+ATOM 25  C CB  . CYS A 1 4  ? 10.057 7.752  10.682 1.00 4.41  ? ? ? ? ? ? 4  CYS A CB  1 
+ATOM 26  S SG  . CYS A 1 4  ? 9.837  8.018  8.904  1.00 4.72  ? ? ? ? ? ? 4  CYS A SG  1 
+ATOM 27  N N   . PRO A 1 5  ? 9.561  9.108  13.563 1.00 3.96  ? ? ? ? ? ? 5  PRO A N   1 
+ATOM 28  C CA  . PRO A 1 5  ? 9.448  9.034  15.012 1.00 4.25  ? ? ? ? ? ? 5  PRO A CA  1 
+ATOM 29  C C   . PRO A 1 5  ? 9.288  7.670  15.606 1.00 4.96  ? ? ? ? ? ? 5  PRO A C   1 
+ATOM 30  O O   . PRO A 1 5  ? 9.490  7.519  16.819 1.00 7.44  ? ? ? ? ? ? 5  PRO A O   1 
+ATOM 31  C CB  . PRO A 1 5  ? 8.230  9.957  15.345 1.00 5.11  ? ? ? ? ? ? 5  PRO A CB  1 
+ATOM 32  C CG  . PRO A 1 5  ? 7.338  9.786  14.114 1.00 5.24  ? ? ? ? ? ? 5  PRO A CG  1 
+ATOM 33  C CD  . PRO A 1 5  ? 8.366  9.804  12.958 1.00 5.20  ? ? ? ? ? ? 5  PRO A CD  1 
+ATOM 34  N N   . SER A 1 6  ? 8.875  6.686  14.796 1.00 4.83  ? ? ? ? ? ? 6  SER A N   1 
+ATOM 35  C CA  . SER A 1 6  ? 8.673  5.314  15.279 1.00 4.45  ? ? ? ? ? ? 6  SER A CA  1 
+ATOM 36  C C   . SER A 1 6  ? 8.753  4.376  14.083 1.00 4.99  ? ? ? ? ? ? 6  SER A C   1 
+ATOM 37  O O   . SER A 1 6  ? 8.726  4.858  12.923 1.00 4.61  ? ? ? ? ? ? 6  SER A O   1 
+ATOM 38  C CB  . SER A 1 6  ? 7.340  5.121  15.996 1.00 5.05  ? ? ? ? ? ? 6  SER A CB  1 
+ATOM 39  O OG  . SER A 1 6  ? 6.274  5.220  15.031 1.00 6.39  ? ? ? ? ? ? 6  SER A OG  1 
+ATOM 40  N N   . ILE A 1 7  ? 8.881  3.075  14.358 1.00 4.94  ? ? ? ? ? ? 7  ILE A N   1 
+ATOM 41  C CA  . ILE A 1 7  ? 8.912  2.083  13.258 1.00 6.33  ? ? ? ? ? ? 7  ILE A CA  1 
+ATOM 42  C C   . ILE A 1 7  ? 7.581  2.090  12.506 1.00 5.32  ? ? ? ? ? ? 7  ILE A C   1 
+ATOM 43  O O   . ILE A 1 7  ? 7.670  2.031  11.245 1.00 6.85  ? ? ? ? ? ? 7  ILE A O   1 
+ATOM 44  C CB  . ILE A 1 7  ? 9.207  0.677  13.924 1.00 8.43  ? ? ? ? ? ? 7  ILE A CB  1 
+ATOM 45  C CG1 . ILE A 1 7  ? 10.714 0.702  14.312 1.00 9.78  ? ? ? ? ? ? 7  ILE A CG1 1 
+ATOM 46  C CG2 . ILE A 1 7  ? 8.811  -0.477 12.969 1.00 11.70 ? ? ? ? ? ? 7  ILE A CG2 1 
+ATOM 47  C CD1 . ILE A 1 7  ? 11.185 -0.516 15.142 1.00 9.92  ? ? ? ? ? ? 7  ILE A CD1 1 
+ATOM 48  N N   . VAL A 1 8  ? 6.458  2.162  13.159 1.00 5.02  ? ? ? ? ? ? 8  VAL A N   1 
+ATOM 49  C CA  . VAL A 1 8  ? 5.145  2.209  12.453 1.00 6.93  ? ? ? ? ? ? 8  VAL A CA  1 
+ATOM 50  C C   . VAL A 1 8  ? 5.115  3.379  11.461 1.00 5.39  ? ? ? ? ? ? 8  VAL A C   1 
+ATOM 51  O O   . VAL A 1 8  ? 4.664  3.268  10.343 1.00 6.30  ? ? ? ? ? ? 8  VAL A O   1 
+ATOM 52  C CB  . VAL A 1 8  ? 3.995  2.354  13.478 1.00 9.64  ? ? ? ? ? ? 8  VAL A CB  1 
+ATOM 53  C CG1 . VAL A 1 8  ? 2.716  2.891  12.869 1.00 13.85 ? ? ? ? ? ? 8  VAL A CG1 1 
+ATOM 54  C CG2 . VAL A 1 8  ? 3.758  1.032  14.208 1.00 11.97 ? ? ? ? ? ? 8  VAL A CG2 1 
+ATOM 55  N N   . ALA A 1 9  ? 5.606  4.546  11.941 1.00 3.73  ? ? ? ? ? ? 9  ALA A N   1 
+ATOM 56  C CA  . ALA A 1 9  ? 5.598  5.767  11.082 1.00 3.56  ? ? ? ? ? ? 9  ALA A CA  1 
+ATOM 57  C C   . ALA A 1 9  ? 6.441  5.527  9.850  1.00 4.13  ? ? ? ? ? ? 9  ALA A C   1 
+ATOM 58  O O   . ALA A 1 9  ? 6.052  5.933  8.744  1.00 4.36  ? ? ? ? ? ? 9  ALA A O   1 
+ATOM 59  C CB  . ALA A 1 9  ? 6.022  6.977  11.891 1.00 4.80  ? ? ? ? ? ? 9  ALA A CB  1 
+ATOM 60  N N   . ARG A 1 10 ? 7.647  4.909  10.005 1.00 3.73  ? ? ? ? ? ? 10 ARG A N   1 
+ATOM 61  C CA  . ARG A 1 10 ? 8.496  4.609  8.837  1.00 3.38  ? ? ? ? ? ? 10 ARG A CA  1 
+ATOM 62  C C   . ARG A 1 10 ? 7.798  3.609  7.876  1.00 3.47  ? ? ? ? ? ? 10 ARG A C   1 
+ATOM 63  O O   . ARG A 1 10 ? 7.878  3.778  6.651  1.00 4.67  ? ? ? ? ? ? 10 ARG A O   1 
+ATOM 64  C CB  . ARG A 1 10 ? 9.847  4.020  9.305  1.00 3.95  ? ? ? ? ? ? 10 ARG A CB  1 
+ATOM 65  C CG  . ARG A 1 10 ? 10.752 3.607  8.149  1.00 4.55  ? ? ? ? ? ? 10 ARG A CG  1 
+ATOM 66  C CD  . ARG A 1 10 ? 11.226 4.699  7.244  1.00 5.89  ? ? ? ? ? ? 10 ARG A CD  1 
+ATOM 67  N NE  . ARG A 1 10 ? 12.143 5.571  8.035  1.00 6.20  ? ? ? ? ? ? 10 ARG A NE  1 
+ATOM 68  C CZ  . ARG A 1 10 ? 12.758 6.609  7.443  1.00 7.52  ? ? ? ? ? ? 10 ARG A CZ  1 
+ATOM 69  N NH1 . ARG A 1 10 ? 12.539 6.932  6.158  1.00 10.68 ? ? ? ? ? ? 10 ARG A NH1 1 
+ATOM 70  N NH2 . ARG A 1 10 ? 13.601 7.322  8.202  1.00 9.48  ? ? ? ? ? ? 10 ARG A NH2 1 
+ATOM 71  N N   . SER A 1 11 ? 7.186  2.582  8.445  1.00 5.19  ? ? ? ? ? ? 11 SER A N   1 
+ATOM 72  C CA  . SER A 1 11 ? 6.500  1.584  7.565  1.00 4.60  ? ? ? ? ? ? 11 SER A CA  1 
+ATOM 73  C C   . SER A 1 11 ? 5.382  2.313  6.773  1.00 4.84  ? ? ? ? ? ? 11 SER A C   1 
+ATOM 74  O O   . SER A 1 11 ? 5.213  2.016  5.557  1.00 5.84  ? ? ? ? ? ? 11 SER A O   1 
+ATOM 75  C CB  . SER A 1 11 ? 5.908  0.462  8.400  1.00 5.91  ? ? ? ? ? ? 11 SER A CB  1 
+ATOM 76  O OG  . SER A 1 11 ? 6.990  -0.272 9.012  1.00 8.38  ? ? ? ? ? ? 11 SER A OG  1 
+ATOM 77  N N   . ASN A 1 12 ? 4.648  3.182  7.446  1.00 3.54  ? ? ? ? ? ? 12 ASN A N   1 
+ATOM 78  C CA  . ASN A 1 12 ? 3.545  3.935  6.751  1.00 4.57  ? ? ? ? ? ? 12 ASN A CA  1 
+ATOM 79  C C   . ASN A 1 12 ? 4.107  4.851  5.691  1.00 4.14  ? ? ? ? ? ? 12 ASN A C   1 
+ATOM 80  O O   . ASN A 1 12 ? 3.536  5.001  4.617  1.00 5.52  ? ? ? ? ? ? 12 ASN A O   1 
+ATOM 81  C CB  . ASN A 1 12 ? 2.663  4.677  7.748  1.00 6.42  ? ? ? ? ? ? 12 ASN A CB  1 
+ATOM 82  C CG  . ASN A 1 12 ? 1.802  3.735  8.610  1.00 8.25  ? ? ? ? ? ? 12 ASN A CG  1 
+ATOM 83  O OD1 . ASN A 1 12 ? 1.567  2.613  8.165  1.00 12.72 ? ? ? ? ? ? 12 ASN A OD1 1 
+ATOM 84  N ND2 . ASN A 1 12 ? 1.394  4.252  9.767  1.00 9.92  ? ? ? ? ? ? 12 ASN A ND2 1 
+ATOM 85  N N   . PHE A 1 13 ? 5.259  5.498  6.005  1.00 3.43  ? ? ? ? ? ? 13 PHE A N   1 
+ATOM 86  C CA  . PHE A 1 13 ? 5.929  6.358  5.055  1.00 3.49  ? ? ? ? ? ? 13 PHE A CA  1 
+ATOM 87  C C   . PHE A 1 13 ? 6.304  5.578  3.799  1.00 3.40  ? ? ? ? ? ? 13 PHE A C   1 
+ATOM 88  O O   . PHE A 1 13 ? 6.136  6.072  2.653  1.00 4.07  ? ? ? ? ? ? 13 PHE A O   1 
+ATOM 89  C CB  . PHE A 1 13 ? 7.183  6.994  5.754  1.00 5.48  ? ? ? ? ? ? 13 PHE A CB  1 
+ATOM 90  C CG  . PHE A 1 13 ? 7.884  8.006  4.883  1.00 5.57  ? ? ? ? ? ? 13 PHE A CG  1 
+ATOM 91  C CD1 . PHE A 1 13 ? 8.906  7.586  4.027  1.00 6.99  ? ? ? ? ? ? 13 PHE A CD1 1 
+ATOM 92  C CD2 . PHE A 1 13 ? 7.532  9.373  4.983  1.00 6.52  ? ? ? ? ? ? 13 PHE A CD2 1 
+ATOM 93  C CE1 . PHE A 1 13 ? 9.560  8.539  3.194  1.00 8.20  ? ? ? ? ? ? 13 PHE A CE1 1 
+ATOM 94  C CE2 . PHE A 1 13 ? 8.176  10.281 4.145  1.00 6.34  ? ? ? ? ? ? 13 PHE A CE2 1 
+ATOM 95  C CZ  . PHE A 1 13 ? 9.141  9.845  3.292  1.00 6.84  ? ? ? ? ? ? 13 PHE A CZ  1 
+ATOM 96  N N   . ASN A 1 14 ? 6.900  4.390  3.989  1.00 3.64  ? ? ? ? ? ? 14 ASN A N   1 
+ATOM 97  C CA  . ASN A 1 14 ? 7.331  3.607  2.791  1.00 4.31  ? ? ? ? ? ? 14 ASN A CA  1 
+ATOM 98  C C   . ASN A 1 14 ? 6.116  3.210  1.915  1.00 3.98  ? ? ? ? ? ? 14 ASN A C   1 
+ATOM 99  O O   . ASN A 1 14 ? 6.240  3.144  0.684  1.00 6.22  ? ? ? ? ? ? 14 ASN A O   1 
+ATOM 100 C CB  . ASN A 1 14 ? 8.145  2.404  3.240  1.00 5.81  ? ? ? ? ? ? 14 ASN A CB  1 
+ATOM 101 C CG  . ASN A 1 14 ? 9.555  2.856  3.730  1.00 6.82  ? ? ? ? ? ? 14 ASN A CG  1 
+ATOM 102 O OD1 . ASN A 1 14 ? 10.013 3.895  3.323  1.00 9.43  ? ? ? ? ? ? 14 ASN A OD1 1 
+ATOM 103 N ND2 . ASN A 1 14 ? 10.120 1.956  4.539  1.00 8.21  ? ? ? ? ? ? 14 ASN A ND2 1 
+ATOM 104 N N   . VAL A 1 15 ? 4.993  2.927  2.571  1.00 3.76  ? ? ? ? ? ? 15 VAL A N   1 
+ATOM 105 C CA  . VAL A 1 15 ? 3.782  2.599  1.742  1.00 3.98  ? ? ? ? ? ? 15 VAL A CA  1 
+ATOM 106 C C   . VAL A 1 15 ? 3.296  3.871  1.004  1.00 3.80  ? ? ? ? ? ? 15 VAL A C   1 
+ATOM 107 O O   . VAL A 1 15 ? 2.947  3.817  -0.189 1.00 4.85  ? ? ? ? ? ? 15 VAL A O   1 
+ATOM 108 C CB  . VAL A 1 15 ? 2.698  1.953  2.608  1.00 4.71  ? ? ? ? ? ? 15 VAL A CB  1 
+ATOM 109 C CG1 . VAL A 1 15 ? 1.384  1.826  1.806  1.00 6.67  ? ? ? ? ? ? 15 VAL A CG1 1 
+ATOM 110 C CG2 . VAL A 1 15 ? 3.174  0.533  3.005  1.00 6.26  ? ? ? ? ? ? 15 VAL A CG2 1 
+ATOM 111 N N   . CYS A 1 16 ? 3.321  4.987  1.720  1.00 3.79  ? ? ? ? ? ? 16 CYS A N   1 
+ATOM 112 C CA  . CYS A 1 16 ? 2.890  6.285  1.126  1.00 3.54  ? ? ? ? ? ? 16 CYS A CA  1 
+ATOM 113 C C   . CYS A 1 16 ? 3.687  6.597  -0.111 1.00 3.48  ? ? ? ? ? ? 16 CYS A C   1 
+ATOM 114 O O   . CYS A 1 16 ? 3.200  7.147  -1.103 1.00 4.63  ? ? ? ? ? ? 16 CYS A O   1 
+ATOM 115 C CB  . CYS A 1 16 ? 3.039  7.369  2.240  1.00 4.58  ? ? ? ? ? ? 16 CYS A CB  1 
+ATOM 116 S SG  . CYS A 1 16 ? 2.559  9.014  1.649  1.00 5.66  ? ? ? ? ? ? 16 CYS A SG  1 
+ATOM 117 N N   . ARG A 1 17 ? 4.997  6.227  -0.100 1.00 3.99  ? ? ? ? ? ? 17 ARG A N   1 
+ATOM 118 C CA  . ARG A 1 17 ? 5.895  6.489  -1.213 1.00 3.83  ? ? ? ? ? ? 17 ARG A CA  1 
+ATOM 119 C C   . ARG A 1 17 ? 5.738  5.560  -2.409 1.00 3.79  ? ? ? ? ? ? 17 ARG A C   1 
+ATOM 120 O O   . ARG A 1 17 ? 6.228  5.901  -3.507 1.00 5.39  ? ? ? ? ? ? 17 ARG A O   1 
+ATOM 121 C CB  . ARG A 1 17 ? 7.370  6.507  -0.731 1.00 4.11  ? ? ? ? ? ? 17 ARG A CB  1 
+ATOM 122 C CG  . ARG A 1 17 ? 7.717  7.687  0.206  1.00 4.69  ? ? ? ? ? ? 17 ARG A CG  1 
+ATOM 123 C CD  . ARG A 1 17 ? 7.949  8.947  -0.615 1.00 5.10  ? ? ? ? ? ? 17 ARG A CD  1 
+ATOM 124 N NE  . ARG A 1 17 ? 9.212  8.856  -1.337 1.00 4.71  ? ? ? ? ? ? 17 ARG A NE  1 
+ATOM 125 C CZ  . ARG A 1 17 ? 9.537  9.533  -2.431 1.00 5.28  ? ? ? ? ? ? 17 ARG A CZ  1 
+ATOM 126 N NH1 . ARG A 1 17 ? 8.659  10.350 -3.032 1.00 6.67  ? ? ? ? ? ? 17 ARG A NH1 1 
+ATOM 127 N NH2 . ARG A 1 17 ? 10.793 9.491  -2.899 1.00 6.41  ? ? ? ? ? ? 17 ARG A NH2 1 
+ATOM 128 N N   . LEU A 1 18 ? 5.051  4.411  -2.204 1.00 4.70  ? ? ? ? ? ? 18 LEU A N   1 
+ATOM 129 C CA  . LEU A 1 18 ? 4.933  3.431  -3.326 1.00 5.46  ? ? ? ? ? ? 18 LEU A CA  1 
+ATOM 130 C C   . LEU A 1 18 ? 4.397  4.014  -4.620 1.00 5.13  ? ? ? ? ? ? 18 LEU A C   1 
+ATOM 131 O O   . LEU A 1 18 ? 4.988  3.755  -5.687 1.00 5.55  ? ? ? ? ? ? 18 LEU A O   1 
+ATOM 132 C CB  . LEU A 1 18 ? 4.196  2.184  -2.863 1.00 6.47  ? ? ? ? ? ? 18 LEU A CB  1 
+ATOM 133 C CG  . LEU A 1 18 ? 4.960  1.178  -1.991 1.00 7.43  ? ? ? ? ? ? 18 LEU A CG  1 
+ATOM 134 C CD1 . LEU A 1 18 ? 3.907  0.097  -1.634 1.00 8.70  ? ? ? ? ? ? 18 LEU A CD1 1 
+ATOM 135 C CD2 . LEU A 1 18 ? 6.129  0.606  -2.768 1.00 9.39  ? ? ? ? ? ? 18 LEU A CD2 1 
+ATOM 136 N N   . PRO A 1 19 ? 3.329  4.795  -4.543 1.00 4.28  ? ? ? ? ? ? 19 PRO A N   1 
+ATOM 137 C CA  . PRO A 1 19 ? 2.792  5.376  -5.797 1.00 5.38  ? ? ? ? ? ? 19 PRO A CA  1 
+ATOM 138 C C   . PRO A 1 19 ? 3.573  6.540  -6.322 1.00 6.30  ? ? ? ? ? ? 19 PRO A C   1 
+ATOM 139 O O   . PRO A 1 19 ? 3.260  7.045  -7.422 1.00 9.62  ? ? ? ? ? ? 19 PRO A O   1 
+ATOM 140 C CB  . PRO A 1 19 ? 1.358  5.766  -5.472 1.00 5.87  ? ? ? ? ? ? 19 PRO A CB  1 
+ATOM 141 C CG  . PRO A 1 19 ? 1.223  5.694  -3.993 1.00 6.47  ? ? ? ? ? ? 19 PRO A CG  1 
+ATOM 142 C CD  . PRO A 1 19 ? 2.421  4.941  -3.408 1.00 6.45  ? ? ? ? ? ? 19 PRO A CD  1 
+ATOM 143 N N   . GLY A 1 20 ? 4.565  7.047  -5.559 1.00 4.94  ? ? ? ? ? ? 20 GLY A N   1 
+ATOM 144 C CA  . GLY A 1 20 ? 5.366  8.191  -6.018 1.00 5.39  ? ? ? ? ? ? 20 GLY A CA  1 
+ATOM 145 C C   . GLY A 1 20 ? 5.007  9.481  -5.280 1.00 5.03  ? ? ? ? ? ? 20 GLY A C   1 
+ATOM 146 O O   . GLY A 1 20 ? 5.535  10.510 -5.730 1.00 7.34  ? ? ? ? ? ? 20 GLY A O   1 
+ATOM 147 N N   . THR A 1 21 ? 4.181  9.438  -4.262 1.00 4.10  ? ? ? ? ? ? 21 THR A N   1 
+ATOM 148 C CA  . THR A 1 21 ? 3.767  10.609 -3.513 1.00 3.94  ? ? ? ? ? ? 21 THR A CA  1 
+ATOM 149 C C   . THR A 1 21 ? 5.017  11.397 -3.042 1.00 3.96  ? ? ? ? ? ? 21 THR A C   1 
+ATOM 150 O O   . THR A 1 21 ? 5.947  10.757 -2.523 1.00 5.82  ? ? ? ? ? ? 21 THR A O   1 
+ATOM 151 C CB  . THR A 1 21 ? 2.992  10.188 -2.225 1.00 4.13  ? ? ? ? ? ? 21 THR A CB  1 
+ATOM 152 O OG1 . THR A 1 21 ? 2.051  9.144  -2.623 1.00 5.45  ? ? ? ? ? ? 21 THR A OG1 1 
+ATOM 153 C CG2 . THR A 1 21 ? 2.260  11.349 -1.551 1.00 5.41  ? ? ? ? ? ? 21 THR A CG2 1 
+ATOM 154 N N   . PRO A 1 22 ? 4.971  12.703 -3.176 1.00 5.04  ? ? ? ? ? ? 22 PRO A N   1 
+ATOM 155 C CA  . PRO A 1 22 ? 6.143  13.513 -2.696 1.00 4.69  ? ? ? ? ? ? 22 PRO A CA  1 
+ATOM 156 C C   . PRO A 1 22 ? 6.400  13.233 -1.225 1.00 4.19  ? ? ? ? ? ? 22 PRO A C   1 
+ATOM 157 O O   . PRO A 1 22 ? 5.485  13.061 -0.382 1.00 4.47  ? ? ? ? ? ? 22 PRO A O   1 
+ATOM 158 C CB  . PRO A 1 22 ? 5.703  14.969 -2.920 1.00 7.12  ? ? ? ? ? ? 22 PRO A CB  1 
+ATOM 159 C CG  . PRO A 1 22 ? 4.676  14.893 -3.996 1.00 7.03  ? ? ? ? ? ? 22 PRO A CG  1 
+ATOM 160 C CD  . PRO A 1 22 ? 3.964  13.567 -3.811 1.00 4.90  ? ? ? ? ? ? 22 PRO A CD  1 
+ATOM 161 N N   . GLU A 1 23 ? 7.728  13.297 -0.921 1.00 5.16  ? ? ? ? ? ? 23 GLU A N   1 
+ATOM 162 C CA  . GLU A 1 23 ? 8.114  13.103 0.500  1.00 5.31  ? ? ? ? ? ? 23 GLU A CA  1 
+ATOM 163 C C   . GLU A 1 23 ? 7.427  14.073 1.410  1.00 4.11  ? ? ? ? ? ? 23 GLU A C   1 
+ATOM 164 O O   . GLU A 1 23 ? 7.036  13.682 2.540  1.00 5.11  ? ? ? ? ? ? 23 GLU A O   1 
+ATOM 165 C CB  . GLU A 1 23 ? 9.648  13.285 0.660  1.00 6.16  ? ? ? ? ? ? 23 GLU A CB  1 
+ATOM 166 C CG  . GLU A 1 23 ? 10.440 12.093 0.063  1.00 7.48  ? ? ? ? ? ? 23 GLU A CG  1 
+ATOM 167 C CD  . GLU A 1 23 ? 11.941 12.170 0.391  1.00 9.40  ? ? ? ? ? ? 23 GLU A CD  1 
+ATOM 168 O OE1 . GLU A 1 23 ? 12.416 13.225 0.681  1.00 10.40 ? ? ? ? ? ? 23 GLU A OE1 1 
+ATOM 169 O OE2 . GLU A 1 23 ? 12.539 11.070 0.292  1.00 13.32 ? ? ? ? ? ? 23 GLU A OE2 1 
+ATOM 170 N N   . ALA A 1 24 ? 7.212  15.334 0.966  1.00 4.56  ? ? ? ? ? ? 24 ALA A N   1 
+ATOM 171 C CA  . ALA A 1 24 ? 6.614  16.317 1.913  1.00 4.49  ? ? ? ? ? ? 24 ALA A CA  1 
+ATOM 172 C C   . ALA A 1 24 ? 5.212  15.936 2.350  1.00 4.10  ? ? ? ? ? ? 24 ALA A C   1 
+ATOM 173 O O   . ALA A 1 24 ? 4.782  16.166 3.495  1.00 5.64  ? ? ? ? ? ? 24 ALA A O   1 
+ATOM 174 C CB  . ALA A 1 24 ? 6.605  17.695 1.246  1.00 5.80  ? ? ? ? ? ? 24 ALA A CB  1 
+ATOM 175 N N   . ILE A 1 25 ? 4.445  15.318 1.405  1.00 4.37  ? ? ? ? ? ? 25 ILE A N   1 
+ATOM 176 C CA  . ILE A 1 25 ? 3.074  14.894 1.756  1.00 5.44  ? ? ? ? ? ? 25 ILE A CA  1 
+ATOM 177 C C   . ILE A 1 25 ? 3.085  13.643 2.645  1.00 4.32  ? ? ? ? ? ? 25 ILE A C   1 
+ATOM 178 O O   . ILE A 1 25 ? 2.315  13.523 3.578  1.00 4.72  ? ? ? ? ? ? 25 ILE A O   1 
+ATOM 179 C CB  . ILE A 1 25 ? 2.204  14.637 0.462  1.00 6.42  ? ? ? ? ? ? 25 ILE A CB  1 
+ATOM 180 C CG1 . ILE A 1 25 ? 1.815  16.048 -0.129 1.00 7.50  ? ? ? ? ? ? 25 ILE A CG1 1 
+ATOM 181 C CG2 . ILE A 1 25 ? 0.903  13.864 0.811  1.00 7.65  ? ? ? ? ? ? 25 ILE A CG2 1 
+ATOM 182 C CD1 . ILE A 1 25 ? 0.756  16.761 0.757  1.00 7.80  ? ? ? ? ? ? 25 ILE A CD1 1 
+ATOM 183 N N   . CYS A 1 26 ? 4.032  12.764 2.313  1.00 3.92  ? ? ? ? ? ? 26 CYS A N   1 
+ATOM 184 C CA  . CYS A 1 26 ? 4.180  11.549 3.187  1.00 4.37  ? ? ? ? ? ? 26 CYS A CA  1 
+ATOM 185 C C   . CYS A 1 26 ? 4.632  11.944 4.596  1.00 3.95  ? ? ? ? ? ? 26 CYS A C   1 
+ATOM 186 O O   . CYS A 1 26 ? 4.227  11.252 5.547  1.00 4.74  ? ? ? ? ? ? 26 CYS A O   1 
+ATOM 187 C CB  . CYS A 1 26 ? 5.038  10.518 2.539  1.00 4.63  ? ? ? ? ? ? 26 CYS A CB  1 
+ATOM 188 S SG  . CYS A 1 26 ? 4.349  9.794  1.022  1.00 5.61  ? ? ? ? ? ? 26 CYS A SG  1 
+ATOM 189 N N   . ALA A 1 27 ? 5.408  13.012 4.694  1.00 3.89  ? ? ? ? ? ? 27 ALA A N   1 
+ATOM 190 C CA  . ALA A 1 27 ? 5.879  13.502 6.026  1.00 4.43  ? ? ? ? ? ? 27 ALA A CA  1 
+ATOM 191 C C   . ALA A 1 27 ? 4.696  13.908 6.882  1.00 4.26  ? ? ? ? ? ? 27 ALA A C   1 
+ATOM 192 O O   . ALA A 1 27 ? 4.528  13.422 8.025  1.00 5.44  ? ? ? ? ? ? 27 ALA A O   1 
+ATOM 193 C CB  . ALA A 1 27 ? 6.880  14.615 5.830  1.00 5.36  ? ? ? ? ? ? 27 ALA A CB  1 
+ATOM 194 N N   . THR A 1 28 ? 3.827  14.802 6.358  1.00 4.53  ? ? ? ? ? ? 28 THR A N   1 
+ATOM 195 C CA  . THR A 1 28 ? 2.691  15.221 7.194  1.00 5.08  ? ? ? ? ? ? 28 THR A CA  1 
+ATOM 196 C C   . THR A 1 28 ? 1.672  14.132 7.434  1.00 4.62  ? ? ? ? ? ? 28 THR A C   1 
+ATOM 197 O O   . THR A 1 28 ? 0.947  14.112 8.468  1.00 7.80  ? ? ? ? ? ? 28 THR A O   1 
+ATOM 198 C CB  . THR A 1 28 ? 1.986  16.520 6.614  1.00 6.03  ? ? ? ? ? ? 28 THR A CB  1 
+ATOM 199 O OG1 . THR A 1 28 ? 1.664  16.221 5.230  1.00 7.19  ? ? ? ? ? ? 28 THR A OG1 1 
+ATOM 200 C CG2 . THR A 1 28 ? 2.914  17.739 6.700  1.00 7.34  ? ? ? ? ? ? 28 THR A CG2 1 
+ATOM 201 N N   . TYR A 1 29 ? 1.621  13.190 6.511  1.00 5.01  ? ? ? ? ? ? 29 TYR A N   1 
+ATOM 202 C CA  . TYR A 1 29 ? 0.715  12.045 6.657  1.00 6.60  ? ? ? ? ? ? 29 TYR A CA  1 
+ATOM 203 C C   . TYR A 1 29 ? 1.125  11.125 7.815  1.00 4.92  ? ? ? ? ? ? 29 TYR A C   1 
+ATOM 204 O O   . TYR A 1 29 ? 0.286  10.632 8.545  1.00 7.13  ? ? ? ? ? ? 29 TYR A O   1 
+ATOM 205 C CB  . TYR A 1 29 ? 0.755  11.229 5.322  1.00 9.66  ? ? ? ? ? ? 29 TYR A CB  1 
+ATOM 206 C CG  . TYR A 1 29 ? -0.203 10.044 5.354  1.00 11.56 ? ? ? ? ? ? 29 TYR A CG  1 
+ATOM 207 C CD1 . TYR A 1 29 ? -1.547 10.337 5.645  1.00 12.85 ? ? ? ? ? ? 29 TYR A CD1 1 
+ATOM 208 C CD2 . TYR A 1 29 ? 0.193  8.750  5.100  1.00 14.44 ? ? ? ? ? ? 29 TYR A CD2 1 
+ATOM 209 C CE1 . TYR A 1 29 ? -2.496 9.329  5.673  1.00 16.61 ? ? ? ? ? ? 29 TYR A CE1 1 
+ATOM 210 C CE2 . TYR A 1 29 ? -0.801 7.705  5.156  1.00 17.11 ? ? ? ? ? ? 29 TYR A CE2 1 
+ATOM 211 C CZ  . TYR A 1 29 ? -2.079 8.031  5.430  1.00 19.99 ? ? ? ? ? ? 29 TYR A CZ  1 
+ATOM 212 O OH  . TYR A 1 29 ? -3.097 7.057  5.458  1.00 28.98 ? ? ? ? ? ? 29 TYR A OH  1 
+ATOM 213 N N   . THR A 1 30 ? 2.470  10.984 7.995  1.00 5.31  ? ? ? ? ? ? 30 THR A N   1 
+ATOM 214 C CA  . THR A 1 30 ? 2.986  9.994  8.950  1.00 5.70  ? ? ? ? ? ? 30 THR A CA  1 
+ATOM 215 C C   . THR A 1 30 ? 3.609  10.505 10.230 1.00 6.28  ? ? ? ? ? ? 30 THR A C   1 
+ATOM 216 O O   . THR A 1 30 ? 3.766  9.715  11.186 1.00 8.77  ? ? ? ? ? ? 30 THR A O   1 
+ATOM 217 C CB  . THR A 1 30 ? 4.076  9.103  8.225  1.00 6.55  ? ? ? ? ? ? 30 THR A CB  1 
+ATOM 218 O OG1 . THR A 1 30 ? 5.125  10.027 7.824  1.00 6.57  ? ? ? ? ? ? 30 THR A OG1 1 
+ATOM 219 C CG2 . THR A 1 30 ? 3.493  8.324  7.035  1.00 7.29  ? ? ? ? ? ? 30 THR A CG2 1 
+ATOM 220 N N   . GLY A 1 31 ? 3.984  11.764 10.241 1.00 4.99  ? ? ? ? ? ? 31 GLY A N   1 
+ATOM 221 C CA  . GLY A 1 31 ? 4.769  12.336 11.360 1.00 5.50  ? ? ? ? ? ? 31 GLY A CA  1 
+ATOM 222 C C   . GLY A 1 31 ? 6.255  12.243 11.106 1.00 4.19  ? ? ? ? ? ? 31 GLY A C   1 
+ATOM 223 O O   . GLY A 1 31 ? 7.037  12.750 11.954 1.00 6.12  ? ? ? ? ? ? 31 GLY A O   1 
+ATOM 224 N N   . CYS A 1 32 ? 6.710  11.631 9.992  1.00 4.30  ? ? ? ? ? ? 32 CYS A N   1 
+ATOM 225 C CA  . CYS A 1 32 ? 8.140  11.694 9.635  1.00 4.89  ? ? ? ? ? ? 32 CYS A CA  1 
+ATOM 226 C C   . CYS A 1 32 ? 8.500  13.141 9.206  1.00 5.50  ? ? ? ? ? ? 32 CYS A C   1 
+ATOM 227 O O   . CYS A 1 32 ? 7.581  13.949 8.944  1.00 5.82  ? ? ? ? ? ? 32 CYS A O   1 
+ATOM 228 C CB  . CYS A 1 32 ? 8.504  10.686 8.530  1.00 4.66  ? ? ? ? ? ? 32 CYS A CB  1 
+ATOM 229 S SG  . CYS A 1 32 ? 8.048  8.987  8.881  1.00 5.33  ? ? ? ? ? ? 32 CYS A SG  1 
+ATOM 230 N N   . ILE A 1 33 ? 9.793  13.410 9.173  1.00 6.02  ? ? ? ? ? ? 33 ILE A N   1 
+ATOM 231 C CA  . ILE A 1 33 ? 10.280 14.760 8.823  1.00 5.24  ? ? ? ? ? ? 33 ILE A CA  1 
+ATOM 232 C C   . ILE A 1 33 ? 11.346 14.658 7.743  1.00 5.16  ? ? ? ? ? ? 33 ILE A C   1 
+ATOM 233 O O   . ILE A 1 33 ? 11.971 13.583 7.552  1.00 7.19  ? ? ? ? ? ? 33 ILE A O   1 
+ATOM 234 C CB  . ILE A 1 33 ? 10.790 15.535 10.085 1.00 5.49  ? ? ? ? ? ? 33 ILE A CB  1 
+ATOM 235 C CG1 . ILE A 1 33 ? 12.059 14.803 10.671 1.00 6.85  ? ? ? ? ? ? 33 ILE A CG1 1 
+ATOM 236 C CG2 . ILE A 1 33 ? 9.684  15.686 11.138 1.00 6.45  ? ? ? ? ? ? 33 ILE A CG2 1 
+ATOM 237 C CD1 . ILE A 1 33 ? 12.733 15.676 11.781 1.00 8.94  ? ? ? ? ? ? 33 ILE A CD1 1 
+ATOM 238 N N   . ILE A 1 34 ? 11.490 15.773 7.038  1.00 5.52  ? ? ? ? ? ? 34 ILE A N   1 
+ATOM 239 C CA  . ILE A 1 34 ? 12.552 15.877 6.036  1.00 6.82  ? ? ? ? ? ? 34 ILE A CA  1 
+ATOM 240 C C   . ILE A 1 34 ? 13.590 16.917 6.560  1.00 6.92  ? ? ? ? ? ? 34 ILE A C   1 
+ATOM 241 O O   . ILE A 1 34 ? 13.168 18.006 6.945  1.00 9.22  ? ? ? ? ? ? 34 ILE A O   1 
+ATOM 242 C CB  . ILE A 1 34 ? 11.987 16.360 4.681  1.00 8.11  ? ? ? ? ? ? 34 ILE A CB  1 
+ATOM 243 C CG1 . ILE A 1 34 ? 10.914 15.338 4.163  1.00 9.59  ? ? ? ? ? ? 34 ILE A CG1 1 
+ATOM 244 C CG2 . ILE A 1 34 ? 13.131 16.517 3.629  1.00 9.73  ? ? ? ? ? ? 34 ILE A CG2 1 
+ATOM 245 C CD1 . ILE A 1 34 ? 10.151 16.024 2.938  1.00 13.41 ? ? ? ? ? ? 34 ILE A CD1 1 
+ATOM 246 N N   . ILE A 1 35 ? 14.856 16.493 6.536  1.00 7.06  ? ? ? ? ? ? 35 ILE A N   1 
+ATOM 247 C CA  . ILE A 1 35 ? 15.930 17.454 6.941  1.00 7.52  ? ? ? ? ? ? 35 ILE A CA  1 
+ATOM 248 C C   . ILE A 1 35 ? 16.913 17.550 5.819  1.00 6.63  ? ? ? ? ? ? 35 ILE A C   1 
+ATOM 249 O O   . ILE A 1 35 ? 17.097 16.660 4.970  1.00 7.90  ? ? ? ? ? ? 35 ILE A O   1 
+ATOM 250 C CB  . ILE A 1 35 ? 16.622 16.995 8.285  1.00 8.07  ? ? ? ? ? ? 35 ILE A CB  1 
+ATOM 251 C CG1 . ILE A 1 35 ? 17.360 15.651 8.067  1.00 9.41  ? ? ? ? ? ? 35 ILE A CG1 1 
+ATOM 252 C CG2 . ILE A 1 35 ? 15.592 16.974 9.434  1.00 9.46  ? ? ? ? ? ? 35 ILE A CG2 1 
+ATOM 253 C CD1 . ILE A 1 35 ? 18.298 15.206 9.219  1.00 9.85  ? ? ? ? ? ? 35 ILE A CD1 1 
+ATOM 254 N N   . PRO A 1 36 ? 17.664 18.669 5.806  1.00 8.07  ? ? ? ? ? ? 36 PRO A N   1 
+ATOM 255 C CA  . PRO A 1 36 ? 18.635 18.861 4.738  1.00 8.78  ? ? ? ? ? ? 36 PRO A CA  1 
+ATOM 256 C C   . PRO A 1 36 ? 19.925 18.042 4.949  1.00 8.31  ? ? ? ? ? ? 36 PRO A C   1 
+ATOM 257 O O   . PRO A 1 36 ? 20.593 17.742 3.945  1.00 9.09  ? ? ? ? ? ? 36 PRO A O   1 
+ATOM 258 C CB  . PRO A 1 36 ? 18.945 20.364 4.783  1.00 9.67  ? ? ? ? ? ? 36 PRO A CB  1 
+ATOM 259 C CG  . PRO A 1 36 ? 18.238 20.937 5.908  1.00 10.15 ? ? ? ? ? ? 36 PRO A CG  1 
+ATOM 260 C CD  . PRO A 1 36 ? 17.371 19.900 6.596  1.00 9.53  ? ? ? ? ? ? 36 PRO A CD  1 
+ATOM 261 N N   . GLY A 1 37 ? 20.172 17.730 6.217  1.00 8.48  ? ? ? ? ? ? 37 GLY A N   1 
+ATOM 262 C CA  . GLY A 1 37 ? 21.452 16.969 6.513  1.00 9.20  ? ? ? ? ? ? 37 GLY A CA  1 
+ATOM 263 C C   . GLY A 1 37 ? 21.143 15.478 6.427  1.00 10.41 ? ? ? ? ? ? 37 GLY A C   1 
+ATOM 264 O O   . GLY A 1 37 ? 20.138 15.023 5.878  1.00 12.06 ? ? ? ? ? ? 37 GLY A O   1 
+ATOM 265 N N   . ALA A 1 38 ? 22.055 14.701 7.032  1.00 9.24  ? ? ? ? ? ? 38 ALA A N   1 
+ATOM 266 C CA  . ALA A 1 38 ? 22.019 13.242 7.020  1.00 9.24  ? ? ? ? ? ? 38 ALA A CA  1 
+ATOM 267 C C   . ALA A 1 38 ? 21.944 12.628 8.396  1.00 9.60  ? ? ? ? ? ? 38 ALA A C   1 
+ATOM 268 O O   . ALA A 1 38 ? 21.869 11.387 8.435  1.00 13.65 ? ? ? ? ? ? 38 ALA A O   1 
+ATOM 269 C CB  . ALA A 1 38 ? 23.246 12.697 6.275  1.00 10.43 ? ? ? ? ? ? 38 ALA A CB  1 
+ATOM 270 N N   . THR A 1 39 ? 21.894 13.435 9.436  1.00 8.70  ? ? ? ? ? ? 39 THR A N   1 
+ATOM 271 C CA  . THR A 1 39 ? 21.936 12.911 10.809 1.00 9.46  ? ? ? ? ? ? 39 THR A CA  1 
+ATOM 272 C C   . THR A 1 39 ? 20.615 13.191 11.521 1.00 8.32  ? ? ? ? ? ? 39 THR A C   1 
+ATOM 273 O O   . THR A 1 39 ? 20.357 14.317 11.948 1.00 9.89  ? ? ? ? ? ? 39 THR A O   1 
+ATOM 274 C CB  . THR A 1 39 ? 23.131 13.601 11.593 1.00 10.72 ? ? ? ? ? ? 39 THR A CB  1 
+ATOM 275 O OG1 . THR A 1 39 ? 24.284 13.401 10.709 1.00 11.66 ? ? ? ? ? ? 39 THR A OG1 1 
+ATOM 276 C CG2 . THR A 1 39 ? 23.340 12.935 12.962 1.00 11.81 ? ? ? ? ? ? 39 THR A CG2 1 
+ATOM 277 N N   . CYS A 1 40 ? 19.827 12.110 11.642 1.00 7.64  ? ? ? ? ? ? 40 CYS A N   1 
+ATOM 278 C CA  . CYS A 1 40 ? 18.504 12.312 12.298 1.00 8.05  ? ? ? ? ? ? 40 CYS A CA  1 
+ATOM 279 C C   . CYS A 1 40 ? 18.684 12.451 13.784 1.00 7.63  ? ? ? ? ? ? 40 CYS A C   1 
+ATOM 280 O O   . CYS A 1 40 ? 19.533 11.718 14.362 1.00 9.64  ? ? ? ? ? ? 40 CYS A O   1 
+ATOM 281 C CB  . CYS A 1 40 ? 17.582 11.117 11.996 1.00 7.80  ? ? ? ? ? ? 40 CYS A CB  1 
+ATOM 282 S SG  . CYS A 1 40 ? 17.199 10.929 10.237 1.00 7.30  ? ? ? ? ? ? 40 CYS A SG  1 
+ATOM 283 N N   . PRO A 1 41 ? 17.880 13.266 14.426 1.00 8.00  ? ? ? ? ? ? 41 PRO A N   1 
+ATOM 284 C CA  . PRO A 1 41 ? 17.924 13.421 15.877 1.00 8.96  ? ? ? ? ? ? 41 PRO A CA  1 
+ATOM 285 C C   . PRO A 1 41 ? 17.392 12.206 16.594 1.00 9.06  ? ? ? ? ? ? 41 PRO A C   1 
+ATOM 286 O O   . PRO A 1 41 ? 16.652 11.368 16.033 1.00 8.82  ? ? ? ? ? ? 41 PRO A O   1 
+ATOM 287 C CB  . PRO A 1 41 ? 17.076 14.658 16.145 1.00 10.39 ? ? ? ? ? ? 41 PRO A CB  1 
+ATOM 288 C CG  . PRO A 1 41 ? 16.098 14.689 14.997 1.00 10.99 ? ? ? ? ? ? 41 PRO A CG  1 
+ATOM 289 C CD  . PRO A 1 41 ? 16.859 14.150 13.779 1.00 10.49 ? ? ? ? ? ? 41 PRO A CD  1 
+ATOM 290 N N   . GLY A 1 42 ? 17.728 12.124 17.884 1.00 7.55  ? ? ? ? ? ? 42 GLY A N   1 
+ATOM 291 C CA  . GLY A 1 42 ? 17.334 10.956 18.691 1.00 8.00  ? ? ? ? ? ? 42 GLY A CA  1 
+ATOM 292 C C   . GLY A 1 42 ? 15.875 10.688 18.871 1.00 7.22  ? ? ? ? ? ? 42 GLY A C   1 
+ATOM 293 O O   . GLY A 1 42 ? 15.434 9.550  19.166 1.00 8.41  ? ? ? ? ? ? 42 GLY A O   1 
+ATOM 294 N N   . ASP A 1 43 ? 15.036 11.747 18.715 1.00 5.54  ? ? ? ? ? ? 43 ASP A N   1 
+ATOM 295 C CA  . ASP A 1 43 ? 13.564 11.573 18.836 1.00 5.85  ? ? ? ? ? ? 43 ASP A CA  1 
+ATOM 296 C C   . ASP A 1 43 ? 12.936 11.227 17.470 1.00 5.87  ? ? ? ? ? ? 43 ASP A C   1 
+ATOM 297 O O   . ASP A 1 43 ? 11.720 11.040 17.428 1.00 7.29  ? ? ? ? ? ? 43 ASP A O   1 
+ATOM 298 C CB  . ASP A 1 43 ? 12.933 12.737 19.580 1.00 6.72  ? ? ? ? ? ? 43 ASP A CB  1 
+ATOM 299 C CG  . ASP A 1 43 ? 13.140 14.094 18.958 1.00 8.59  ? ? ? ? ? ? 43 ASP A CG  1 
+ATOM 300 O OD1 . ASP A 1 43 ? 14.109 14.303 18.212 1.00 9.59  ? ? ? ? ? ? 43 ASP A OD1 1 
+ATOM 301 O OD2 . ASP A 1 43 ? 12.267 14.963 19.265 1.00 11.45 ? ? ? ? ? ? 43 ASP A OD2 1 
+ATOM 302 N N   . TYR A 1 44 ? 13.725 11.174 16.425 1.00 5.22  ? ? ? ? ? ? 44 TYR A N   1 
+ATOM 303 C CA  . TYR A 1 44 ? 13.257 10.745 15.081 1.00 5.56  ? ? ? ? ? ? 44 TYR A CA  1 
+ATOM 304 C C   . TYR A 1 44 ? 14.275 9.687  14.612 1.00 4.61  ? ? ? ? ? ? 44 TYR A C   1 
+ATOM 305 O O   . TYR A 1 44 ? 14.930 9.862  13.568 1.00 6.04  ? ? ? ? ? ? 44 TYR A O   1 
+ATOM 306 C CB  . TYR A 1 44 ? 13.200 11.914 14.071 1.00 5.41  ? ? ? ? ? ? 44 TYR A CB  1 
+ATOM 307 C CG  . TYR A 1 44 ? 12.000 12.819 14.399 1.00 5.34  ? ? ? ? ? ? 44 TYR A CG  1 
+ATOM 308 C CD1 . TYR A 1 44 ? 12.119 13.853 15.332 1.00 6.59  ? ? ? ? ? ? 44 TYR A CD1 1 
+ATOM 309 C CD2 . TYR A 1 44 ? 10.775 12.617 13.762 1.00 5.94  ? ? ? ? ? ? 44 TYR A CD2 1 
+ATOM 310 C CE1 . TYR A 1 44 ? 11.045 14.675 15.610 1.00 5.97  ? ? ? ? ? ? 44 TYR A CE1 1 
+ATOM 311 C CE2 . TYR A 1 44 ? 9.676  13.433 14.048 1.00 5.17  ? ? ? ? ? ? 44 TYR A CE2 1 
+ATOM 312 C CZ  . TYR A 1 44 ? 9.802  14.456 14.996 1.00 5.96  ? ? ? ? ? ? 44 TYR A CZ  1 
+ATOM 313 O OH  . TYR A 1 44 ? 8.740  15.265 15.269 1.00 8.60  ? ? ? ? ? ? 44 TYR A OH  1 
+ATOM 314 N N   . ALA A 1 45 ? 14.342 8.640  15.422 1.00 4.76  ? ? ? ? ? ? 45 ALA A N   1 
+ATOM 315 C CA  . ALA A 1 45 ? 15.445 7.667  15.246 1.00 5.89  ? ? ? ? ? ? 45 ALA A CA  1 
+ATOM 316 C C   . ALA A 1 45 ? 15.171 6.533  14.280 1.00 6.67  ? ? ? ? ? ? 45 ALA A C   1 
+ATOM 317 O O   . ALA A 1 45 ? 16.093 5.705  14.039 1.00 7.56  ? ? ? ? ? ? 45 ALA A O   1 
+ATOM 318 C CB  . ALA A 1 45 ? 15.680 7.099  16.682 1.00 6.82  ? ? ? ? ? ? 45 ALA A CB  1 
+ATOM 319 N N   . ASN A 1 46 ? 13.966 6.502  13.739 1.00 5.80  ? ? ? ? ? ? 46 ASN A N   1 
+ATOM 320 C CA  . ASN A 1 46 ? 13.512 5.395  12.878 1.00 6.15  ? ? ? ? ? ? 46 ASN A CA  1 
+ATOM 321 C C   . ASN A 1 46 ? 13.311 5.853  11.455 1.00 6.61  ? ? ? ? ? ? 46 ASN A C   1 
+ATOM 322 O O   . ASN A 1 46 ? 13.733 6.929  11.026 1.00 7.18  ? ? ? ? ? ? 46 ASN A O   1 
+ATOM 323 C CB  . ASN A 1 46 ? 12.266 4.769  13.501 1.00 7.27  ? ? ? ? ? ? 46 ASN A CB  1 
+ATOM 324 C CG  . ASN A 1 46 ? 12.538 4.304  14.922 1.00 7.98  ? ? ? ? ? ? 46 ASN A CG  1 
+ATOM 325 O OD1 . ASN A 1 46 ? 11.982 4.849  15.886 1.00 11.00 ? ? ? ? ? ? 46 ASN A OD1 1 
+ATOM 326 N ND2 . ASN A 1 46 ? 13.407 3.298  15.015 1.00 10.32 ? ? ? ? ? ? 46 ASN A ND2 1 
+ATOM 327 O OXT . ASN A 1 46 ? 12.703 4.973  10.746 1.00 7.86  ? ? ? ? ? ? 46 ASN A OXT 1 
+# 
+loop_
+_pdbx_poly_seq_scheme.asym_id 
+_pdbx_poly_seq_scheme.entity_id 
+_pdbx_poly_seq_scheme.seq_id 
+_pdbx_poly_seq_scheme.mon_id 
+_pdbx_poly_seq_scheme.ndb_seq_num 
+_pdbx_poly_seq_scheme.pdb_seq_num 
+_pdbx_poly_seq_scheme.auth_seq_num 
+_pdbx_poly_seq_scheme.pdb_mon_id 
+_pdbx_poly_seq_scheme.auth_mon_id 
+_pdbx_poly_seq_scheme.pdb_strand_id 
+_pdbx_poly_seq_scheme.pdb_ins_code 
+_pdbx_poly_seq_scheme.hetero 
+A 1 1  THR 1  1  1  THR THR A . n 
+A 1 2  THR 2  2  2  THR THR A . n 
+A 1 3  CYS 3  3  3  CYS CYS A . n 
+A 1 4  CYS 4  4  4  CYS CYS A . n 
+A 1 5  PRO 5  5  5  PRO PRO A . n 
+A 1 6  SER 6  6  6  SER SER A . n 
+A 1 7  ILE 7  7  7  ILE ILE A . n 
+A 1 8  VAL 8  8  8  VAL VAL A . n 
+A 1 9  ALA 9  9  9  ALA ALA A . n 
+A 1 10 ARG 10 10 10 ARG ARG A . n 
+A 1 11 SER 11 11 11 SER SER A . n 
+A 1 12 ASN 12 12 12 ASN ASN A . n 
+A 1 13 PHE 13 13 13 PHE PHE A . n 
+A 1 14 ASN 14 14 14 ASN ASN A . n 
+A 1 15 VAL 15 15 15 VAL VAL A . n 
+A 1 16 CYS 16 16 16 CYS CYS A . n 
+A 1 17 ARG 17 17 17 ARG ARG A . n 
+A 1 18 LEU 18 18 18 LEU LEU A . n 
+A 1 19 PRO 19 19 19 PRO PRO A . n 
+A 1 20 GLY 20 20 20 GLY GLY A . n 
+A 1 21 THR 21 21 21 THR THR A . n 
+A 1 22 PRO 22 22 22 PRO PRO A . n 
+A 1 23 GLU 23 23 23 GLU GLU A . n 
+A 1 24 ALA 24 24 24 ALA ALA A . n 
+A 1 25 ILE 25 25 25 ILE ILE A . n 
+A 1 26 CYS 26 26 26 CYS CYS A . n 
+A 1 27 ALA 27 27 27 ALA ALA A . n 
+A 1 28 THR 28 28 28 THR THR A . n 
+A 1 29 TYR 29 29 29 TYR TYR A . n 
+A 1 30 THR 30 30 30 THR THR A . n 
+A 1 31 GLY 31 31 31 GLY GLY A . n 
+A 1 32 CYS 32 32 32 CYS CYS A . n 
+A 1 33 ILE 33 33 33 ILE ILE A . n 
+A 1 34 ILE 34 34 34 ILE ILE A . n 
+A 1 35 ILE 35 35 35 ILE ILE A . n 
+A 1 36 PRO 36 36 36 PRO PRO A . n 
+A 1 37 GLY 37 37 37 GLY GLY A . n 
+A 1 38 ALA 38 38 38 ALA ALA A . n 
+A 1 39 THR 39 39 39 THR THR A . n 
+A 1 40 CYS 40 40 40 CYS CYS A . n 
+A 1 41 PRO 41 41 41 PRO PRO A . n 
+A 1 42 GLY 42 42 42 GLY GLY A . n 
+A 1 43 ASP 43 43 43 ASP ASP A . n 
+A 1 44 TYR 44 44 44 TYR TYR A . n 
+A 1 45 ALA 45 45 45 ALA ALA A . n 
+A 1 46 ASN 46 46 46 ASN ASN A . n 
+# 
+_software.name             PROLSQ 
+_software.classification   refinement 
+_software.version          . 
+_software.citation_id      ? 
+_software.pdbx_ordinal     1 
+# 
+loop_
+_pdbx_version.entry_id 
+_pdbx_version.revision_date 
+_pdbx_version.major_version 
+_pdbx_version.minor_version 
+_pdbx_version.revision_type 
+_pdbx_version.details 
+1CRN 2008-03-24 3 2    'Version format compliance' 'compliance with PDB format V.3.15'          
+1CRN 2011-07-13 4 0000 'Version format compliance' 'compliance with PDB Exchange Dictionary V4' 
+1CRN 2012-07-11 4 0001 Other                       'Correct SCALE records, updated Header'      
+# 
+_pdbx_struct_assembly.id                   1 
+_pdbx_struct_assembly.details              author_defined_assembly 
+_pdbx_struct_assembly.method_details       ? 
+_pdbx_struct_assembly.oligomeric_details   monomeric 
+_pdbx_struct_assembly.oligomeric_count     1 
+# 
+_pdbx_struct_assembly_gen.assembly_id       1 
+_pdbx_struct_assembly_gen.oper_expression   1 
+_pdbx_struct_assembly_gen.asym_id_list      A 
+# 
+_pdbx_struct_oper_list.id                   1 
+_pdbx_struct_oper_list.type                 'identity operation' 
+_pdbx_struct_oper_list.name                 1_555 
+_pdbx_struct_oper_list.symmetry_operation   x,y,z 
+_pdbx_struct_oper_list.matrix[1][1]         1.0000000000 
+_pdbx_struct_oper_list.matrix[1][2]         0.0000000000 
+_pdbx_struct_oper_list.matrix[1][3]         0.0000000000 
+_pdbx_struct_oper_list.vector[1]            0.0000000000 
+_pdbx_struct_oper_list.matrix[2][1]         0.0000000000 
+_pdbx_struct_oper_list.matrix[2][2]         1.0000000000 
+_pdbx_struct_oper_list.matrix[2][3]         0.0000000000 
+_pdbx_struct_oper_list.vector[2]            0.0000000000 
+_pdbx_struct_oper_list.matrix[3][1]         0.0000000000 
+_pdbx_struct_oper_list.matrix[3][2]         0.0000000000 
+_pdbx_struct_oper_list.matrix[3][3]         1.0000000000 
+_pdbx_struct_oper_list.vector[3]            0.0000000000 
+# 
+_pdbx_entry_details.entry_id             1CRN 
+_pdbx_entry_details.compound_details     
+;THE SECONDARY STRUCTURE SPECIFICATIONS ARE THOSE DEFINED
+IN REFERENCE 1 ABOVE AND DEPEND ON PARTICULAR DEFINITIONS
+THAT MAY AFFECT THE DETERMINATION OF END POINTS.  PLEASE
+CONSULT THE PRIMARY REFERENCE AND EXAMINE STRUCTURAL
+DETAILS SUCH AS HYDROGEN BONDING AND CONFORMATION ANGLES
+WHEN MAKING USE OF THE SPECIFICATIONS.
+;
+_pdbx_entry_details.source_details       ? 
+_pdbx_entry_details.nonpolymer_details   ? 
+_pdbx_entry_details.sequence_details     ? 
+# 
+loop_
+_pdbx_validate_rmsd_angle.id 
+_pdbx_validate_rmsd_angle.PDB_model_num 
+_pdbx_validate_rmsd_angle.auth_atom_id_1 
+_pdbx_validate_rmsd_angle.auth_asym_id_1 
+_pdbx_validate_rmsd_angle.auth_comp_id_1 
+_pdbx_validate_rmsd_angle.auth_seq_id_1 
+_pdbx_validate_rmsd_angle.PDB_ins_code_1 
+_pdbx_validate_rmsd_angle.label_alt_id_1 
+_pdbx_validate_rmsd_angle.auth_atom_id_2 
+_pdbx_validate_rmsd_angle.auth_asym_id_2 
+_pdbx_validate_rmsd_angle.auth_comp_id_2 
+_pdbx_validate_rmsd_angle.auth_seq_id_2 
+_pdbx_validate_rmsd_angle.PDB_ins_code_2 
+_pdbx_validate_rmsd_angle.label_alt_id_2 
+_pdbx_validate_rmsd_angle.auth_atom_id_3 
+_pdbx_validate_rmsd_angle.auth_asym_id_3 
+_pdbx_validate_rmsd_angle.auth_comp_id_3 
+_pdbx_validate_rmsd_angle.auth_seq_id_3 
+_pdbx_validate_rmsd_angle.PDB_ins_code_3 
+_pdbx_validate_rmsd_angle.label_alt_id_3 
+_pdbx_validate_rmsd_angle.angle_deviation 
+1 1 NE A ARG 10 ? ? CZ A ARG 10 ? ? NH2 A ARG 10 ? ? -3.6 
+2 1 CB A TYR 29 ? ? CG A TYR 29 ? ? CD1 A TYR 29 ? ? -4.7 
+# 

A diferenza do arquivo foi suprimida porque é demasiado grande
+ 422 - 271
package-lock.json


+ 31 - 26
package.json

@@ -12,9 +12,10 @@
   },
   },
   "scripts": {
   "scripts": {
     "lint": "tslint src/**/*.ts",
     "lint": "tslint src/**/*.ts",
-    "build": "cpx \"src/**/*.{vert,frag,glsl,scss,woff,woff2,ttf,otf,eot,svg,html}\" build/node_modules/ && tsc",
+    "build": "cpx \"src/**/*.{vert,frag,glsl,scss,woff,woff2,ttf,otf,eot,svg,html,gql}\" build/node_modules/ && tsc",
     "watch": "tsc -watch",
     "watch": "tsc -watch",
-    "watch-extra": "cpx \"src/**/*.{vert,frag,glsl,scss,woff,woff2,ttf,otf,eot,svg,html}\" build/node_modules/ --watch",
+    "watch-extra": "cpx \"src/**/*.{vert,frag,glsl,scss,woff,woff2,ttf,otf,eot,svg,html,gql}\" build/node_modules/ --watch",
+    "watch-all-win": "start cmd /K npm run watch & start cmd /K npm run watch-extra & start cmd /K npm run watch-viewer & start http-server -p 1338",
     "test": "jest",
     "test": "jest",
     "build-viewer": "webpack build/node_modules/apps/viewer/index.js --mode development -o build/viewer/index.js",
     "build-viewer": "webpack build/node_modules/apps/viewer/index.js --mode development -o build/viewer/index.js",
     "watch-viewer": "webpack build/node_modules/apps/viewer/index.js -w --mode development -o build/viewer/index.js",
     "watch-viewer": "webpack build/node_modules/apps/viewer/index.js -w --mode development -o build/viewer/index.js",
@@ -69,44 +70,48 @@
     "@types/argparse": "^1.0.34",
     "@types/argparse": "^1.0.34",
     "@types/benchmark": "^1.0.31",
     "@types/benchmark": "^1.0.31",
     "@types/compression": "0.0.36",
     "@types/compression": "0.0.36",
-    "@types/express": "^4.11.1",
-    "@types/jest": "^23.0.0",
-    "@types/node": "~10.1.2",
-    "@types/node-fetch": "^2.1.1",
-    "@types/react": "^16.3.16",
-    "@types/react-dom": "^16.0.5",
+    "@types/express": "^4.16.0",
+    "@types/jest": "^23.3.1",
+    "@types/node": "^10.5.7",
+    "@types/node-fetch": "^2.1.2",
+    "@types/react": "^16.4.8",
+    "@types/react-dom": "^16.0.7",
     "benchmark": "^2.1.4",
     "benchmark": "^2.1.4",
-    "copyfiles": "^2.0.0",
     "cpx": "^1.5.0",
     "cpx": "^1.5.0",
-    "css-loader": "^0.28.11",
+    "css-loader": "^1.0.0",
     "extra-watch-webpack-plugin": "^1.0.3",
     "extra-watch-webpack-plugin": "^1.0.3",
-    "extract-text-webpack-plugin": "^4.0.0-beta.0",
     "file-loader": "^1.1.11",
     "file-loader": "^1.1.11",
     "glslify-import": "^3.1.0",
     "glslify-import": "^3.1.0",
     "glslify-loader": "^1.0.2",
     "glslify-loader": "^1.0.2",
-    "jest": "^23.1.0",
+    "graphql-code-generator": "^0.10.6",
+    "graphql-codegen-typescript-template": "^0.10.6",
+    "graphql-tag": "^2.9.2",
+    "jest": "^23.4.2",
     "jest-raw-loader": "^1.0.1",
     "jest-raw-loader": "^1.0.1",
-    "node-sass": "^4.9.0",
+    "mini-css-extract-plugin": "^0.4.1",
+    "node-sass": "^4.9.2",
     "raw-loader": "^0.5.1",
     "raw-loader": "^0.5.1",
     "resolve-url-loader": "^2.3.0",
     "resolve-url-loader": "^2.3.0",
-    "sass-loader": "^7.0.2",
-    "style-loader": "^0.21.0",
-    "ts-jest": "^22.4.6",
-    "tslint": "^5.10.0",
-    "typescript": "^2.9.1",
-    "uglify-js": "^3.4.0",
+    "sass-loader": "^7.1.0",
+    "style-loader": "^0.22.0",
+    "ts-jest": "^23.1.3",
+    "tslint": "^5.11.0",
+    "typescript": "^3.0.1",
+    "uglify-js": "^3.4.6",
     "util.promisify": "^1.0.0",
     "util.promisify": "^1.0.0",
-    "webpack": "^4.10.2",
-    "webpack-cli": "^3.0.1"
+    "webpack": "^4.16.5",
+    "webpack-cli": "^3.1.0"
   },
   },
   "dependencies": {
   "dependencies": {
     "argparse": "^1.0.10",
     "argparse": "^1.0.10",
-    "compression": "^1.7.2",
+    "compression": "^1.7.3",
     "express": "^4.16.3",
     "express": "^4.16.3",
+    "graphql": "^0.13.2",
+    "graphql-request": "^1.8.1",
     "immutable": "^4.0.0-rc.9",
     "immutable": "^4.0.0-rc.9",
-    "node-fetch": "^2.1.2",
-    "react": "^16.4.0",
-    "react-dom": "^16.4.0",
-    "rxjs": "^6.2.0"
+    "node-fetch": "^2.2.0",
+    "react": "^16.4.2",
+    "react-dom": "^16.4.2",
+    "rxjs": "^6.2.2"
   }
   }
 }
 }

+ 46 - 21
src/apps/cif2bcif/converter.ts

@@ -8,36 +8,61 @@ import CIF, { CifCategory } from 'mol-io/reader/cif'
 import { CifWriter } from 'mol-io/writer/cif'
 import { CifWriter } from 'mol-io/writer/cif'
 import * as fs from 'fs'
 import * as fs from 'fs'
 import classify from './field-classifier'
 import classify from './field-classifier'
+import { Progress, Task, RuntimeContext } from 'mol-task';
 
 
-async function getCIF(path: string) {
+function showProgress(p: Progress) {
+    process.stdout.write(`\r${new Array(80).join(' ')}`);
+    process.stdout.write(`\r${Progress.format(p)}`);
+}
+
+async function getCIF(ctx: RuntimeContext, path: string) {
     const str = fs.readFileSync(path, 'utf8');
     const str = fs.readFileSync(path, 'utf8');
-    const parsed = await CIF.parseText(str).run();
+    const parsed = await CIF.parseText(str).runInContext(ctx);
     if (parsed.isError) {
     if (parsed.isError) {
         throw new Error(parsed.toString());
         throw new Error(parsed.toString());
     }
     }
     return parsed.result;
     return parsed.result;
 }
 }
 
 
-function getCategoryInstanceProvider(cat: CifCategory): CifWriter.Category.Provider {
-    return function (ctx: any) {
-        return {
-            data: cat,
-            name: cat.name,
-            fields: cat.fieldNames.map(n => classify(n, cat.getField(n)!)),
-            rowCount: cat.rowCount
-        };
-    }
+function getCategoryInstanceProvider(cat: CifCategory, fields: CifWriter.Field[]): CifWriter.Category {
+    return {
+        name: cat.name,
+        instance: () => ({ data: cat, fields, rowCount: cat.rowCount })
+    };
 }
 }
 
 
-export default async function convert(path: string, asText = false) {
-    const cif = await getCIF(path);
+export default function convert(path: string, asText = false) {
+    return Task.create<Uint8Array>('BinaryCIF', async ctx => {
+        const cif = await getCIF(ctx, path);
 
 
-    const encoder = CifWriter.createEncoder({ binary: !asText, encoderName: 'mol* cif2bcif' });
-    for (const b of cif.blocks) {
-        encoder.startDataBlock(b.header);
-        for (const c of b.categoryNames) {
-            encoder.writeCategory(getCategoryInstanceProvider(b.categories[c]));
+        const encoder = CifWriter.createEncoder({ binary: !asText, encoderName: 'mol* cif2bcif' });
+
+        let maxProgress = 0;
+        for (const b of cif.blocks) {
+            maxProgress += b.categoryNames.length;
+            for (const c of b.categoryNames) maxProgress += b.categories[c].fieldNames.length;
         }
         }
-    }
-    return encoder.getData();
-}
+
+        let current = 0;
+        for (const b of cif.blocks) {
+            encoder.startDataBlock(b.header);
+            for (const c of b.categoryNames) {
+                const cat = b.categories[c];
+                const fields: CifWriter.Field[] = [];
+                for (const f of cat.fieldNames) {
+                    fields.push(classify(f, cat.getField(f)!))
+                    current++;
+                    if (ctx.shouldUpdate) await ctx.update({ message: 'Encoding...', current, max: maxProgress });
+                }
+
+                encoder.writeCategory(getCategoryInstanceProvider(b.categories[c], fields));
+                current++;
+                if (ctx.shouldUpdate) await ctx.update({ message: 'Encoding...', current, max: maxProgress });
+            }
+        }
+        await ctx.update('Exporting...');
+        const ret = encoder.getData() as Uint8Array;
+        await ctx.update('Done.');
+        return ret;
+    }).run(showProgress, 250);
+}

+ 174 - 3
src/apps/cif2bcif/field-classifier.ts

@@ -7,9 +7,175 @@
 import { Column } from 'mol-data/db'
 import { Column } from 'mol-data/db'
 import { CifField } from 'mol-io/reader/cif/data-model'
 import { CifField } from 'mol-io/reader/cif/data-model'
 import { CifWriter } from 'mol-io/writer/cif'
 import { CifWriter } from 'mol-io/writer/cif'
+import { ArrayEncoder, ArrayEncoding as E } from 'mol-io/common/binary-cif';
+
+namespace IntClassifier {
+    function packSize(value: number, upperLimit: number) {
+        return value >= 0
+            ? Math.ceil((value + 1) / upperLimit)
+            : Math.ceil((value + 1) / (-upperLimit - 1));
+    }
+
+    type IntColumnInfo = { signed: boolean, limit8: number, limit16: number };
+
+    function getInfo(data: number[]): IntColumnInfo {
+        let signed = false;
+        for (let i = 0, n = data.length; i < n; i++) {
+            if (data[i] < 0) {
+                signed = true;
+                break;
+            }
+        }
+        return signed ? { signed, limit8: 0x7F, limit16: 0x7FFF } : { signed, limit8: 0xFF, limit16: 0xFFFF };
+    }
+
+    type SizeInfo = { pack8: number, pack16: number, count: number }
+    function SizeInfo(): SizeInfo { return { pack8: 0, pack16: 0, count: 0 } };
+
+    function incSize({ limit8, limit16 }: IntColumnInfo, info: SizeInfo, value: number) {
+        info.pack8 += packSize(value, limit8);
+        info.pack16 += packSize(value, limit16);
+        info.count += 1;
+    }
+
+    function incSizeSigned(info: SizeInfo, value: number) {
+        info.pack8 += packSize(value, 0x7F);
+        info.pack16 += packSize(value, 0x7FFF);
+        info.count += 1;
+    }
+
+    function byteSize(info: SizeInfo) {
+        if (info.count * 4 < info.pack16 * 2) return { length: info.count * 4, elem: 4 };
+        if (info.pack16 * 2 < info.pack8) return { length: info.pack16 * 2, elem: 2 };
+        return { length: info.pack8, elem: 1 };
+    }
+
+    function packingSize(data: number[], info: IntColumnInfo) {
+        const size = SizeInfo();
+        for (let i = 0, n = data.length; i < n; i++) {
+            incSize(info, size, data[i]);
+        }
+        return { ...byteSize(size), kind: 'pack' };
+    }
+
+    function deltaSize(data: number[], info: IntColumnInfo) {
+        const size = SizeInfo();
+        let prev = data[0];
+        for (let i = 1, n = data.length; i < n; i++) {
+            incSizeSigned(size, data[i] - prev);
+            prev = data[i];
+        }
+        return { ...byteSize(size), kind: 'delta' };
+    }
+
+    function rleSize(data: number[], info: IntColumnInfo) {
+        const size = SizeInfo();
+        let run = 1;
+        for (let i = 1, n = data.length; i < n; i++) {
+            if (data[i - 1] !== data[i]) {
+                incSize(info, size, data[i - 1]);
+                incSize(info, size, run);
+                run = 1;
+            } else {
+                run++;
+            }
+        }
+        incSize(info, size, data[data.length - 1]);
+        incSize(info, size, run);
+
+        return { ...byteSize(size), kind: 'rle' };
+    }
+
+    function deltaRleSize(data: number[], info: IntColumnInfo) {
+        const size = SizeInfo();
+        let run = 1, prev = 0, prevValue = 0;
+        for (let i = 1, n = data.length; i < n; i++) {
+            const v = data[i] - prev;
+            if (prevValue !== v) {
+                incSizeSigned(size, prevValue);
+                incSizeSigned(size, run);
+                run = 1;
+            } else {
+                run++;
+            }
+            prevValue = v;
+            prev = data[i];
+        }
+        incSizeSigned(size, prevValue);
+        incSizeSigned(size, run);
+
+        return { ...byteSize(size), kind: 'delta-rle' };
+    }
+
+    export function getSize(data: number[]) {
+        const info = getInfo(data);
+        const sizes = [packingSize(data, info), rleSize(data, info), deltaSize(data, info), deltaRleSize(data, info)];
+        sizes.sort((a, b) => a.length - b.length);
+        return sizes;
+    }
+
+    export function classify(data: number[], name: string): ArrayEncoder {
+        if (data.length < 2) return E.by(E.byteArray);
+
+        const sizes = getSize(data);
+        const size = sizes[0];
+        // console.log(`${name}: ${size.kind} ${size.length}b ${data.length}`);
+        // console.log(`${name}: ${sizes.map(s => `${s.kind}: ${s.length}b`).join(' | ')}`);
+
+        switch (size.kind) {
+            case 'pack': return E.by(E.integerPacking);
+            case 'rle': return E.by(E.runLength).and(E.integerPacking);
+            case 'delta': return E.by(E.delta).and(E.integerPacking);
+            case 'delta-rle': return E.by(E.delta).and(E.runLength).and(E.integerPacking);
+        }
+
+        throw 'bug';
+    }
+}
+
+namespace FloatClassifier {
+    const delta = 1e-6;
+    function digitCount(v: number) {
+        let m = 1;
+        for (let i = 0; i < 5; i++) {
+            const r = Math.round(m * v) / m;
+            if (Math.abs(v - r) < delta) return m;
+            m *= 10;
+        }
+        return 10000;
+    }
+
+    export function classify(data: number[], name: string) {
+        // if a vector/matrix, do not reduce precision
+        if (name.indexOf('[') > 0) return { encoder: E.by(E.byteArray), typedArray: Float64Array };
+
+        let dc = 10;
+        for (let i = 0, n = data.length; i < n; i++) dc = Math.max(dc, digitCount(data[i]));
+
+        if (dc >= 10000) return { encoder: E.by(E.byteArray), typedArray: Float64Array };
+
+        const intArray = new Int32Array(data.length);
+        for (let i = 0, n = data.length; i < n; i++) intArray[i] = data[i] * dc;
+
+        const sizes = IntClassifier.getSize(intArray as any);
+        const size = sizes[0];
+
+        // console.log(`>> ${name}: ${size.kind} ${size.length}b ${data.length} x${dc}`);
+        // console.log(`   ${name}: ${sizes.map(s => `${s.kind}: ${s.length}b`).join(' | ')}`);
+
+        switch (size.kind) {
+            case 'pack': return { encoder: E.by(E.fixedPoint(dc)).and(E.integerPacking), typedArray: Float32Array };
+            case 'rle': return { encoder: E.by(E.fixedPoint(dc)).and(E.runLength).and(E.integerPacking), typedArray: Float32Array };
+            case 'delta': return { encoder: E.by(E.fixedPoint(dc)).and(E.delta).and(E.integerPacking), typedArray: Float32Array };
+            case 'delta-rle': return { encoder: E.by(E.fixedPoint(dc)).and(E.delta).and(E.runLength).and(E.integerPacking), typedArray: Float32Array };
+        }
+
+        throw 'bug';
+    }
+}
 
 
 const intRegex = /^-?\d+$/
 const intRegex = /^-?\d+$/
-const floatRegex = /^-?(([0-9]+)[.]?|([0-9]*[.][0-9]+))([(][0-9]+[)])?([eE][+-]?[0-9]+)?/
+const floatRegex = /^-?(([0-9]+)[.]?|([0-9]*[.][0-9]+))([(][0-9]+[)])?([eE][+-]?[0-9]+)?$/
 
 
 // Classify a cif field as str, int or float based the data it contains.
 // Classify a cif field as str, int or float based the data it contains.
 // To classify a field as int or float all items are checked.
 // To classify a field as int or float all items are checked.
@@ -25,8 +191,13 @@ function classify(name: string, field: CifField): CifWriter.Field {
     }
     }
 
 
     if (hasString) return { name, type: CifWriter.Field.Type.Str, value: field.str, valueKind: field.valueKind };
     if (hasString) return { name, type: CifWriter.Field.Type.Str, value: field.str, valueKind: field.valueKind };
-    if (floatCount > 0) return { name, type: CifWriter.Field.Type.Float, value: field.float, valueKind: field.valueKind };
-    return { name, type: CifWriter.Field.Type.Int, value: field.int, valueKind: field.valueKind };
+    if (floatCount > 0) {
+        const { encoder, typedArray } = FloatClassifier.classify(field.toFloatArray({ array: Float64Array }) as number[], name)
+        return CifWriter.Field.float(name, field.float, { valueKind: field.valueKind, encoder, typedArray });
+    } else {
+        const encoder = IntClassifier.classify(field.toIntArray({ array: Int32Array }) as number[], name);
+        return CifWriter.Field.int(name, field.int, { valueKind: field.valueKind, encoder, typedArray: Int32Array });
+    }
 }
 }
 
 
 export default classify;
 export default classify;

+ 4 - 4
src/apps/domain-annotation-server/mapping.ts

@@ -7,7 +7,7 @@
 import { Table } from 'mol-data/db'
 import { Table } from 'mol-data/db'
 import { CifWriter } from 'mol-io/writer/cif'
 import { CifWriter } from 'mol-io/writer/cif'
 import * as S from './schemas'
 import * as S from './schemas'
-import { getCategoryInstanceProvider } from './utils'
+//import { getCategoryInstanceProvider } from './utils'
 
 
 export default function create(allData: any) {
 export default function create(allData: any) {
     const mols = Object.keys(allData);
     const mols = Object.keys(allData);
@@ -21,7 +21,7 @@ export default function create(allData: any) {
     const sources = getSources(data);
     const sources = getSources(data);
     if (!sources._rowCount) return enc.getData();
     if (!sources._rowCount) return enc.getData();
 
 
-    enc.writeCategory(getCategoryInstanceProvider(`pdbx_domain_annotation_sources`, sources));
+    enc.writeCategory({ name: `pdbx_domain_annotation_sources`, instance: () => CifWriter.Category.ofTable(sources) });
 
 
     for (const cat of Object.keys(S.categories)) {
     for (const cat of Object.keys(S.categories)) {
         writeDomain(enc, getDomain(cat, (S.categories as any)[cat], data));
         writeDomain(enc, getDomain(cat, (S.categories as any)[cat], data));
@@ -38,8 +38,8 @@ type MappingRow = Table.Row<S.mapping>;
 
 
 function writeDomain(enc: CifWriter.Encoder, domain: DomainAnnotation | undefined) {
 function writeDomain(enc: CifWriter.Encoder, domain: DomainAnnotation | undefined) {
     if (!domain) return;
     if (!domain) return;
-    enc.writeCategory(getCategoryInstanceProvider(`pdbx_${domain.name}_domain_annotation`, domain.domains));
-    enc.writeCategory(getCategoryInstanceProvider(`pdbx_${domain.name}_domain_mapping`, domain.mappings));
+    enc.writeCategory({ name: `pdbx_${domain.name}_domain_annotation`, instance: () =>  CifWriter.Category.ofTable(domain.domains) });
+    enc.writeCategory({ name: `pdbx_${domain.name}_domain_mapping`, instance: () => CifWriter.Category.ofTable(domain.mappings) });
 }
 }
 
 
 function getSources(data: any): Table<S.Sources> {
 function getSources(data: any): Table<S.Sources> {

+ 0 - 37
src/apps/domain-annotation-server/utils.ts

@@ -1,37 +0,0 @@
-/**
- * Copyright (c) 2017 mol* contributors, licensed under MIT, See LICENSE file for more info.
- *
- * @author David Sehnal <david.sehnal@gmail.com>
- */
-
-import { Table } from 'mol-data/db'
-import { CifWriter } from 'mol-io/writer/cif'
-
-function columnValue(k: string) {
-    return (i: number, d: any) => d[k].value(i);
-}
-
-function columnValueKind(k: string) {
-    return (i: number, d: any) => d[k].valueKind(i);
-}
-
-function ofSchema(schema: Table.Schema) {
-    const fields: CifWriter.Field[] = [];
-    for (const k of Object.keys(schema)) {
-        const t = schema[k];
-        const type: any = t.valueType === 'str' ? CifWriter.Field.Type.Str : t.valueType === 'int' ? CifWriter.Field.Type.Int : CifWriter.Field.Type.Float;
-        fields.push({ name: k, type, value: columnValue(k), valueKind: columnValueKind(k) })
-    }
-    return fields;
-}
-
-export function getCategoryInstanceProvider(name: string, table: Table<any>): CifWriter.Category.Provider {
-    return () => {
-        return {
-            data: table,
-            name,
-            fields: ofSchema(table._schema),
-            rowCount: table._rowCount
-        };
-    }
-}

+ 8 - 0
src/apps/schema-generator/util/cif-dic.ts

@@ -155,6 +155,7 @@ const COMMA_SEPARATED_LIST_FIELDS = [
     '_em_diffraction.tilt_angle_list', // 20,40,50,55
     '_em_diffraction.tilt_angle_list', // 20,40,50,55
     '_em_entity_assembly.entity_id_list',
     '_em_entity_assembly.entity_id_list',
     '_entity.pdbx_ec',
     '_entity.pdbx_ec',
+    '_entity_poly.pdbx_strand_id', // A,B
     '_pdbx_depui_entry_details.experimental_methods',
     '_pdbx_depui_entry_details.experimental_methods',
     '_pdbx_depui_entry_details.requested_accession_types',
     '_pdbx_depui_entry_details.requested_accession_types',
     '_pdbx_soln_scatter_model.software_list', // INSIGHT II, HOMOLOGY, DISCOVERY, BIOPOLYMER, DELPHI
     '_pdbx_soln_scatter_model.software_list', // INSIGHT II, HOMOLOGY, DISCOVERY, BIOPOLYMER, DELPHI
@@ -178,6 +179,10 @@ const SPACE_SEPARATED_LIST_FIELDS = [
     '_pdbx_soln_scatter.data_analysis_software_list', // SCTPL5 GNOM
     '_pdbx_soln_scatter.data_analysis_software_list', // SCTPL5 GNOM
 ];
 ];
 
 
+const SEMICOLON_SEPARATED_LIST_FIELDS = [
+    '_chem_comp.pdbx_synonyms' // GLYCERIN; PROPANE-1,2,3-TRIOL
+]
+
 export function generateSchema (frames: CifFrame[]) {
 export function generateSchema (frames: CifFrame[]) {
     const schema: Database = {}
     const schema: Database = {}
 
 
@@ -250,6 +255,9 @@ export function generateSchema (frames: CifFrame[]) {
                         } else if (SPACE_SEPARATED_LIST_FIELDS.includes(d.header)) {
                         } else if (SPACE_SEPARATED_LIST_FIELDS.includes(d.header)) {
                             fieldType = { 'list': [ 'str', ' ' ] };
                             fieldType = { 'list': [ 'str', ' ' ] };
                             console.log(`space separated: ${d.header}`)
                             console.log(`space separated: ${d.header}`)
+                        } else if (SEMICOLON_SEPARATED_LIST_FIELDS.includes(d.header)) {
+                            fieldType = { 'list': [ 'str', ';' ] };
+                            console.log(`space separated: ${d.header}`)
                         }
                         }
                     }
                     }
                     fields[itemName] = fieldType
                     fields[itemName] = fieldType

+ 143 - 71
src/apps/structure-info/model.ts

@@ -8,51 +8,47 @@
 import * as argparse from 'argparse'
 import * as argparse from 'argparse'
 require('util.promisify').shim();
 require('util.promisify').shim();
 
 
-// import { Table } from 'mol-data/db'
-import CIF from 'mol-io/reader/cif'
-import { Model, Structure, Element, Unit, Queries } from 'mol-model/structure'
+import { CifFrame } from 'mol-io/reader/cif'
+import { Model, Structure, StructureElement, Unit, Format, StructureProperties, UnitRing } from 'mol-model/structure'
 // import { Run, Progress } from 'mol-task'
 // import { Run, Progress } from 'mol-task'
 import { OrderedSet } from 'mol-data/int';
 import { OrderedSet } from 'mol-data/int';
-import { Table } from 'mol-data/db';
-import { mmCIF_Database } from 'mol-io/reader/cif/schema/mmcif';
 import { openCif, downloadCif } from './helpers';
 import { openCif, downloadCif } from './helpers';
-import { BitFlags } from 'mol-util';
-import { SecondaryStructureType } from 'mol-model/structure/model/types';
+import { Vec3 } from 'mol-math/linear-algebra';
 
 
 
 
 async function downloadFromPdb(pdb: string) {
 async function downloadFromPdb(pdb: string) {
     // `https://files.rcsb.org/download/${pdb}.cif`
     // `https://files.rcsb.org/download/${pdb}.cif`
     const parsed = await downloadCif(`http://www.ebi.ac.uk/pdbe/static/entry/${pdb}_updated.cif`, false);
     const parsed = await downloadCif(`http://www.ebi.ac.uk/pdbe/static/entry/${pdb}_updated.cif`, false);
-    return CIF.schema.mmCIF(parsed.blocks[0]);
+    return parsed.blocks[0];
 }
 }
 
 
 async function readPdbFile(path: string) {
 async function readPdbFile(path: string) {
     const parsed = await openCif(path);
     const parsed = await openCif(path);
-    return CIF.schema.mmCIF(parsed.blocks[0]);
+    return parsed.blocks[0];
 }
 }
 
 
 export function atomLabel(model: Model, aI: number) {
 export function atomLabel(model: Model, aI: number) {
-    const { atoms, residues, chains, residueSegments, chainSegments } = model.atomicHierarchy
+    const { atoms, residues, chains, residueAtomSegments, chainAtomSegments } = model.atomicHierarchy
     const { label_atom_id } = atoms
     const { label_atom_id } = atoms
     const { label_comp_id, label_seq_id } = residues
     const { label_comp_id, label_seq_id } = residues
     const { label_asym_id } = chains
     const { label_asym_id } = chains
-    const rI = residueSegments.segmentMap[aI]
-    const cI = chainSegments.segmentMap[aI]
+    const rI = residueAtomSegments.index[aI]
+    const cI = chainAtomSegments.index[aI]
     return `${label_asym_id.value(cI)} ${label_comp_id.value(rI)} ${label_seq_id.value(rI)} ${label_atom_id.value(aI)}`
     return `${label_asym_id.value(cI)} ${label_comp_id.value(rI)} ${label_seq_id.value(rI)} ${label_atom_id.value(aI)}`
 }
 }
 
 
 export function residueLabel(model: Model, rI: number) {
 export function residueLabel(model: Model, rI: number) {
-    const { residues, chains, residueSegments, chainSegments } = model.atomicHierarchy
+    const { residues, chains, residueAtomSegments, chainAtomSegments } = model.atomicHierarchy
     const { label_comp_id, label_seq_id } = residues
     const { label_comp_id, label_seq_id } = residues
     const { label_asym_id } = chains
     const { label_asym_id } = chains
-    const cI = chainSegments.segmentMap[residueSegments.segments[rI]]
+    const cI = chainAtomSegments.index[residueAtomSegments.offsets[rI]]
     return `${label_asym_id.value(cI)} ${label_comp_id.value(rI)} ${label_seq_id.value(rI)}`
     return `${label_asym_id.value(cI)} ${label_comp_id.value(rI)} ${label_seq_id.value(rI)}`
 }
 }
 
 
 export function printSecStructure(model: Model) {
 export function printSecStructure(model: Model) {
-    console.log('Secondary Structure\n=============');
+    console.log('\nSecondary Structure\n=============');
     const { residues } = model.atomicHierarchy;
     const { residues } = model.atomicHierarchy;
-    const { type, key } = model.properties.secondaryStructure;
+    const { key, elements } = model.properties.secondaryStructure;
 
 
     const count = residues._rowCount;
     const count = residues._rowCount;
     let rI = 0;
     let rI = 0;
@@ -61,57 +57,95 @@ export function printSecStructure(model: Model) {
         while (rI < count && key[start] === key[rI]) rI++;
         while (rI < count && key[start] === key[rI]) rI++;
         rI--;
         rI--;
 
 
-        if (BitFlags.has(type[start], SecondaryStructureType.Flag.Beta)) {
-            console.log(`Sheet: ${residueLabel(model, start)} - ${residueLabel(model, rI)} (key ${key[start]})`);
-        } else if (BitFlags.has(type[start], SecondaryStructureType.Flag.Helix)) {
-            console.log(`Helix: ${residueLabel(model, start)} - ${residueLabel(model, rI)} (key ${key[start]})`);
-        }
-
+        const e = elements[key[start]];
+        if (e.kind !== 'none') console.log(`${e.kind}: ${residueLabel(model, start)} - ${residueLabel(model, rI)}`);
         rI++;
         rI++;
     }
     }
 }
 }
 
 
-export function printBonds(structure: Structure) {
-    for (const unit of structure.units) {
-        if (!Unit.isAtomic(unit)) continue;
+export function printLinks(structure: Structure, showIntra: boolean, showInter: boolean) {
+    if (showIntra) {
+        console.log('\nIntra Unit Links\n=============');
+        for (const unit of structure.units) {
+            if (!Unit.isAtomic(unit)) continue;
 
 
-        const elements = unit.elements;
-        const { count, offset, neighbor } = unit.bonds;
-        const { model }  = unit;
+            const elements = unit.elements;
+            const { a, b, edgeCount } = unit.links;
+            const { model } = unit;
+
+            if (!edgeCount) continue;
+
+            for (let bI = 0, _bI = edgeCount * 2; bI < _bI; bI++) {
+                const x = a[bI], y = b[bI];
+                if (x >= y) continue;
+                console.log(`${atomLabel(model, elements[x])} -- ${atomLabel(model, elements[y])}`);
+            }
+        }
+    }
 
 
-        if (!count) continue;
+    if (showInter) {
+        console.log('\nInter Unit Links\n=============');
+        const links = structure.links;
+        for (const unit of structure.units) {
+            if (!Unit.isAtomic(unit)) continue;
 
 
-        for (let j = 0; j < offset.length - 1; ++j) {
-            const start = offset[j];
-            const end = offset[j + 1];
+            for (const pairLinks of links.getLinkedUnits(unit)) {
+                if (!pairLinks.areUnitsOrdered || pairLinks.bondCount === 0) continue;
 
 
-            if (end <= start) continue;
+                const { unitA, unitB } = pairLinks;
+                console.log(`${pairLinks.unitA.id} - ${pairLinks.unitB.id}: ${pairLinks.bondCount} bond(s)`);
 
 
-            const aI = elements[j];
-            for (let _bI = start; _bI < end; _bI++) {
-                const bI = elements[neighbor[_bI]];
-                console.log(`${atomLabel(model, aI)} -- ${atomLabel(model, bI)}`);
+                for (const aI of pairLinks.linkedElementIndices) {
+                    for (const link of pairLinks.getBonds(aI)) {
+                        console.log(`${atomLabel(unitA.model, unitA.elements[aI])} -- ${atomLabel(unitB.model, unitB.elements[link.indexB])}`);
+                    }
+                }
             }
             }
         }
         }
     }
     }
 }
 }
 
 
 export function printSequence(model: Model) {
 export function printSequence(model: Model) {
-    console.log('Sequence\n=============');
+    console.log('\nSequence\n=============');
     const { byEntityKey } = model.sequence;
     const { byEntityKey } = model.sequence;
     for (const key of Object.keys(byEntityKey)) {
     for (const key of Object.keys(byEntityKey)) {
         const seq = byEntityKey[+key];
         const seq = byEntityKey[+key];
-        console.log(`${seq.entityId} (${seq.num.value(0)}, ${seq.num.value(seq.num.rowCount - 1)}) (${seq.compId.value(0)}, ${seq.compId.value(seq.compId.rowCount - 1)})`);
-        // for (let i = 0; i < seq.compId.rowCount; i++) {
-        //     console.log(`${seq.entityId} ${seq.num.value(i)} ${seq.compId.value(i)}`);
-        // }
+        console.log(`${seq.entityId} (${seq.sequence.kind} ${seq.num.value(0)} (offset ${seq.sequence.offset}), ${seq.num.value(seq.num.rowCount - 1)}) (${seq.compId.value(0)}, ${seq.compId.value(seq.compId.rowCount - 1)})`);
+        console.log(`${seq.sequence.sequence}`);
+    }
+    console.log();
+}
+
+export function printModRes(model: Model) {
+    console.log('\nModified Residues\n=============');
+    const map = model.properties.modifiedResidues.parentId;
+    const { label_comp_id, _rowCount } = model.atomicHierarchy.residues;
+    for (let i = 0; i < _rowCount; i++) {
+        const comp_id = label_comp_id.value(i);
+        if (!map.has(comp_id)) continue;
+        console.log(`[${i}] ${map.get(comp_id)} -> ${comp_id}`);
+    }
+    console.log();
+}
+
+export function printRings(structure: Structure) {
+    console.log('\nRings\n=============');
+    for (const unit of structure.units) {
+        if (!Unit.isAtomic(unit)) continue;
+        const { all, byFingerprint } = unit.rings;
+        const fps: string[] = [];
+        for (let i = 0, _i = Math.min(5, all.length); i < _i; i++) {
+            fps[fps.length] = UnitRing.fingerprint(unit, all[i]);
+        }
+        if (all.length > 5) fps.push('...')
+        console.log(`Unit ${unit.id}, ${all.length} ring(s), ${byFingerprint.size} different fingerprint(s).\n  ${fps.join(', ')}`);
     }
     }
     console.log();
     console.log();
 }
 }
 
 
 export function printUnits(structure: Structure) {
 export function printUnits(structure: Structure) {
-    console.log('Units\n=============');
-    const l = Element.Location();
+    console.log('\nUnits\n=============');
+    const l = StructureElement.create();
 
 
     for (const unit of structure.units) {
     for (const unit of structure.units) {
         l.unit = unit;
         l.unit = unit;
@@ -123,7 +157,7 @@ export function printUnits(structure: Structure) {
         } else if (Unit.isCoarse(l.unit)) {
         } else if (Unit.isCoarse(l.unit)) {
             console.log(`Coarse unit ${unit.id} ${unit.conformation.operator.name} (${Unit.isSpheres(l.unit) ? 'spheres' : 'gaussians'}): ${size} elements.`);
             console.log(`Coarse unit ${unit.id} ${unit.conformation.operator.name} (${Unit.isSpheres(l.unit) ? 'spheres' : 'gaussians'}): ${size} elements.`);
 
 
-            const props = Queries.props.coarse;
+            const props = StructureProperties.coarse;
             const seq = l.unit.model.sequence;
             const seq = l.unit.model.sequence;
 
 
             for (let j = 0, _j = Math.min(size, 3); j < _j; j++) {
             for (let j = 0, _j = Math.min(size, 3); j < _j; j++) {
@@ -140,48 +174,86 @@ export function printUnits(structure: Structure) {
     }
     }
 }
 }
 
 
+export function printSymmetryInfo(model: Model) {
+    console.log('\nSymmetry Info\n=============');
+    const { symmetry } = model;
+    const { size, anglesInRadians } = symmetry.spacegroup.cell;
+    console.log(`Spacegroup: ${symmetry.spacegroup.name} size: ${Vec3.toString(size)} angles: ${Vec3.toString(anglesInRadians)}`);
+    console.log(`Assembly names: ${symmetry.assemblies.map(a => a.id).join(', ')}`);
+    // NCS example: 1auy
+    console.log(`NCS operators: ${symmetry.ncsOperators && symmetry.ncsOperators.map(a => a.name).join(', ')}`);
+}
+
+export function printModelStats(models: ReadonlyArray<Model>) {
+    console.log('\nModels\n=============');
 
 
-export function printIHMModels(model: Model) {
-    if (!model.coarseHierarchy.isDefined) return false;
-    console.log('IHM Models\n=============');
-    console.log(Table.formatToString(model.coarseHierarchy.models));
+    for (const m of models) {
+        if (m.coarseHierarchy.isDefined) {
+            console.log(`${m.label} ${m.modelNum}: ${m.atomicHierarchy.atoms._rowCount} atom(s), ${m.coarseHierarchy.spheres.count} sphere(s), ${m.coarseHierarchy.gaussians.count} gaussian(s)`);
+        } else {
+            console.log(`${m.label} ${m.modelNum}: ${m.atomicHierarchy.atoms._rowCount} atom(s)`);
+        }
+    }
+    console.log();
 }
 }
 
 
-async function run(mmcif: mmCIF_Database) {
-    const models = await Model.create({ kind: 'mmCIF', data: mmcif }).run();
+async function run(frame: CifFrame, args: Args) {
+    const models = await Model.create(Format.mmCIF(frame)).run();
     const structure = Structure.ofModel(models[0]);
     const structure = Structure.ofModel(models[0]);
-    printSequence(models[0]);
-    printIHMModels(models[0]);
-    printUnits(structure);
-    // printBonds(structure);
-    printSecStructure(models[0]);
+
+    if (args.models) printModelStats(models);
+    if (args.seq) printSequence(models[0]);
+    if (args.units) printUnits(structure);
+    if (args.sym) printSymmetryInfo(models[0]);
+    if (args.rings) printRings(structure);
+    if (args.intraLinks) printLinks(structure, true, false);
+    if (args.interLinks) printLinks(structure, false, true);
+    if (args.mod) printModRes(models[0]);
+    if (args.sec) printSecStructure(models[0]);
 }
 }
 
 
-async function runDL(pdb: string) {
+async function runDL(pdb: string, args: Args) {
     const mmcif = await downloadFromPdb(pdb)
     const mmcif = await downloadFromPdb(pdb)
-    run(mmcif);
+    run(mmcif, args);
 }
 }
 
 
-async function runFile(filename: string) {
+async function runFile(filename: string, args: Args) {
     const mmcif = await readPdbFile(filename);
     const mmcif = await readPdbFile(filename);
-    run(mmcif);
+    run(mmcif, args);
 }
 }
 
 
 const parser = new argparse.ArgumentParser({
 const parser = new argparse.ArgumentParser({
-  addHelp: true,
-  description: 'Print info about a structure, mainly to test and showcase the mol-model module'
-});
-parser.addArgument([ '--download', '-d' ], {
-    help: 'Pdb entry id'
-});
-parser.addArgument([ '--file', '-f' ], {
-    help: 'filename'
+    addHelp: true,
+    description: 'Print info about a structure, mainly to test and showcase the mol-model module'
 });
 });
+parser.addArgument(['--download', '-d'], { help: 'Pdb entry id' });
+parser.addArgument(['--file', '-f'], { help: 'filename' });
+
+parser.addArgument(['--models'], { help: 'print models info', action: 'storeTrue' });
+parser.addArgument(['--seq'], { help: 'print sequence', action: 'storeTrue' });
+parser.addArgument(['--units'], { help: 'print units', action: 'storeTrue' });
+parser.addArgument(['--sym'], { help: 'print symmetry', action: 'storeTrue' });
+parser.addArgument(['--rings'], { help: 'print rings', action: 'storeTrue' });
+parser.addArgument(['--intraLinks'], { help: 'print intra unit links', action: 'storeTrue' });
+parser.addArgument(['--interLinks'], { help: 'print inter unit links', action: 'storeTrue' });
+parser.addArgument(['--mod'], { help: 'print modified residues', action: 'storeTrue' });
+parser.addArgument(['--sec'], { help: 'print secoundary structure', action: 'storeTrue' });
 interface Args {
 interface Args {
     download?: string,
     download?: string,
-    file?: string
+    file?: string,
+
+    models?:boolean,
+    seq?: boolean,
+    ihm?: boolean,
+    units?: boolean,
+    sym?: boolean,
+    rings?: boolean,
+    intraLinks?: boolean,
+    interLinks?: boolean,
+    mod?: boolean,
+    sec?: boolean,
 }
 }
 const args: Args = parser.parseArgs();
 const args: Args = parser.parseArgs();
 
 
-if (args.download) runDL(args.download)
-else if (args.file) runFile(args.file)
+if (args.download) runDL(args.download, args)
+else if (args.file) runFile(args.file, args)

+ 25 - 0
src/apps/viewer/index.tsx

@@ -22,6 +22,10 @@ import { EntityTree } from 'mol-app/ui/entity/tree';
 import { EntityTreeController } from 'mol-app/controller/entity/tree';
 import { EntityTreeController } from 'mol-app/controller/entity/tree';
 import { TransformListController } from 'mol-app/controller/transform/list';
 import { TransformListController } from 'mol-app/controller/transform/list';
 import { TransformList } from 'mol-app/ui/transform/list';
 import { TransformList } from 'mol-app/ui/transform/list';
+import { SequenceView } from 'mol-app/ui/visualization/sequence-view';
+import { InteractivityEvents } from 'mol-app/event/basic';
+import { MarkerAction } from 'mol-geo/util/marker-data';
+import { EveryLoci } from 'mol-model/loci';
 
 
 const elm = document.getElementById('app')
 const elm = document.getElementById('app')
 if (!elm) throw new Error('Can not find element with id "app".')
 if (!elm) throw new Error('Can not find element with id "app".')
@@ -45,6 +49,14 @@ targets[LayoutRegion.Bottom].components.push({
     isStatic: true
     isStatic: true
 });
 });
 
 
+targets[LayoutRegion.Top].components.push({
+    key: 'molstar-sequence-view',
+    controller: ctx.components.sequenceView,
+    region: LayoutRegion.Top,
+    view: SequenceView,
+    isStatic: true
+});
+
 targets[LayoutRegion.Main].components.push({
 targets[LayoutRegion.Main].components.push({
     key: 'molstar-background-jobs',
     key: 'molstar-background-jobs',
     controller: new JobsController(ctx, 'Background'),
     controller: new JobsController(ctx, 'Background'),
@@ -85,4 +97,17 @@ ctx.layout.setState({
 })
 })
 // ctx.viewport.setState()
 // ctx.viewport.setState()
 
 
+ctx.dispatcher.getStream(InteractivityEvents.HighlightLoci).subscribe(event => {
+    ctx.stage.viewer.mark(EveryLoci, MarkerAction.RemoveHighlight)
+    if (event && event.data) {
+        ctx.stage.viewer.mark(event.data, MarkerAction.Highlight)
+    }
+})
+
+ctx.dispatcher.getStream(InteractivityEvents.SelectLoci).subscribe(event => {
+    if (event && event.data) {
+        ctx.stage.viewer.mark(event.data, MarkerAction.ToggleSelect)
+    }
+})
+
 ReactDOM.render(React.createElement(Layout, { controller: ctx.layout }), elm);
 ReactDOM.render(React.createElement(Layout, { controller: ctx.layout }), elm);

+ 3 - 0
src/helpers.d.ts

@@ -13,4 +13,7 @@ declare module Helpers {
     export type NumberArray = TypedArray | number[]
     export type NumberArray = TypedArray | number[]
     export type UintArray = Uint8Array | Uint16Array | Uint32Array | number[]
     export type UintArray = Uint8Array | Uint16Array | Uint32Array | number[]
     export type ValueOf<T> = T[keyof T]
     export type ValueOf<T> = T[keyof T]
+    export type ArrayCtor<T> = { new(size: number): { [i: number]: T, length: number } }
+    /** assignable ArrayLike version */
+    export type ArrayLike<T> =  { [i: number]: T, length: number }
 }
 }

+ 7 - 1
src/mol-app/context/context.ts

@@ -15,6 +15,7 @@ import { Stage } from 'mol-view/stage';
 import { AnyTransform } from 'mol-view/state/transform';
 import { AnyTransform } from 'mol-view/state/transform';
 import { BehaviorSubject } from 'rxjs';
 import { BehaviorSubject } from 'rxjs';
 import { AnyEntity } from 'mol-view/state/entity';
 import { AnyEntity } from 'mol-view/state/entity';
+import { SequenceViewController } from '../controller/visualization/sequence-view';
 
 
 export class Settings {
 export class Settings {
     private settings = new Map<string, any>();
     private settings = new Map<string, any>();
@@ -35,11 +36,16 @@ export class Context {
     logger = new Logger(this);
     logger = new Logger(this);
     performance = new PerformanceMonitor();
     performance = new PerformanceMonitor();
 
 
-    stage = new Stage();
+    stage = new Stage(this);
     viewport = new ViewportController(this);
     viewport = new ViewportController(this);
     layout: LayoutController;
     layout: LayoutController;
     settings = new Settings();
     settings = new Settings();
 
 
+    // TODO: this is a temporary solution
+    components = {
+        sequenceView: new SequenceViewController(this)
+    };
+
     currentEntity = new BehaviorSubject(undefined) as BehaviorSubject<AnyEntity | undefined>
     currentEntity = new BehaviorSubject(undefined) as BehaviorSubject<AnyEntity | undefined>
     currentTransforms = new BehaviorSubject([] as AnyTransform[])
     currentTransforms = new BehaviorSubject([] as AnyTransform[])
 
 

+ 21 - 0
src/mol-app/controller/visualization/sequence-view.ts

@@ -0,0 +1,21 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author David Sehnal <david.sehnal@gmail.com>
+ */
+
+import { shallowClone } from 'mol-util';
+import { Context } from '../../context/context'
+import { Controller } from '../controller';
+import { Structure } from 'mol-model/structure';
+
+export const DefaultSequenceViewState = {
+    structure: void 0 as (Structure | undefined)
+}
+export type SequenceViewState = typeof DefaultSequenceViewState
+
+export class SequenceViewController extends Controller<SequenceViewState> {
+    constructor(context: Context) {
+        super(context, shallowClone(DefaultSequenceViewState));
+    }
+}

+ 7 - 0
src/mol-app/event/basic.ts

@@ -11,6 +11,7 @@ import { Dispatcher } from '../service/dispatcher'
 import { LayoutState } from '../controller/layout';
 import { LayoutState } from '../controller/layout';
 import { ViewportOptions } from '../controller/visualization/viewport';
 import { ViewportOptions } from '../controller/visualization/viewport';
 import { Job } from '../service/job';
 import { Job } from '../service/job';
+import { Loci } from 'mol-model/loci';
 
 
 const Lane = Dispatcher.Lane;
 const Lane = Dispatcher.Lane;
 
 
@@ -31,3 +32,9 @@ export namespace LayoutEvents {
     export const SetState = Event.create<Partial<LayoutState>>('lm.cmd.Layout.SetState', Lane.Slow);
     export const SetState = Event.create<Partial<LayoutState>>('lm.cmd.Layout.SetState', Lane.Slow);
     export const SetViewportOptions = Event.create<ViewportOptions>('bs.cmd.Layout.SetViewportOptions', Lane.Slow);
     export const SetViewportOptions = Event.create<ViewportOptions>('bs.cmd.Layout.SetViewportOptions', Lane.Slow);
 }
 }
+
+export namespace InteractivityEvents {
+    export const HighlightLoci = Event.create<Loci>('bs.Interactivity.HighlightLoci', Lane.Slow);
+    export const SelectLoci = Event.create<Loci>('bs.Interactivity.SelectLoci', Lane.Slow);
+    export const LabelLoci = Event.create<Loci>('bs.Interactivity.LabelLoci', Lane.Slow);
+}

+ 9 - 0
src/mol-app/skin/components/sequence-view.scss

@@ -0,0 +1,9 @@
+.molstar-sequence-view-wrap {
+    position: absolute;
+    right: 0;
+    top: 0;
+    left: 0;
+    bottom: 0;
+    overflow: hidden;
+    overflow-x: scroll;
+}

+ 1 - 1
src/mol-app/skin/layout/common.scss

@@ -23,7 +23,7 @@
     overflow: hidden;
     overflow: hidden;
 }
 }
 
 
-.molstar-layout-main, .molstar-layout-bottom {
+.molstar-layout-main, .molstar-layout-bottom, .molstar-layout-top {
     .molstar-layout-static {
     .molstar-layout-static {
         left: 0;
         left: 0;
         right: 0;
         right: 0;

+ 2 - 1
src/mol-app/skin/ui.scss

@@ -35,4 +35,5 @@
 @import 'components/misc';
 @import 'components/misc';
 @import 'components/panel';
 @import 'components/panel';
 @import 'components/slider';
 @import 'components/slider';
-@import 'components/viewport';
+@import 'components/viewport';
+@import 'components/sequence-view';

+ 6 - 0
src/mol-app/ui/controls/slider.tsx

@@ -206,6 +206,12 @@ export class SliderBase extends React.Component<SliderBaseProps, SliderBaseState
     private sliderElement: HTMLElement | undefined = void 0;
     private sliderElement: HTMLElement | undefined = void 0;
     private handleElements: (HTMLElement | undefined)[] = [];
     private handleElements: (HTMLElement | undefined)[] = [];
 
 
+    state: SliderBaseState = {
+        handle: null,
+        recent: 0,
+        bounds: [0, 0],
+    };
+
     constructor(props: SliderBaseProps) {
     constructor(props: SliderBaseProps) {
         super(props);
         super(props);
 
 

+ 15 - 3
src/mol-app/ui/entity/tree.tsx

@@ -13,13 +13,13 @@ import { View } from '../view';
 import { EntityTreeController } from '../../controller/entity/tree';
 import { EntityTreeController } from '../../controller/entity/tree';
 import { Controller } from '../../controller/controller';
 import { Controller } from '../../controller/controller';
 import { AnyEntity, RootEntity } from 'mol-view/state/entity';
 import { AnyEntity, RootEntity } from 'mol-view/state/entity';
-import { AnyTransform, SpacefillUpdate, UrlToData, DataToCif, FileToData, CifToMmcif, MmcifToModel, ModelToStructure, StructureToSpacefill, MmcifFileToSpacefill, StructureCenter } from 'mol-view/state/transform';
+import { AnyTransform, SpacefillUpdate, UrlToData, DataToCif, FileToData, CifToMmcif, MmcifToModel, ModelToStructure, StructureToSpacefill, MmcifFileToSpacefill, StructureCenter, StructureToBallAndStick, DistanceRestraintUpdate, CartoonUpdate, BallAndStickUpdate, BackboneUpdate, MmcifUrlToSpacefill } from 'mol-view/state/transform';
 
 
 function getTransforms(entity: AnyEntity): AnyTransform[] {
 function getTransforms(entity: AnyEntity): AnyTransform[] {
     const transforms: AnyTransform[] = []
     const transforms: AnyTransform[] = []
     switch (entity.kind) {
     switch (entity.kind) {
         case 'root':
         case 'root':
-            transforms.push(MmcifFileToSpacefill)
+            transforms.push(MmcifFileToSpacefill, MmcifUrlToSpacefill)
             break;
             break;
         case 'url':
         case 'url':
             transforms.push(UrlToData)
             transforms.push(UrlToData)
@@ -40,11 +40,23 @@ function getTransforms(entity: AnyEntity): AnyTransform[] {
             transforms.push(ModelToStructure)
             transforms.push(ModelToStructure)
             break;
             break;
         case 'structure':
         case 'structure':
-            transforms.push(StructureToSpacefill, StructureCenter)
+            transforms.push(StructureToSpacefill, StructureToBallAndStick, StructureCenter)
             break;
             break;
         case 'spacefill':
         case 'spacefill':
             transforms.push(SpacefillUpdate)
             transforms.push(SpacefillUpdate)
             break;
             break;
+        case 'ballandstick':
+            transforms.push(BallAndStickUpdate)
+            break;
+        case 'distancerestraint':
+            transforms.push(DistanceRestraintUpdate)
+            break;
+        case 'backbone':
+            transforms.push(BackboneUpdate)
+            break;
+        case 'cartoon':
+            transforms.push(CartoonUpdate)
+            break;
     }
     }
     return transforms
     return transforms
 }
 }

+ 243 - 0
src/mol-app/ui/transform/backbone.tsx

@@ -0,0 +1,243 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * Adapted from LiteMol
+ * Copyright (c) 2016 - now David Sehnal, licensed under Apache 2.0, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+import * as React from 'react'
+
+import { View } from '../view';
+import { Controller } from '../../controller/controller';
+import { Toggle } from '../controls/common';
+import { BackboneEntity } from 'mol-view/state/entity';
+import { BackboneUpdate } from 'mol-view/state/transform'
+import { StateContext } from 'mol-view/state/context';
+import { ColorTheme, SizeTheme } from 'mol-geo/theme';
+import { Color, ColorNames } from 'mol-util/color';
+import { Slider } from '../controls/slider';
+import { VisualQuality } from 'mol-geo/representation/util';
+import { Unit } from 'mol-model/structure';
+
+export const ColorThemeInfo = {
+    'atom-index': {},
+    'chain-id': {},
+    'element-symbol': {},
+    'instance-index': {},
+    'uniform': {}
+}
+export type ColorThemeInfo = keyof typeof ColorThemeInfo
+
+interface BackboneState {
+    doubleSided: boolean
+    flipSided: boolean
+    flatShaded: boolean
+    detail: number
+    colorTheme: ColorTheme
+    colorValue: Color
+    sizeTheme: SizeTheme
+    visible: boolean
+    alpha: number
+    depthMask: boolean
+    useFog: boolean
+    quality: VisualQuality
+    unitKinds: Unit.Kind[]
+}
+
+export class Backbone extends View<Controller<any>, BackboneState, { transform: BackboneUpdate, entity: BackboneEntity, ctx: StateContext }> {
+    state = {
+        doubleSided: true,
+        flipSided: false,
+        flatShaded: false,
+        detail: 2,
+        colorTheme: { name: 'element-symbol' } as ColorTheme,
+        colorValue: 0x000000,
+        sizeTheme: { name: 'uniform' } as SizeTheme,
+        visible: true,
+        alpha: 1,
+        depthMask: true,
+        useFog: true,
+        quality: 'auto' as VisualQuality,
+        unitKinds: [] as Unit.Kind[]
+    }
+
+    componentWillMount() {
+        this.setState({ ...this.state, ...this.props.entity.value.props })
+    }
+
+    update(state?: Partial<BackboneState>) {
+        console.log(state)
+        const { transform, entity, ctx } = this.props
+        const newState = { ...this.state, ...state }
+        this.setState(newState)
+        transform.apply(ctx, entity, newState)
+    }
+
+    render() {
+        const { transform } = this.props
+
+        const qualityOptions = ['auto', 'custom', 'highest', 'high', 'medium', 'low', 'lowest'].map((name, idx) => {
+            return <option key={name} value={name}>{name}</option>
+        })
+
+        const sphereDetailOptions = [0, 1, 2, 3].map((value, idx) => {
+            return <option key={value} value={value}>{value.toString()}</option>
+        })
+
+        const colorThemeOptions = Object.keys(ColorThemeInfo).map((name, idx) => {
+            return <option key={name} value={name}>{name}</option>
+        })
+
+        const colorValueOptions = Object.keys(ColorNames).map((name, idx) => {
+            return <option key={name} value={(ColorNames as any)[name]}>{name}</option>
+        })
+
+        return <div className='molstar-transformer-wrapper'>
+            <div className='molstar-panel molstar-control molstar-transformer molstar-panel-expanded'>
+                <div className='molstar-panel-header'>
+                    <button
+                        className='molstar-btn molstar-btn-link molstar-panel-expander'
+                        onClick={() => this.update()}
+                    >
+                        <span>[{transform.kind}] {transform.inputKind} -> {transform.outputKind}</span>
+                    </button>
+                </div>
+                <div className='molstar-panel-body'>
+                    <div>
+                    <div className='molstar-control-row molstar-options-group'>
+                        <span>Quality</span>
+                            <div>
+                                <select
+                                    className='molstar-form-control'
+                                    value={this.state.quality}
+                                    onChange={(e) => this.update({ quality: e.target.value as VisualQuality })}
+                                >
+                                    {qualityOptions}
+                                </select>
+                            </div>
+                        </div>
+                        <div className='molstar-control-row molstar-options-group'>
+                            <span>Sphere detail</span>
+                            <div>
+                                <select
+                                    className='molstar-form-control'
+                                    value={this.state.detail}
+                                    onChange={(e) => this.update({ detail: parseInt(e.target.value) })}
+                                >
+                                    {sphereDetailOptions}
+                                </select>
+                            </div>
+                        </div>
+                        <div className='molstar-control-row molstar-options-group'>
+                            <span>Color theme</span>
+                            <div>
+                                <select
+                                    className='molstar-form-control'
+                                    value={this.state.colorTheme.name}
+                                    onChange={(e) => {
+                                        const colorThemeName = e.target.value as ColorThemeInfo
+                                        if (colorThemeName === 'uniform') {
+                                            this.update({
+                                                colorTheme: {
+                                                    name: colorThemeName,
+                                                    value: this.state.colorValue
+                                                }
+                                            })
+                                        } else {
+                                            this.update({
+                                                colorTheme: { name: colorThemeName }
+                                            })
+                                        }
+                                    }}
+                                >
+                                    {colorThemeOptions}
+                                </select>
+                            </div>
+                        </div>
+                        <div className='molstar-control-row molstar-options-group'>
+                            <span>Color value</span>
+                            <div>
+                                <select
+                                    className='molstar-form-control'
+                                    value={this.state.colorValue}
+                                    onChange={(e) => {
+                                        const colorValue = parseInt(e.target.value)
+                                        this.update({
+                                            colorTheme: {
+                                                name: 'uniform',
+                                                value: colorValue
+                                            },
+                                            colorValue
+                                        })
+                                    }}
+                                >
+                                    {colorValueOptions}
+                                </select>
+                            </div>
+                        </div>
+                        <div className='molstar-control-row molstar-options-group'>
+                            <div>
+                                <Toggle
+                                    value={this.state.visible}
+                                    label='Visibility'
+                                    onChange={value => this.update({ visible: value })}
+                                />
+                            </div>
+                        </div>
+                        <div className='molstar-control-row molstar-options-group'>
+                            <div>
+                                <Toggle
+                                    value={this.state.depthMask}
+                                    label='Depth write'
+                                    onChange={value => this.update({ depthMask: value })}
+                                />
+                            </div>
+                        </div>
+                        <div className='molstar-control-row molstar-options-group'>
+                            <div>
+                                <Toggle
+                                    value={this.state.doubleSided}
+                                    label='Double sided'
+                                    onChange={value => this.update({ doubleSided: value })}
+                                />
+                            </div>
+                        </div>
+                        <div className='molstar-control-row molstar-options-group'>
+                            <div>
+                                <Toggle
+                                    value={this.state.flipSided}
+                                    label='Flip sided'
+                                    onChange={value => this.update({ flipSided: value })}
+                                />
+                            </div>
+                        </div>
+                        <div className='molstar-control-row molstar-options-group'>
+                            <div>
+                                <Toggle
+                                    value={this.state.flatShaded}
+                                    label='Flat shaded'
+                                    onChange={value => this.update({ flatShaded: value })}
+                                />
+                            </div>
+                        </div>
+                        <div className='molstar-control-row molstar-options-group'>
+                            <div>
+                                <Slider
+                                    value={this.state.alpha}
+                                    label='Opacity'
+                                    min={0}
+                                    max={1}
+                                    step={0.01}
+                                    callOnChangeWhileSliding={true}
+                                    onChange={value => this.update({ alpha: value })}
+                                />
+                            </div>
+                        </div>
+                    </div>
+                </div>
+            </div>
+        </div>;
+    }
+}

+ 234 - 0
src/mol-app/ui/transform/ball-and-stick.tsx

@@ -0,0 +1,234 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * Adapted from LiteMol
+ * Copyright (c) 2016 - now David Sehnal, licensed under Apache 2.0, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+import * as React from 'react'
+
+import { View } from '../view';
+import { Controller } from '../../controller/controller';
+import { Toggle } from '../controls/common';
+import { DistanceRestraintEntity } from 'mol-view/state/entity';
+import { DistanceRestraintUpdate } from 'mol-view/state/transform'
+import { StateContext } from 'mol-view/state/context';
+import { ColorTheme, SizeTheme } from 'mol-geo/theme';
+import { Color, ColorNames } from 'mol-util/color';
+import { Slider } from '../controls/slider';
+import { VisualQuality } from 'mol-geo/representation/util';
+import { Unit } from 'mol-model/structure';
+
+export const ColorThemeInfo = {
+    'atom-index': {},
+    'chain-id': {},
+    'element-symbol': {},
+    'instance-index': {},
+    'uniform': {}
+}
+export type ColorThemeInfo = keyof typeof ColorThemeInfo
+
+interface BallAndStickState {
+    doubleSided: boolean
+    flipSided: boolean
+    flatShaded: boolean
+    colorTheme: ColorTheme
+    colorValue: Color
+    sizeTheme: SizeTheme
+    visible: boolean
+    alpha: number
+    depthMask: boolean
+    useFog: boolean
+    quality: VisualQuality
+    linkScale: number
+    linkSpacing: number
+    linkRadius: number
+    radialSegments: number
+    detail: number
+    unitKinds: Unit.Kind[]
+}
+
+export class BallAndStick extends View<Controller<any>, BallAndStickState, { transform: DistanceRestraintUpdate, entity: DistanceRestraintEntity, ctx: StateContext }> {
+    state = {
+        doubleSided: true,
+        flipSided: false,
+        flatShaded: false,
+        colorTheme: { name: 'element-symbol' } as ColorTheme,
+        colorValue: 0x000000,
+        sizeTheme: { name: 'uniform' } as SizeTheme,
+        visible: true,
+        alpha: 1,
+        depthMask: true,
+        useFog: true,
+        quality: 'auto' as VisualQuality,
+        linkScale: 0.4,
+        linkSpacing: 1,
+        linkRadius: 0.25,
+        radialSegments: 16,
+        detail: 1,
+        unitKinds: [] as Unit.Kind[]
+    }
+
+    componentWillMount() {
+        this.setState({ ...this.state, ...this.props.entity.value.props })
+    }
+
+    update(state?: Partial<BallAndStickState>) {
+        const { transform, entity, ctx } = this.props
+        const newState = { ...this.state, ...state }
+        this.setState(newState)
+        transform.apply(ctx, entity, newState)
+    }
+
+    render() {
+        const { transform } = this.props
+
+        const qualityOptions = ['auto', 'custom', 'highest', 'high', 'medium', 'low', 'lowest'].map((name, idx) => {
+            return <option key={name} value={name}>{name}</option>
+        })
+
+        const colorThemeOptions = Object.keys(ColorThemeInfo).map((name, idx) => {
+            return <option key={name} value={name}>{name}</option>
+        })
+
+        const colorValueOptions = Object.keys(ColorNames).map((name, idx) => {
+            return <option key={name} value={(ColorNames as any)[name]}>{name}</option>
+        })
+
+        return <div className='molstar-transformer-wrapper'>
+            <div className='molstar-panel molstar-control molstar-transformer molstar-panel-expanded'>
+                <div className='molstar-panel-header'>
+                    <button
+                        className='molstar-btn molstar-btn-link molstar-panel-expander'
+                        onClick={() => this.update()}
+                    >
+                        <span>[{transform.kind}] {transform.inputKind} -> {transform.outputKind}</span>
+                    </button>
+                </div>
+                <div className='molstar-panel-body'>
+                    <div>
+                        <div className='molstar-control-row molstar-options-group'>
+                            <span>Quality</span>
+                            <div>
+                                <select
+                                    className='molstar-form-control'
+                                    value={this.state.quality}
+                                    onChange={(e) => this.update({ quality: e.target.value as VisualQuality })}
+                                >
+                                    {qualityOptions}
+                                </select>
+                            </div>
+                        </div>
+                        <div className='molstar-control-row molstar-options-group'>
+                            <span>Color theme</span>
+                            <div>
+                                <select
+                                    className='molstar-form-control'
+                                    value={this.state.colorTheme.name}
+                                    onChange={(e) => {
+                                        const colorThemeName = e.target.value as ColorThemeInfo
+                                        if (colorThemeName === 'uniform') {
+                                            this.update({
+                                                colorTheme: {
+                                                    name: colorThemeName,
+                                                    value: this.state.colorValue
+                                                }
+                                            })
+                                        } else {
+                                            this.update({
+                                                colorTheme: { name: colorThemeName }
+                                            })
+                                        }
+                                    }}
+                                >
+                                    {colorThemeOptions}
+                                </select>
+                            </div>
+                        </div>
+                        <div className='molstar-control-row molstar-options-group'>
+                            <span>Color value</span>
+                            <div>
+                                <select
+                                    className='molstar-form-control'
+                                    value={this.state.colorValue}
+                                    onChange={(e) => {
+                                        const colorValue = parseInt(e.target.value)
+                                        this.update({
+                                            colorTheme: {
+                                                name: 'uniform',
+                                                value: colorValue
+                                            },
+                                            colorValue
+                                        })
+                                    }}
+                                >
+                                    {colorValueOptions}
+                                </select>
+                            </div>
+                        </div>
+                        <div className='molstar-control-row molstar-options-group'>
+                            <div>
+                                <Toggle
+                                    value={this.state.visible}
+                                    label='Visibility'
+                                    onChange={value => this.update({ visible: value })}
+                                />
+                            </div>
+                        </div>
+                        <div className='molstar-control-row molstar-options-group'>
+                            <div>
+                                <Toggle
+                                    value={this.state.depthMask}
+                                    label='Depth write'
+                                    onChange={value => this.update({ depthMask: value })}
+                                />
+                            </div>
+                        </div>
+                        <div className='molstar-control-row molstar-options-group'>
+                            <div>
+                                <Toggle
+                                    value={this.state.doubleSided}
+                                    label='Double sided'
+                                    onChange={value => this.update({ doubleSided: value })}
+                                />
+                            </div>
+                        </div>
+                        <div className='molstar-control-row molstar-options-group'>
+                            <div>
+                                <Toggle
+                                    value={this.state.flipSided}
+                                    label='Flip sided'
+                                    onChange={value => this.update({ flipSided: value })}
+                                />
+                            </div>
+                        </div>
+                        <div className='molstar-control-row molstar-options-group'>
+                            <div>
+                                <Toggle
+                                    value={this.state.flatShaded}
+                                    label='Flat shaded'
+                                    onChange={value => this.update({ flatShaded: value })}
+                                />
+                            </div>
+                        </div>
+                        <div className='molstar-control-row molstar-options-group'>
+                            <div>
+                                <Slider
+                                    value={this.state.alpha}
+                                    label='Opacity'
+                                    min={0}
+                                    max={1}
+                                    step={0.01}
+                                    callOnChangeWhileSliding={true}
+                                    onChange={value => this.update({ alpha: value })}
+                                />
+                            </div>
+                        </div>
+                    </div>
+                </div>
+            </div>
+        </div>;
+    }
+}

+ 243 - 0
src/mol-app/ui/transform/cartoon.tsx

@@ -0,0 +1,243 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * Adapted from LiteMol
+ * Copyright (c) 2016 - now David Sehnal, licensed under Apache 2.0, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+import * as React from 'react'
+
+import { View } from '../view';
+import { Controller } from '../../controller/controller';
+import { Toggle } from '../controls/common';
+import { CartoonEntity } from 'mol-view/state/entity';
+import { CartoonUpdate } from 'mol-view/state/transform'
+import { StateContext } from 'mol-view/state/context';
+import { ColorTheme, SizeTheme } from 'mol-geo/theme';
+import { Color, ColorNames } from 'mol-util/color';
+import { Slider } from '../controls/slider';
+import { VisualQuality } from 'mol-geo/representation/util';
+import { Unit } from 'mol-model/structure';
+
+export const ColorThemeInfo = {
+    'atom-index': {},
+    'chain-id': {},
+    'element-symbol': {},
+    'instance-index': {},
+    'uniform': {}
+}
+export type ColorThemeInfo = keyof typeof ColorThemeInfo
+
+interface CartoonState {
+    doubleSided: boolean
+    flipSided: boolean
+    flatShaded: boolean
+    detail: number
+    colorTheme: ColorTheme
+    colorValue: Color
+    sizeTheme: SizeTheme
+    visible: boolean
+    alpha: number
+    depthMask: boolean
+    useFog: boolean
+    quality: VisualQuality
+    unitKinds: Unit.Kind[]
+}
+
+export class Cartoon extends View<Controller<any>, CartoonState, { transform: CartoonUpdate, entity: CartoonEntity, ctx: StateContext }> {
+    state = {
+        doubleSided: true,
+        flipSided: false,
+        flatShaded: false,
+        detail: 2,
+        colorTheme: { name: 'element-symbol' } as ColorTheme,
+        colorValue: 0x000000,
+        sizeTheme: { name: 'uniform' } as SizeTheme,
+        visible: true,
+        alpha: 1,
+        depthMask: true,
+        useFog: true,
+        quality: 'auto' as VisualQuality,
+        unitKinds: [] as Unit.Kind[]
+    }
+
+    componentWillMount() {
+        this.setState({ ...this.state, ...this.props.entity.value.props })
+    }
+
+    update(state?: Partial<CartoonState>) {
+        console.log(state)
+        const { transform, entity, ctx } = this.props
+        const newState = { ...this.state, ...state }
+        this.setState(newState)
+        transform.apply(ctx, entity, newState)
+    }
+
+    render() {
+        const { transform } = this.props
+
+        const qualityOptions = ['auto', 'custom', 'highest', 'high', 'medium', 'low', 'lowest'].map((name, idx) => {
+            return <option key={name} value={name}>{name}</option>
+        })
+
+        const sphereDetailOptions = [0, 1, 2, 3].map((value, idx) => {
+            return <option key={value} value={value}>{value.toString()}</option>
+        })
+
+        const colorThemeOptions = Object.keys(ColorThemeInfo).map((name, idx) => {
+            return <option key={name} value={name}>{name}</option>
+        })
+
+        const colorValueOptions = Object.keys(ColorNames).map((name, idx) => {
+            return <option key={name} value={(ColorNames as any)[name]}>{name}</option>
+        })
+
+        return <div className='molstar-transformer-wrapper'>
+            <div className='molstar-panel molstar-control molstar-transformer molstar-panel-expanded'>
+                <div className='molstar-panel-header'>
+                    <button
+                        className='molstar-btn molstar-btn-link molstar-panel-expander'
+                        onClick={() => this.update()}
+                    >
+                        <span>[{transform.kind}] {transform.inputKind} -> {transform.outputKind}</span>
+                    </button>
+                </div>
+                <div className='molstar-panel-body'>
+                    <div>
+                    <div className='molstar-control-row molstar-options-group'>
+                        <span>Quality</span>
+                            <div>
+                                <select
+                                    className='molstar-form-control'
+                                    value={this.state.quality}
+                                    onChange={(e) => this.update({ quality: e.target.value as VisualQuality })}
+                                >
+                                    {qualityOptions}
+                                </select>
+                            </div>
+                        </div>
+                        <div className='molstar-control-row molstar-options-group'>
+                            <span>Sphere detail</span>
+                            <div>
+                                <select
+                                    className='molstar-form-control'
+                                    value={this.state.detail}
+                                    onChange={(e) => this.update({ detail: parseInt(e.target.value) })}
+                                >
+                                    {sphereDetailOptions}
+                                </select>
+                            </div>
+                        </div>
+                        <div className='molstar-control-row molstar-options-group'>
+                            <span>Color theme</span>
+                            <div>
+                                <select
+                                    className='molstar-form-control'
+                                    value={this.state.colorTheme.name}
+                                    onChange={(e) => {
+                                        const colorThemeName = e.target.value as ColorThemeInfo
+                                        if (colorThemeName === 'uniform') {
+                                            this.update({
+                                                colorTheme: {
+                                                    name: colorThemeName,
+                                                    value: this.state.colorValue
+                                                }
+                                            })
+                                        } else {
+                                            this.update({
+                                                colorTheme: { name: colorThemeName }
+                                            })
+                                        }
+                                    }}
+                                >
+                                    {colorThemeOptions}
+                                </select>
+                            </div>
+                        </div>
+                        <div className='molstar-control-row molstar-options-group'>
+                            <span>Color value</span>
+                            <div>
+                                <select
+                                    className='molstar-form-control'
+                                    value={this.state.colorValue}
+                                    onChange={(e) => {
+                                        const colorValue = parseInt(e.target.value)
+                                        this.update({
+                                            colorTheme: {
+                                                name: 'uniform',
+                                                value: colorValue
+                                            },
+                                            colorValue
+                                        })
+                                    }}
+                                >
+                                    {colorValueOptions}
+                                </select>
+                            </div>
+                        </div>
+                        <div className='molstar-control-row molstar-options-group'>
+                            <div>
+                                <Toggle
+                                    value={this.state.visible}
+                                    label='Visibility'
+                                    onChange={value => this.update({ visible: value })}
+                                />
+                            </div>
+                        </div>
+                        <div className='molstar-control-row molstar-options-group'>
+                            <div>
+                                <Toggle
+                                    value={this.state.depthMask}
+                                    label='Depth write'
+                                    onChange={value => this.update({ depthMask: value })}
+                                />
+                            </div>
+                        </div>
+                        <div className='molstar-control-row molstar-options-group'>
+                            <div>
+                                <Toggle
+                                    value={this.state.doubleSided}
+                                    label='Double sided'
+                                    onChange={value => this.update({ doubleSided: value })}
+                                />
+                            </div>
+                        </div>
+                        <div className='molstar-control-row molstar-options-group'>
+                            <div>
+                                <Toggle
+                                    value={this.state.flipSided}
+                                    label='Flip sided'
+                                    onChange={value => this.update({ flipSided: value })}
+                                />
+                            </div>
+                        </div>
+                        <div className='molstar-control-row molstar-options-group'>
+                            <div>
+                                <Toggle
+                                    value={this.state.flatShaded}
+                                    label='Flat shaded'
+                                    onChange={value => this.update({ flatShaded: value })}
+                                />
+                            </div>
+                        </div>
+                        <div className='molstar-control-row molstar-options-group'>
+                            <div>
+                                <Slider
+                                    value={this.state.alpha}
+                                    label='Opacity'
+                                    min={0}
+                                    max={1}
+                                    step={0.01}
+                                    callOnChangeWhileSliding={true}
+                                    onChange={value => this.update({ alpha: value })}
+                                />
+                            </div>
+                        </div>
+                    </div>
+                </div>
+            </div>
+        </div>;
+    }
+}

+ 234 - 0
src/mol-app/ui/transform/distance-restraint.tsx

@@ -0,0 +1,234 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * Adapted from LiteMol
+ * Copyright (c) 2016 - now David Sehnal, licensed under Apache 2.0, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+import * as React from 'react'
+
+import { View } from '../view';
+import { Controller } from '../../controller/controller';
+import { Toggle } from '../controls/common';
+import { DistanceRestraintEntity } from 'mol-view/state/entity';
+import { DistanceRestraintUpdate } from 'mol-view/state/transform'
+import { StateContext } from 'mol-view/state/context';
+import { ColorTheme, SizeTheme } from 'mol-geo/theme';
+import { Color, ColorNames } from 'mol-util/color';
+import { Slider } from '../controls/slider';
+import { VisualQuality } from 'mol-geo/representation/util';
+import { Unit } from 'mol-model/structure';
+
+export const ColorThemeInfo = {
+    'atom-index': {},
+    'chain-id': {},
+    'element-symbol': {},
+    'instance-index': {},
+    'uniform': {}
+}
+export type ColorThemeInfo = keyof typeof ColorThemeInfo
+
+interface DistanceRestraintState {
+    doubleSided: boolean
+    flipSided: boolean
+    flatShaded: boolean
+    colorTheme: ColorTheme
+    colorValue: Color
+    sizeTheme: SizeTheme
+    visible: boolean
+    alpha: number
+    depthMask: boolean
+    useFog: boolean
+    quality: VisualQuality
+    linkScale: number
+    linkSpacing: number
+    linkRadius: number
+    radialSegments: number
+    detail: number
+    unitKinds: Unit.Kind[]
+}
+
+export class DistanceRestraint extends View<Controller<any>, DistanceRestraintState, { transform: DistanceRestraintUpdate, entity: DistanceRestraintEntity, ctx: StateContext }> {
+    state = {
+        doubleSided: true,
+        flipSided: false,
+        flatShaded: false,
+        colorTheme: { name: 'element-symbol' } as ColorTheme,
+        colorValue: 0x000000,
+        sizeTheme: { name: 'uniform' } as SizeTheme,
+        visible: true,
+        alpha: 1,
+        depthMask: true,
+        useFog: true,
+        quality: 'auto' as VisualQuality,
+        linkScale: 0.4,
+        linkSpacing: 1,
+        linkRadius: 0.25,
+        radialSegments: 16,
+        detail: 1,
+        unitKinds: [] as Unit.Kind[]
+    }
+
+    componentWillMount() {
+        this.setState({ ...this.state, ...this.props.entity.value.props })
+    }
+
+    update(state?: Partial<DistanceRestraintState>) {
+        const { transform, entity, ctx } = this.props
+        const newState = { ...this.state, ...state }
+        this.setState(newState)
+        transform.apply(ctx, entity, newState)
+    }
+
+    render() {
+        const { transform } = this.props
+
+        const qualityOptions = ['auto', 'custom', 'highest', 'high', 'medium', 'low', 'lowest'].map((name, idx) => {
+            return <option key={name} value={name}>{name}</option>
+        })
+
+        const colorThemeOptions = Object.keys(ColorThemeInfo).map((name, idx) => {
+            return <option key={name} value={name}>{name}</option>
+        })
+
+        const colorValueOptions = Object.keys(ColorNames).map((name, idx) => {
+            return <option key={name} value={(ColorNames as any)[name]}>{name}</option>
+        })
+
+        return <div className='molstar-transformer-wrapper'>
+            <div className='molstar-panel molstar-control molstar-transformer molstar-panel-expanded'>
+                <div className='molstar-panel-header'>
+                    <button
+                        className='molstar-btn molstar-btn-link molstar-panel-expander'
+                        onClick={() => this.update()}
+                    >
+                        <span>[{transform.kind}] {transform.inputKind} -> {transform.outputKind}</span>
+                    </button>
+                </div>
+                <div className='molstar-panel-body'>
+                    <div>
+                        <div className='molstar-control-row molstar-options-group'>
+                            <span>Quality</span>
+                            <div>
+                                <select
+                                    className='molstar-form-control'
+                                    value={this.state.quality}
+                                    onChange={(e) => this.update({ quality: e.target.value as VisualQuality })}
+                                >
+                                    {qualityOptions}
+                                </select>
+                            </div>
+                        </div>
+                        <div className='molstar-control-row molstar-options-group'>
+                            <span>Color theme</span>
+                            <div>
+                                <select
+                                    className='molstar-form-control'
+                                    value={this.state.colorTheme.name}
+                                    onChange={(e) => {
+                                        const colorThemeName = e.target.value as ColorThemeInfo
+                                        if (colorThemeName === 'uniform') {
+                                            this.update({
+                                                colorTheme: {
+                                                    name: colorThemeName,
+                                                    value: this.state.colorValue
+                                                }
+                                            })
+                                        } else {
+                                            this.update({
+                                                colorTheme: { name: colorThemeName }
+                                            })
+                                        }
+                                    }}
+                                >
+                                    {colorThemeOptions}
+                                </select>
+                            </div>
+                        </div>
+                        <div className='molstar-control-row molstar-options-group'>
+                            <span>Color value</span>
+                            <div>
+                                <select
+                                    className='molstar-form-control'
+                                    value={this.state.colorValue}
+                                    onChange={(e) => {
+                                        const colorValue = parseInt(e.target.value)
+                                        this.update({
+                                            colorTheme: {
+                                                name: 'uniform',
+                                                value: colorValue
+                                            },
+                                            colorValue
+                                        })
+                                    }}
+                                >
+                                    {colorValueOptions}
+                                </select>
+                            </div>
+                        </div>
+                        <div className='molstar-control-row molstar-options-group'>
+                            <div>
+                                <Toggle
+                                    value={this.state.visible}
+                                    label='Visibility'
+                                    onChange={value => this.update({ visible: value })}
+                                />
+                            </div>
+                        </div>
+                        <div className='molstar-control-row molstar-options-group'>
+                            <div>
+                                <Toggle
+                                    value={this.state.depthMask}
+                                    label='Depth write'
+                                    onChange={value => this.update({ depthMask: value })}
+                                />
+                            </div>
+                        </div>
+                        <div className='molstar-control-row molstar-options-group'>
+                            <div>
+                                <Toggle
+                                    value={this.state.doubleSided}
+                                    label='Double sided'
+                                    onChange={value => this.update({ doubleSided: value })}
+                                />
+                            </div>
+                        </div>
+                        <div className='molstar-control-row molstar-options-group'>
+                            <div>
+                                <Toggle
+                                    value={this.state.flipSided}
+                                    label='Flip sided'
+                                    onChange={value => this.update({ flipSided: value })}
+                                />
+                            </div>
+                        </div>
+                        <div className='molstar-control-row molstar-options-group'>
+                            <div>
+                                <Toggle
+                                    value={this.state.flatShaded}
+                                    label='Flat shaded'
+                                    onChange={value => this.update({ flatShaded: value })}
+                                />
+                            </div>
+                        </div>
+                        <div className='molstar-control-row molstar-options-group'>
+                            <div>
+                                <Slider
+                                    value={this.state.alpha}
+                                    label='Opacity'
+                                    min={0}
+                                    max={1}
+                                    step={0.01}
+                                    callOnChangeWhileSliding={true}
+                                    onChange={value => this.update({ alpha: value })}
+                                />
+                            </div>
+                        </div>
+                    </div>
+                </div>
+            </div>
+        </div>;
+    }
+}

+ 46 - 4
src/mol-app/ui/transform/file-loader.tsx

@@ -9,18 +9,60 @@ import { View } from '../view';
 import { FileInput } from '../controls/common';
 import { FileInput } from '../controls/common';
 import { TransformListController } from '../../controller/transform/list';
 import { TransformListController } from '../../controller/transform/list';
 import { FileEntity } from 'mol-view/state/entity';
 import { FileEntity } from 'mol-view/state/entity';
-import { MmcifFileToSpacefill } from 'mol-view/state/transform';
+import { MmcifFileToModel, ModelToStructure, StructureToBallAndStick, StructureToSpacefill, StructureToDistanceRestraint, StructureToBackbone } from 'mol-view/state/transform';
 import { StateContext } from 'mol-view/state/context';
 import { StateContext } from 'mol-view/state/context';
+import { SpacefillProps } from 'mol-geo/representation/structure/spacefill';
+import { BallAndStickProps } from 'mol-geo/representation/structure/ball-and-stick';
+import { DistanceRestraintProps } from 'mol-geo/representation/structure/distance-restraint';
+import { BackboneProps } from 'mol-geo/representation/structure/backbone';
+
+const spacefillProps: SpacefillProps = {
+    doubleSided: true,
+    colorTheme: { name: 'chain-id' },
+    quality: 'auto',
+    useFog: false
+}
+
+const ballAndStickProps: BallAndStickProps = {
+    doubleSided: true,
+    colorTheme: { name: 'chain-id' },
+    sizeTheme: { name: 'uniform', value: 0.05 },
+    linkRadius: 0.05,
+    quality: 'auto',
+    useFog: false
+}
+
+const distanceRestraintProps: DistanceRestraintProps = {
+    doubleSided: true,
+    colorTheme: { name: 'chain-id' },
+    linkRadius: 0.5,
+    quality: 'auto',
+    useFog: false
+}
+
+const backboneProps: BackboneProps = {
+    doubleSided: true,
+    colorTheme: { name: 'chain-id' },
+    quality: 'auto',
+    useFog: false
+}
 
 
 export class FileLoader extends View<TransformListController, {}, { ctx: StateContext }> {
 export class FileLoader extends View<TransformListController, {}, { ctx: StateContext }> {
     render() {
     render() {
         return <div className='molstar-file-loader'>
         return <div className='molstar-file-loader'>
             <FileInput
             <FileInput
                 accept='*.cif'
                 accept='*.cif'
-                onChange={files => {
+                onChange={async files => {
                     if (files) {
                     if (files) {
-                        const fileEntity = FileEntity.ofFile(this.props.ctx, files[0])
-                        MmcifFileToSpacefill.apply(this.props.ctx, fileEntity)
+                        const ctx = this.props.ctx
+                        const fileEntity = FileEntity.ofFile(ctx, files[0])
+                        const modelEntity = await MmcifFileToModel.apply(ctx, fileEntity)
+                        const structureEntity = await ModelToStructure.apply(ctx, modelEntity)
+
+                        StructureToBallAndStick.apply(ctx, structureEntity, { ...ballAndStickProps, visible: true })
+                        StructureToSpacefill.apply(ctx, structureEntity, { ...spacefillProps, visible: false })
+                        StructureToDistanceRestraint.apply(ctx, structureEntity, { ...distanceRestraintProps, visible: false })
+                        StructureToBackbone.apply(ctx, structureEntity, { ...backboneProps, visible: true })
                     }
                     }
                 }}
                 }}
             />
             />

+ 15 - 0
src/mol-app/ui/transform/list.tsx

@@ -14,13 +14,20 @@ import { Controller } from '../../controller/controller';
 import { TransformListController } from '../../controller/transform/list';
 import { TransformListController } from '../../controller/transform/list';
 import { AnyTransform } from 'mol-view/state/transform';
 import { AnyTransform } from 'mol-view/state/transform';
 import { Spacefill } from './spacefill';
 import { Spacefill } from './spacefill';
+import { BallAndStick } from './ball-and-stick';
 import { AnyEntity } from 'mol-view/state/entity';
 import { AnyEntity } from 'mol-view/state/entity';
 import { FileLoader } from './file-loader';
 import { FileLoader } from './file-loader';
 import { ModelToStructure } from './model';
 import { ModelToStructure } from './model';
 import { StructureCenter } from './structure';
 import { StructureCenter } from './structure';
+import { Cartoon } from './cartoon';
+import { DistanceRestraint } from './distance-restraint';
+import { Backbone } from './backbone';
+import { UrlLoader } from './url-loader';
 
 
 function getTransformComponent(controller: TransformListController, entity: AnyEntity, transform: AnyTransform) {
 function getTransformComponent(controller: TransformListController, entity: AnyEntity, transform: AnyTransform) {
     switch (transform.kind) {
     switch (transform.kind) {
+        case 'url-to-spacefill':
+            return <UrlLoader controller={controller} ctx={controller.context.stage.ctx}></UrlLoader>
         case 'file-to-spacefill':
         case 'file-to-spacefill':
             return <FileLoader controller={controller} ctx={controller.context.stage.ctx}></FileLoader>
             return <FileLoader controller={controller} ctx={controller.context.stage.ctx}></FileLoader>
         case 'model-to-structure':
         case 'model-to-structure':
@@ -29,6 +36,14 @@ function getTransformComponent(controller: TransformListController, entity: AnyE
             return <StructureCenter controller={controller} entity={entity} transform={transform} ctx={controller.context.stage.ctx}></StructureCenter>
             return <StructureCenter controller={controller} entity={entity} transform={transform} ctx={controller.context.stage.ctx}></StructureCenter>
         case 'spacefill-update':
         case 'spacefill-update':
             return <Spacefill controller={controller} entity={entity} transform={transform} ctx={controller.context.stage.ctx}></Spacefill>
             return <Spacefill controller={controller} entity={entity} transform={transform} ctx={controller.context.stage.ctx}></Spacefill>
+        case 'ballandstick-update':
+            return <BallAndStick controller={controller} entity={entity} transform={transform} ctx={controller.context.stage.ctx}></BallAndStick>
+        case 'distancerestraint-update':
+            return <DistanceRestraint controller={controller} entity={entity} transform={transform} ctx={controller.context.stage.ctx}></DistanceRestraint>
+        case 'backbone-update':
+            return <Backbone controller={controller} entity={entity} transform={transform} ctx={controller.context.stage.ctx}></Backbone>
+        case 'cartoon-update':
+            return <Cartoon controller={controller} entity={entity} transform={transform} ctx={controller.context.stage.ctx}></Cartoon>
     }
     }
     return <Transform controller={controller} entity={entity} transform={transform}></Transform>
     return <Transform controller={controller} entity={entity} transform={transform}></Transform>
 }
 }

+ 33 - 2
src/mol-app/ui/transform/spacefill.tsx

@@ -15,9 +15,11 @@ import { Toggle } from '../controls/common';
 import { SpacefillEntity } from 'mol-view/state/entity';
 import { SpacefillEntity } from 'mol-view/state/entity';
 import { SpacefillUpdate } from 'mol-view/state/transform'
 import { SpacefillUpdate } from 'mol-view/state/transform'
 import { StateContext } from 'mol-view/state/context';
 import { StateContext } from 'mol-view/state/context';
-import { ColorTheme } from 'mol-geo/theme';
+import { ColorTheme, SizeTheme } from 'mol-geo/theme';
 import { Color, ColorNames } from 'mol-util/color';
 import { Color, ColorNames } from 'mol-util/color';
 import { Slider } from '../controls/slider';
 import { Slider } from '../controls/slider';
+import { VisualQuality } from 'mol-geo/representation/util';
+import { Unit } from 'mol-model/structure';
 
 
 export const ColorThemeInfo = {
 export const ColorThemeInfo = {
     'atom-index': {},
     'atom-index': {},
@@ -35,9 +37,13 @@ interface SpacefillState {
     detail: number
     detail: number
     colorTheme: ColorTheme
     colorTheme: ColorTheme
     colorValue: Color
     colorValue: Color
+    sizeTheme: SizeTheme
     visible: boolean
     visible: boolean
     alpha: number
     alpha: number
     depthMask: boolean
     depthMask: boolean
+    useFog: boolean
+    quality: VisualQuality
+    unitKinds: Unit.Kind[]
 }
 }
 
 
 export class Spacefill extends View<Controller<any>, SpacefillState, { transform: SpacefillUpdate, entity: SpacefillEntity, ctx: StateContext }> {
 export class Spacefill extends View<Controller<any>, SpacefillState, { transform: SpacefillUpdate, entity: SpacefillEntity, ctx: StateContext }> {
@@ -48,12 +54,21 @@ export class Spacefill extends View<Controller<any>, SpacefillState, { transform
         detail: 2,
         detail: 2,
         colorTheme: { name: 'element-symbol' } as ColorTheme,
         colorTheme: { name: 'element-symbol' } as ColorTheme,
         colorValue: 0x000000,
         colorValue: 0x000000,
+        sizeTheme: { name: 'uniform' } as SizeTheme,
         visible: true,
         visible: true,
         alpha: 1,
         alpha: 1,
-        depthMask: true
+        depthMask: true,
+        useFog: true,
+        quality: 'auto' as VisualQuality,
+        unitKinds: [] as Unit.Kind[]
+    }
+
+    componentWillMount() {
+        this.setState({ ...this.state, ...this.props.entity.value.props })
     }
     }
 
 
     update(state?: Partial<SpacefillState>) {
     update(state?: Partial<SpacefillState>) {
+        console.log(state)
         const { transform, entity, ctx } = this.props
         const { transform, entity, ctx } = this.props
         const newState = { ...this.state, ...state }
         const newState = { ...this.state, ...state }
         this.setState(newState)
         this.setState(newState)
@@ -63,6 +78,10 @@ export class Spacefill extends View<Controller<any>, SpacefillState, { transform
     render() {
     render() {
         const { transform } = this.props
         const { transform } = this.props
 
 
+        const qualityOptions = ['auto', 'custom', 'highest', 'high', 'medium', 'low', 'lowest'].map((name, idx) => {
+            return <option key={name} value={name}>{name}</option>
+        })
+
         const sphereDetailOptions = [0, 1, 2, 3].map((value, idx) => {
         const sphereDetailOptions = [0, 1, 2, 3].map((value, idx) => {
             return <option key={value} value={value}>{value.toString()}</option>
             return <option key={value} value={value}>{value.toString()}</option>
         })
         })
@@ -87,6 +106,18 @@ export class Spacefill extends View<Controller<any>, SpacefillState, { transform
                 </div>
                 </div>
                 <div className='molstar-panel-body'>
                 <div className='molstar-panel-body'>
                     <div>
                     <div>
+                    <div className='molstar-control-row molstar-options-group'>
+                        <span>Quality</span>
+                            <div>
+                                <select
+                                    className='molstar-form-control'
+                                    value={this.state.quality}
+                                    onChange={(e) => this.update({ quality: e.target.value as VisualQuality })}
+                                >
+                                    {qualityOptions}
+                                </select>
+                            </div>
+                        </div>
                         <div className='molstar-control-row molstar-options-group'>
                         <div className='molstar-control-row molstar-options-group'>
                             <span>Sphere detail</span>
                             <span>Sphere detail</span>
                             <div>
                             <div>

+ 124 - 0
src/mol-app/ui/transform/url-loader.tsx

@@ -0,0 +1,124 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+import * as React from 'react'
+import { View } from '../view';
+import { TransformListController } from '../../controller/transform/list';
+import { UrlEntity } from 'mol-view/state/entity';
+import { ModelToStructure, StructureToBallAndStick, StructureToSpacefill, StructureToDistanceRestraint, StructureToBackbone, MmcifUrlToModel, StructureToCartoon } from 'mol-view/state/transform';
+import { StateContext } from 'mol-view/state/context';
+import { SpacefillProps } from 'mol-geo/representation/structure/spacefill';
+import { BallAndStickProps } from 'mol-geo/representation/structure/ball-and-stick';
+import { DistanceRestraintProps } from 'mol-geo/representation/structure/distance-restraint';
+import { BackboneProps } from 'mol-geo/representation/structure/backbone';
+import { CartoonProps } from 'mol-geo/representation/structure/cartoon';
+
+const spacefillProps: SpacefillProps = {
+    doubleSided: true,
+    colorTheme: { name: 'chain-id' },
+    quality: 'auto',
+    useFog: false
+}
+
+const ballAndStickProps: BallAndStickProps = {
+    doubleSided: true,
+    colorTheme: { name: 'chain-id' },
+    sizeTheme: { name: 'uniform', value: 0.05 },
+    linkRadius: 0.05,
+    quality: 'auto',
+    useFog: false
+}
+
+const distanceRestraintProps: DistanceRestraintProps = {
+    doubleSided: true,
+    colorTheme: { name: 'chain-id' },
+    linkRadius: 0.5,
+    quality: 'auto',
+    useFog: false
+}
+
+const backboneProps: BackboneProps = {
+    doubleSided: true,
+    colorTheme: { name: 'chain-id' },
+    quality: 'auto',
+    useFog: false
+}
+
+const cartoonProps: CartoonProps = {
+    doubleSided: true,
+    colorTheme: { name: 'chain-id' },
+    quality: 'auto',
+    useFog: false
+}
+
+function getPdbdevUrl(pdbdevId: string) {
+    return `https://pdb-dev.wwpdb.org/static/cif/${pdbdevId}.cif`
+}
+
+const exampleUrls = {
+    PDBDEV_00000001: getPdbdevUrl('PDBDEV_00000001'), // ok
+    PDBDEV_00000002: getPdbdevUrl('PDBDEV_00000002'), // ok
+    PDBDEV_00000003: getPdbdevUrl('PDBDEV_00000003'), // ok
+    PDBDEV_00000004: getPdbdevUrl('PDBDEV_00000004'), // TODO issue with cross-link extraction
+    PDBDEV_00000005: getPdbdevUrl('PDBDEV_00000005'), // ok
+    PDBDEV_00000006: getPdbdevUrl('PDBDEV_00000006'), // TODO only three spacefill atoms rendered
+    PDBDEV_00000007: getPdbdevUrl('PDBDEV_00000007'), // TODO only three spacefill atoms rendered
+    PDBDEV_00000008: getPdbdevUrl('PDBDEV_00000008'), // ok
+    PDBDEV_00000010: getPdbdevUrl('PDBDEV_00000010'), // ok
+    PDBDEV_00000011: getPdbdevUrl('PDBDEV_00000011'), // ok
+    PDBDEV_00000012: getPdbdevUrl('PDBDEV_00000012'), // ok
+    PDBDEV_00000014: getPdbdevUrl('PDBDEV_00000014'), // ok
+    PDBDEV_00000016: getPdbdevUrl('PDBDEV_00000016'),
+}
+
+export class UrlLoader extends View<TransformListController, { }, { ctx: StateContext }> {
+    async load(name: keyof typeof exampleUrls) {
+        console.log(exampleUrls[name])
+        const ctx = this.props.ctx
+        const urlEntity = UrlEntity.ofUrl(ctx, exampleUrls[name])
+        console.log(await urlEntity.value.getData())
+        const modelEntity = await MmcifUrlToModel.apply(ctx, urlEntity)
+        const structureEntity = await ModelToStructure.apply(ctx, modelEntity)
+
+        StructureToBallAndStick.apply(ctx, structureEntity, { ...ballAndStickProps, visible: true })
+        StructureToSpacefill.apply(ctx, structureEntity, { ...spacefillProps, visible: false })
+        StructureToDistanceRestraint.apply(ctx, structureEntity, { ...distanceRestraintProps, visible: false })
+        StructureToBackbone.apply(ctx, structureEntity, { ...backboneProps, visible: true })
+        StructureToCartoon.apply(ctx, structureEntity, { ...cartoonProps, visible: false })
+    }
+
+    render() {
+        const exampleOptions = Object.keys(exampleUrls).map(name => {
+            return <option key={name} value={name}>{name}</option>
+        })
+
+        return <div className='molstar-transformer-wrapper'>
+            <div className='molstar-panel molstar-control molstar-transformer molstar-panel-expanded'>
+                <div className='molstar-panel-body'>
+                    <div>
+                        <div className='molstar-control-row molstar-options-group'>
+                            <span>Examples</span>
+                            <div>
+                                <select
+                                    className='molstar-form-control'
+                                    value=''
+                                    onChange={(e) => {
+                                        if (e.target.value) {
+                                            this.load(e.target.value as keyof typeof exampleUrls)}
+                                        }
+                                    }
+                                >
+                                    <option key='' value=''></option>
+                                    {exampleOptions}
+                                </select>
+                            </div>
+                        </div>
+                    </div>
+                </div>
+            </div>
+        </div>;
+    }
+}

+ 6 - 0
src/mol-app/ui/visualization/image-canvas.tsx

@@ -22,6 +22,12 @@ export class ImageCanvas extends React.Component<{ imageData: ImageData, aspectR
     private canvas: HTMLCanvasElement | null = null;
     private canvas: HTMLCanvasElement | null = null;
     private ctx: CanvasRenderingContext2D | null = null;
     private ctx: CanvasRenderingContext2D | null = null;
 
 
+    state = {
+        imageData: new ImageData(1, 1),
+        width: 1,
+        height: 1
+    }
+
     updateStateFromProps() {
     updateStateFromProps() {
         this.setState({
         this.setState({
             imageData: this.props.imageData,
             imageData: this.props.imageData,

+ 85 - 0
src/mol-app/ui/visualization/sequence-view.tsx

@@ -0,0 +1,85 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author David Sehnal <david.sehnal@gmail.com>
+ */
+
+import * as React from 'react'
+import { View } from '../view';
+import { SequenceViewController } from '../../controller/visualization/sequence-view';
+import { Structure, StructureSequence, Queries, StructureSelection, StructureProperties, StructureQuery } from 'mol-model/structure';
+import { Context } from '../../context/context';
+import { InteractivityEvents } from '../../event/basic';
+import { EmptyLoci } from 'mol-model/loci';
+
+export class SequenceView extends View<SequenceViewController, {}, {}> {
+    render() {
+        const s = this.controller.latestState.structure;
+        if (!s) return <div className='molstar-sequence-view-wrap'>No structure available.</div>;
+
+        const seqs = Structure.getModels(s)[0].sequence.sequences;
+        return <div className='molstar-sequence-view-wrap'>
+            {seqs.map((seq, i) => <EntitySequence key={i} ctx={this.controller.context} seq={seq} structure={s} /> )}
+        </div>;
+    }
+}
+
+function createQuery(entityId: string, label_seq_id: number) {
+    return Queries.generators.atoms({
+        entityTest: ctx => StructureProperties.entity.id(ctx.element) === entityId,
+        residueTest: ctx => StructureProperties.residue.label_seq_id(ctx.element) === label_seq_id
+    });
+}
+
+// TODO: this is really ineffective and should be done using a canvas.
+class EntitySequence extends React.Component<{ ctx: Context, seq: StructureSequence.Entity, structure: Structure }> {
+
+    raiseInteractityEvent(seqId?: number) {
+        if (typeof seqId === 'undefined') {
+            InteractivityEvents.HighlightLoci.dispatch(this.props.ctx, EmptyLoci);
+            return;
+        }
+
+        const query = createQuery(this.props.seq.entityId, seqId);
+        const loci = StructureSelection.toLoci(StructureQuery.run(query, this.props.structure));
+        if (loci.elements.length === 0) InteractivityEvents.HighlightLoci.dispatch(this.props.ctx, EmptyLoci);
+        else InteractivityEvents.HighlightLoci.dispatch(this.props.ctx, loci);
+    }
+
+
+    render() {
+        const { ctx, seq } = this.props;
+        const { offset, sequence } = seq.sequence;
+
+        const elems: JSX.Element[] = [];
+        for (let i = 0, _i = sequence.length; i < _i; i++) {
+            elems[elems.length] = <ResidueView ctx={ctx} seqId={offset + i} letter={sequence[i]} parent={this} key={i} />;
+        }
+
+        return <div style={{ wordWrap: 'break-word' }}>
+            <span style={{ fontWeight: 'bold' }}>{this.props.seq.entityId}:{offset}&nbsp;</span>
+            {elems}
+        </div>;
+    }
+}
+
+class ResidueView extends React.Component<{ ctx: Context, seqId: number, letter: string, parent: EntitySequence }, { isHighlighted: boolean }> {
+    state = { isHighlighted: false }
+
+    mouseEnter = () => {
+        this.setState({ isHighlighted: true });
+        this.props.parent.raiseInteractityEvent(this.props.seqId);
+    }
+
+    mouseLeave = () => {
+        this.setState({ isHighlighted: false });
+        this.props.parent.raiseInteractityEvent();
+    }
+
+    render() {
+        return <span onMouseEnter={this.mouseEnter} onMouseLeave={this.mouseLeave}
+            style={{ cursor: 'pointer', backgroundColor: this.state.isHighlighted ? 'yellow' : void 0 }}>
+            {this.props.letter}
+        </span>;
+    }
+}

+ 23 - 5
src/mol-app/ui/visualization/viewport.tsx

@@ -14,6 +14,8 @@ import { View } from '../view';
 import { HelpBox, Toggle, Button } from '../controls/common'
 import { HelpBox, Toggle, Button } from '../controls/common'
 import { Slider } from '../controls/slider'
 import { Slider } from '../controls/slider'
 import { ImageCanvas } from './image-canvas';
 import { ImageCanvas } from './image-canvas';
+import { InteractivityEvents } from '../../event/basic';
+import { labelFirst } from 'mol-view/label';
 
 
 export class ViewportControls extends View<ViewportController, { showSceneOptions?: boolean, showHelp?: boolean }, {}> {
 export class ViewportControls extends View<ViewportController, { showSceneOptions?: boolean, showHelp?: boolean }, {}> {
     state = { showSceneOptions: false, showHelp: false };
     state = { showSceneOptions: false, showHelp: false };
@@ -132,6 +134,7 @@ export class Viewport extends View<ViewportController, ViewportState, { noWebGl?
         if (!this.canvas || !this.container || !this.controller.context.initStage(this.canvas, this.container)) {
         if (!this.canvas || !this.container || !this.controller.context.initStage(this.canvas, this.container)) {
             this.setState({ noWebGl: true });
             this.setState({ noWebGl: true });
         }
         }
+        this.handleResize()
 
 
         const viewer = this.controller.context.stage.viewer
         const viewer = this.controller.context.stage.viewer
 
 
@@ -142,10 +145,6 @@ export class Viewport extends View<ViewportController, ViewportState, { noWebGl?
             })
             })
         })
         })
 
 
-        viewer.identified.subscribe(info => {
-            this.setState({ info })
-        })
-
         viewer.didDraw.subscribe(() => {
         viewer.didDraw.subscribe(() => {
             // this.setState({ imageData: viewer.getImageData() })
             // this.setState({ imageData: viewer.getImageData() })
             this.setState({
             this.setState({
@@ -158,7 +157,26 @@ export class Viewport extends View<ViewportController, ViewportState, { noWebGl?
         })
         })
 
 
         viewer.input.resize.subscribe(() => this.handleResize())
         viewer.input.resize.subscribe(() => this.handleResize())
-        this.handleResize()
+
+        // TODO: clear highlight on mouse/touch down?
+
+        viewer.input.move.subscribe(({x, y, inside, buttons}) => {
+            if (!inside || buttons) return
+            const p = viewer.identify(x, y)
+            const loci = viewer.getLoci(p)
+            InteractivityEvents.HighlightLoci.dispatch(this.controller.context, loci);
+
+            // TODO use LabelLoci event and make configurable
+            const label = labelFirst(loci)
+            const info = `Object: ${p.objectId}, Instance: ${p.instanceId}, Element: ${p.elementId}, Label: ${label}`
+            this.setState({ info })
+        })
+
+        // TODO filter only for left button/single finger touch?
+        viewer.input.click.subscribe(({x, y}) => {
+            const loci = viewer.getLoci(viewer.identify(x, y))
+            InteractivityEvents.SelectLoci.dispatch(this.controller.context, loci);
+        })
     }
     }
 
 
     componentWillUnmount() {
     componentWillUnmount() {

+ 12 - 2
src/mol-data/db/column.ts

@@ -103,6 +103,15 @@ namespace Column {
         return lambdaColumn(spec);
         return lambdaColumn(spec);
     }
     }
 
 
+    /** values [min, max] (i.e. include both values) */
+    export function range(min: number, max: number): Column<number> {
+        return ofLambda({
+            value: i => i + min,
+            rowCount: Math.max(max - min + 1, 0),
+            schema: Schema.int
+        });
+    }
+
     export function ofArray<T extends Column.Schema>(spec: Column.ArraySpec<T>): Column<T['T']> {
     export function ofArray<T extends Column.Schema>(spec: Column.ArraySpec<T>): Column<T['T']> {
         return arrayColumn(spec);
         return arrayColumn(spec);
     }
     }
@@ -132,8 +141,8 @@ namespace Column {
         return createFirstIndexMapOfColumn(column);
         return createFirstIndexMapOfColumn(column);
     }
     }
 
 
-    export function createIndexer<T>(column: Column<T>) {
-        return createIndexerOfColumn(column);
+    export function createIndexer<T, R extends number = number>(column: Column<T>) {
+        return createIndexerOfColumn(column) as ((e: T) => R);
     }
     }
 
 
     export function mapToArray<T, S>(column: Column<T>, f: (v: T) => S, ctor?: ArrayCtor<S>): ArrayLike<S> {
     export function mapToArray<T, S>(column: Column<T>, f: (v: T) => S, ctor?: ArrayCtor<S>): ArrayLike<S> {
@@ -261,6 +270,7 @@ function arrayColumn<T extends Column.Schema>({ array, schema, valueKind }: Colu
 
 
 function windowColumn<T>(column: Column<T>, start: number, end: number) {
 function windowColumn<T>(column: Column<T>, start: number, end: number) {
     if (!column.isDefined) return Column.Undefined(end - start, column.schema);
     if (!column.isDefined) return Column.Undefined(end - start, column.schema);
+    if (start === 0 && end === column.rowCount) return column;
     if (!!column['@array'] && ColumnHelpers.isTypedArray(column['@array'])) return windowTyped(column, start, end);
     if (!!column['@array'] && ColumnHelpers.isTypedArray(column['@array'])) return windowTyped(column, start, end);
     return windowFull(column, start, end);
     return windowFull(column, start, end);
 }
 }

+ 13 - 0
src/mol-data/db/table.ts

@@ -101,6 +101,19 @@ namespace Table {
         return ret as Table<R>;
         return ret as Table<R>;
     }
     }
 
 
+    export function window<S extends R, R extends Schema>(table: Table<S>, schema: R, start: number, end: number) {
+        if (start === 0 && end === table._rowCount) return table;
+        const ret = Object.create(null);
+        const columns = Object.keys(schema);
+        ret._rowCount = end - start;
+        ret._columns = columns;
+        ret._schema = schema;
+        for (const k of columns) {
+            (ret as any)[k] = Column.window(table[k], start, end);
+        }
+        return ret as Table<R>;
+    }
+
     export function concat<S extends R, R extends Schema>(tables: Table<S>[], schema: R) {
     export function concat<S extends R, R extends Schema>(tables: Table<S>[], schema: R) {
         const ret = Object.create(null);
         const ret = Object.create(null);
         const columns = Object.keys(schema);
         const columns = Object.keys(schema);

+ 6 - 0
src/mol-data/int/_spec/interval.spec.ts

@@ -73,4 +73,10 @@ describe('interval', () => {
     test('predIndexInt3', Interval.findPredecessorIndexInInterval(Interval.ofRange(3, 10), 5, Interval.ofRange(2, 6)), 2);
     test('predIndexInt3', Interval.findPredecessorIndexInInterval(Interval.ofRange(3, 10), 5, Interval.ofRange(2, 6)), 2);
 
 
     testI('findRange', Interval.findRange(r05, 2, 3), Interval.ofRange(2, 3));
     testI('findRange', Interval.findRange(r05, 2, 3), Interval.ofRange(2, 3));
+
+    test('intersectionSize1', Interval.intersectionSize(Interval.ofRange(0, 5), Interval.ofRange(0, 5)), 6);
+    test('intersectionSize2', Interval.intersectionSize(Interval.ofRange(0, 5), Interval.ofRange(1, 2)), 2);
+    test('intersectionSize3', Interval.intersectionSize(Interval.ofRange(1, 2), Interval.ofRange(0, 5)), 2);
+    test('intersectionSize4', Interval.intersectionSize(Interval.ofRange(0, 5), Interval.ofRange(3, 8)), 3);
+    test('intersectionSize5', Interval.intersectionSize(Interval.ofRange(0, 5), Interval.ofRange(6, 8)), 0);
 });
 });

+ 25 - 0
src/mol-data/int/_spec/ordered-set.spec.ts

@@ -25,6 +25,7 @@ describe('ordered set', () => {
     const singleton10 = OrderedSet.ofSingleton(10);
     const singleton10 = OrderedSet.ofSingleton(10);
     const range1_4 = OrderedSet.ofRange(1, 4);
     const range1_4 = OrderedSet.ofRange(1, 4);
     const arr136 = OrderedSet.ofSortedArray([1, 3, 6]);
     const arr136 = OrderedSet.ofSortedArray([1, 3, 6]);
+    const arr12369 = OrderedSet.ofSortedArray([1, 2, 3, 6, 9]);
 
 
     const iB = (s: number, e: number) => Interval.ofBounds(s, e);
     const iB = (s: number, e: number) => Interval.ofBounds(s, e);
 
 
@@ -47,6 +48,17 @@ describe('ordered set', () => {
         expect(OrderedSet.areIntersecting(empty, singleton10)).toBe(false);
         expect(OrderedSet.areIntersecting(empty, singleton10)).toBe(false);
         expect(OrderedSet.areIntersecting(empty, range1_4)).toBe(false);
         expect(OrderedSet.areIntersecting(empty, range1_4)).toBe(false);
         expect(OrderedSet.areIntersecting(empty, arr136)).toBe(false);
         expect(OrderedSet.areIntersecting(empty, arr136)).toBe(false);
+
+        expect(OrderedSet.areIntersecting(Interval.ofRange(2, 3), arr12369)).toBe(true);
+        expect(OrderedSet.areIntersecting(Interval.ofRange(2, 6), arr12369)).toBe(true);
+        expect(OrderedSet.areIntersecting(Interval.ofRange(2, 8), arr12369)).toBe(true);
+        expect(OrderedSet.areIntersecting(Interval.ofRange(4, 8), arr12369)).toBe(true);
+
+        expect(OrderedSet.areIntersecting(Interval.ofRange(4, 5), arr12369)).toBe(false);
+        expect(OrderedSet.areIntersecting(Interval.ofRange(7, 8), arr12369)).toBe(false);
+
+        expect(OrderedSet.areIntersecting(Interval.ofRange(6, 6), arr12369)).toBe(true);
+        expect(OrderedSet.areIntersecting(Interval.ofRange(3, 4), OrderedSet.ofSortedArray([0, 1, 10]))).toBe(false);
     });
     });
 
 
     it('isSubset', () => {
     it('isSubset', () => {
@@ -104,6 +116,13 @@ describe('ordered set', () => {
         expect(OrderedSet.findRange(arr136, 2, 7)).toEqual(iB(1, 3));
         expect(OrderedSet.findRange(arr136, 2, 7)).toEqual(iB(1, 3));
     })
     })
 
 
+    it('intersectionSize', () => {
+        expect(OrderedSet.intersectionSize(arr136, range1_4)).toEqual(2);
+        expect(OrderedSet.intersectionSize(arr12369, range1_4)).toEqual(3);
+        expect(OrderedSet.intersectionSize(OrderedSet.ofSortedArray([12, 13, 16]), range1_4)).toEqual(0);
+        expect(OrderedSet.intersectionSize(OrderedSet.ofSortedArray([1, 2, 4]), range1_4)).toEqual(3);
+    })
+
     testEq('union ES', OrderedSet.union(empty, singleton10), [10]);
     testEq('union ES', OrderedSet.union(empty, singleton10), [10]);
     testEq('union ER', OrderedSet.union(empty, range1_4), [1, 2, 3, 4]);
     testEq('union ER', OrderedSet.union(empty, range1_4), [1, 2, 3, 4]);
     testEq('union EA', OrderedSet.union(empty, arr136), [1, 3, 6]);
     testEq('union EA', OrderedSet.union(empty, arr136), [1, 3, 6]);
@@ -157,4 +176,10 @@ describe('ordered set', () => {
     testEq('subtract AA', OrderedSet.subtract(arr136, arr136), []);
     testEq('subtract AA', OrderedSet.subtract(arr136, arr136), []);
     testEq('subtract AA1', OrderedSet.subtract(arr136, OrderedSet.ofSortedArray([2, 3, 4, 6, 7])), [1]);
     testEq('subtract AA1', OrderedSet.subtract(arr136, OrderedSet.ofSortedArray([2, 3, 4, 6, 7])), [1]);
     testEq('subtract AA2', OrderedSet.subtract(arr136, OrderedSet.ofSortedArray([0, 1, 6])), [3]);
     testEq('subtract AA2', OrderedSet.subtract(arr136, OrderedSet.ofSortedArray([0, 1, 6])), [3]);
+
+    it('foreach', () => {
+        const int = OrderedSet.ofBounds(1, 3), set = OrderedSet.ofSortedArray([2, 3, 4]);
+        expect(OrderedSet.forEach(int, (v, i, ctx) => ctx[i] = v, [] as number[])).toEqual([1, 2]);
+        expect(OrderedSet.forEach(set, (v, i, ctx) => ctx[i] = v, [] as number[])).toEqual([2, 3, 4]);
+    })
 });
 });

+ 2 - 2
src/mol-data/int/_spec/segmentation.spec.ts

@@ -21,12 +21,12 @@ describe('segments', () => {
 
 
     it('ofOffsetts', () => {
     it('ofOffsetts', () => {
         const p = Segmentation.ofOffsets([10, 12], Interval.ofBounds(10, 14));
         const p = Segmentation.ofOffsets([10, 12], Interval.ofBounds(10, 14));
-        expect(p.segments).toEqual(new Int32Array([0, 2, 4]))
+        expect(p.offsets).toEqual(new Int32Array([0, 2, 4]))
     });
     });
 
 
     it('map', () => {
     it('map', () => {
         const segs = Segmentation.create([0, 1, 2]);
         const segs = Segmentation.create([0, 1, 2]);
-        expect(segs.segmentMap).toEqual(new Int32Array([0, 1]));
+        expect(segs.index).toEqual(new Int32Array([0, 1]));
         expect(Segmentation.getSegment(segs, 0)).toBe(0);
         expect(Segmentation.getSegment(segs, 0)).toBe(0);
         expect(Segmentation.getSegment(segs, 1)).toBe(1);
         expect(Segmentation.getSegment(segs, 1)).toBe(1);
     });
     });

+ 6 - 0
src/mol-data/int/_spec/sorted-array.spec.ts

@@ -59,6 +59,12 @@ describe('sortedArray', () => {
         compareArrays(SortedArray.deduplicate(SortedArray.ofSortedArray([1, 2, 3])), [1, 2, 3]);
         compareArrays(SortedArray.deduplicate(SortedArray.ofSortedArray([1, 2, 3])), [1, 2, 3]);
     });
     });
 
 
+    it('indicesOf', () => {
+        compareArrays(SortedArray.indicesOf(SortedArray.ofSortedArray([10, 11, 12]), SortedArray.ofSortedArray([10, 12, 14])), [0, 2]);
+    })
+
+    test('intersectionSize', SortedArray.intersectionSize(a1234, a2468), 2);
+
     // console.log(Interval.findPredecessorIndexInInterval(Interval.ofBounds(0, 3), 2, Interval.ofBounds(0, 3)))
     // console.log(Interval.findPredecessorIndexInInterval(Interval.ofBounds(0, 3), 2, Interval.ofBounds(0, 3)))
     // console.log(SortedArray.findPredecessorIndexInInterval(SortedArray.ofSortedArray([0, 1, 2]), 2, Interval.ofBounds(0, 3)))
     // console.log(SortedArray.findPredecessorIndexInInterval(SortedArray.ofSortedArray([0, 1, 2]), 2, Interval.ofBounds(0, 3)))
 });
 });

+ 72 - 0
src/mol-data/int/_spec/sorted-ranges.spec.ts

@@ -0,0 +1,72 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+import SortedRanges from '../sorted-ranges'
+import OrderedSet from '../ordered-set';
+import SortedArray from '../sorted-array';
+
+describe('rangesArray', () => {
+    function test(name: string, a: any, b: any) {
+        it(name, () => expect(a).toEqual(b));
+    }
+
+    function testIterator(name: string, ranges: SortedRanges, set: OrderedSet, expectedValues: { index: number[], start: number[], end: number[]}) {
+        it(`iterator, ${name}`, () => {
+            const rangesIt = SortedRanges.transientSegments(ranges, set)
+            const { index, start, end } = expectedValues
+    
+            let i = 0
+            while (rangesIt.hasNext) {
+                const segment = rangesIt.move()
+                expect(segment.index).toBe(index[i])
+                expect(segment.start).toBe(start[i])
+                expect(segment.end).toBe(end[i])
+                ++i
+            }
+            expect(i).toBe(index.length)
+        })
+    }
+
+    const a1234 = SortedRanges.ofSortedRanges([1, 2, 3, 4]);
+    const a1134 = SortedRanges.ofSortedRanges([1, 1, 3, 4]);
+
+    test('size', SortedRanges.size(a1234), 4);
+    test('size', SortedRanges.size(a1134), 3);
+
+    test('min/max', [SortedRanges.min(a1234), SortedRanges.max(a1234)], [1, 4]);
+    test('start/end', [SortedRanges.start(a1234), SortedRanges.end(a1234)], [1, 5]);
+
+    testIterator('two ranges',
+        SortedRanges.ofSortedRanges([1, 2, 3, 4]),
+        OrderedSet.ofBounds(1, 4),
+        { index: [0, 1], start: [0, 2], end: [2, 4] }
+    )
+    testIterator('first range',
+        SortedRanges.ofSortedRanges([1, 2, 3, 4]),
+        SortedArray.ofSortedArray([2]),
+        { index: [0], start: [0], end: [1] }
+    )
+    testIterator('second range',
+        SortedRanges.ofSortedRanges([1, 2, 3, 4]),
+        SortedArray.ofSortedArray([4]),
+        { index: [1], start: [0], end: [1] }
+    )
+    testIterator('set not in ranges',
+        SortedRanges.ofSortedRanges([1, 2, 3, 4]),
+        SortedArray.ofSortedArray([10]),
+        { index: [], start: [], end: [] }
+    )
+    testIterator('set in second range and beyond',
+        SortedRanges.ofSortedRanges([1, 2, 3, 4]),
+        SortedArray.ofSortedArray([3, 10]),
+        { index: [1], start: [0], end: [2] }
+    )
+    testIterator('length 1 range',
+        SortedRanges.ofSortedRanges([1, 1, 3, 4]),
+        SortedArray.ofSortedArray([0, 1, 10]),
+        { index: [0], start: [1], end: [2] }
+    )
+});

+ 4 - 0
src/mol-data/int/impl/interval.ts

@@ -58,4 +58,8 @@ export function findRange(int: Tuple, min: number, max: number) {
 export function intersect(a: Tuple, b: Tuple) {
 export function intersect(a: Tuple, b: Tuple) {
     if (!areIntersecting(a, b)) return Empty;
     if (!areIntersecting(a, b)) return Empty;
     return ofBounds(Math.max(start(a), start(b)), Math.min(end(a), end(b)));
     return ofBounds(Math.max(start(a), start(b)), Math.min(end(a), end(b)));
+}
+
+export function intersectionSize(a: Tuple, b: Tuple) {
+    return size(findRange(a, min(b), max(b)))
 }
 }

+ 31 - 1
src/mol-data/int/impl/ordered-set.ts

@@ -29,6 +29,8 @@ export function indexOf(set: OrderedSetImpl, x: number) { return I.is(set) ? I.i
 export function getAt(set: OrderedSetImpl, i: number) { return I.is(set) ? I.getAt(set, i) : set[i]; }
 export function getAt(set: OrderedSetImpl, i: number) { return I.is(set) ? I.getAt(set, i) : set[i]; }
 export function min(set: OrderedSetImpl) { return I.is(set) ? I.min(set) : S.min(set); }
 export function min(set: OrderedSetImpl) { return I.is(set) ? I.min(set) : S.min(set); }
 export function max(set: OrderedSetImpl) { return I.is(set) ? I.max(set) : S.max(set); }
 export function max(set: OrderedSetImpl) { return I.is(set) ? I.max(set) : S.max(set); }
+export function start(set: OrderedSetImpl) { return I.is(set) ? I.start(set) : S.start(set); }
+export function end(set: OrderedSetImpl) { return I.is(set) ? I.end(set) : S.end(set); }
 
 
 export function hashCode(set: OrderedSetImpl) { return I.is(set) ? I.hashCode(set) : S.hashCode(set); }
 export function hashCode(set: OrderedSetImpl) { return I.is(set) ? I.hashCode(set) : S.hashCode(set); }
 // TODO: possibly add more hash functions to allow for multilevel hashing.
 // TODO: possibly add more hash functions to allow for multilevel hashing.
@@ -70,6 +72,14 @@ export function findRange(set: OrderedSetImpl, min: number, max: number) {
     return I.is(set) ? I.findRange(set, min, max) : S.findRange(set, min, max);
     return I.is(set) ? I.findRange(set, min, max) : S.findRange(set, min, max);
 }
 }
 
 
+export function intersectionSize(a: OrderedSetImpl, b: OrderedSetImpl): number {
+    if (I.is(a)) {
+        if (I.is(b)) return I.intersectionSize(a, b);
+        return intersectionSizeSI(b, a);
+    } else if (I.is(b)) return intersectionSizeSI(a, b);
+    return S.intersectionSize(a, b);
+}
+
 export function union(a: OrderedSetImpl, b: OrderedSetImpl) {
 export function union(a: OrderedSetImpl, b: OrderedSetImpl) {
     if (I.is(a)) {
     if (I.is(a)) {
         if (I.is(b)) return unionII(a, b);
         if (I.is(b)) return unionII(a, b);
@@ -97,7 +107,7 @@ export function subtract(a: OrderedSetImpl, b: OrderedSetImpl) {
 function areEqualIS(a: I, b: S) { return I.size(a) === S.size(b) && I.start(a) === S.start(b) && I.end(a) === S.end(b); }
 function areEqualIS(a: I, b: S) { return I.size(a) === S.size(b) && I.start(a) === S.start(b) && I.end(a) === S.end(b); }
 
 
 function areIntersectingSI(a: S, b: I) {
 function areIntersectingSI(a: S, b: I) {
-    return areRangesIntersecting(a, b);
+    return a.length !== 0 && I.size(S.findRange(a, I.min(b), I.max(b))) !== 0;
 }
 }
 
 
 function isSubsetSI(a: S, b: I) {
 function isSubsetSI(a: S, b: I) {
@@ -163,6 +173,12 @@ function unionSI(a: S, b: I) {
     return ofSortedArray(indices);
     return ofSortedArray(indices);
 }
 }
 
 
+function intersectionSizeSI(a: S, b: I): number {
+    if (!I.size(b)) return 0;
+    const r = S.findRange(a, I.min(b), I.max(b));
+    return I.end(r) - I.start(r);
+}
+
 function intersectSI(a: S, b: I) {
 function intersectSI(a: S, b: I) {
     if (!I.size(b)) return Empty;
     if (!I.size(b)) return Empty;
 
 
@@ -253,4 +269,18 @@ function subtractIS(a: I, b: S) {
     }
     }
     for (let i = last + 1; i <= max; i++) ret[offset++] = i;
     for (let i = last + 1; i <= max; i++) ret[offset++] = i;
     return ofSortedArray(ret);
     return ofSortedArray(ret);
+}
+
+export function forEach(set: OrderedSetImpl, f: (value: number, i: number, ctx: any) => void, ctx: any) {
+    if (I.is(set)) {
+        const start = I.min(set);
+        for (let i = start, _i = I.max(set); i <= _i; i++) {
+            f(i, i - start, ctx);
+        }
+    } else {
+        for (let i = 0, _i = set.length; i < _i; i++) {
+            f(set[i], i, ctx);
+        }
+    }
+    return ctx;
 }
 }

+ 18 - 19
src/mol-data/int/impl/segmentation.ts

@@ -12,23 +12,23 @@ import Segs from '../segmentation'
 
 
 interface Segmentation {
 interface Segmentation {
     /** Segments stored as a sorted array */
     /** Segments stored as a sorted array */
-    segments: SortedArray,
+    offsets: SortedArray,
     /** Mapping of values to segments */
     /** Mapping of values to segments */
-    segmentMap: Int32Array,
+    index: Int32Array,
     /** Number of segments */
     /** Number of segments */
     count: number
     count: number
 }
 }
 
 
 export function create(values: ArrayLike<number>): Segmentation {
 export function create(values: ArrayLike<number>): Segmentation {
-    const segments = SortedArray.ofSortedArray(values);
-    const max = SortedArray.max(segments);
-    const segmentMap = new Int32Array(max);
+    const offsets = SortedArray.ofSortedArray(values);
+    const max = SortedArray.max(offsets);
+    const index = new Int32Array(max);
     for (let i = 0, _i = values.length - 1; i < _i; i++) {
     for (let i = 0, _i = values.length - 1; i < _i; i++) {
         for (let j = values[i], _j = values[i + 1]; j < _j; j++) {
         for (let j = values[i], _j = values[i + 1]; j < _j; j++) {
-            segmentMap[j] = i;
+            index[j] = i;
         }
         }
     }
     }
-    return { segments, segmentMap, count: values.length - 1 };
+    return { offsets, index, count: values.length - 1 };
 }
 }
 
 
 export function ofOffsets(offsets: ArrayLike<number>, bounds: Interval): Segmentation {
 export function ofOffsets(offsets: ArrayLike<number>, bounds: Interval): Segmentation {
@@ -41,27 +41,28 @@ export function ofOffsets(offsets: ArrayLike<number>, bounds: Interval): Segment
     return create(segments);
     return create(segments);
 }
 }
 
 
+/** Get number of segments in a segmentation */
 export function count({ count }: Segmentation) { return count; }
 export function count({ count }: Segmentation) { return count; }
-export function getSegment({ segmentMap }: Segmentation, value: number) { return segmentMap[value]; }
+export function getSegment({ index }: Segmentation, value: number) { return index[value]; }
 
 
-export function projectValue({ segments }: Segmentation, set: OrderedSet, value: number): Interval {
-    const last = OrderedSet.max(segments);
-    const idx = value >= last ? -1 : OrderedSet.findPredecessorIndex(segments, value - 1);
-    return OrderedSet.findRange(set, OrderedSet.getAt(segments, idx), OrderedSet.getAt(segments, idx + 1) - 1);
+export function projectValue({ offsets }: Segmentation, set: OrderedSet, value: number): Interval {
+    const last = OrderedSet.max(offsets);
+    const idx = value >= last ? -1 : OrderedSet.findPredecessorIndex(offsets, value - 1);
+    return OrderedSet.findRange(set, OrderedSet.getAt(offsets, idx), OrderedSet.getAt(offsets, idx + 1) - 1);
 }
 }
 
 
-export class SegmentIterator implements Iterator<Segs.Segment> {
+export class SegmentIterator<I extends number = number> implements Iterator<Segs.Segment<I>> {
     private segmentMin = 0;
     private segmentMin = 0;
     private segmentMax = 0;
     private segmentMax = 0;
     private setRange = Interval.Empty;
     private setRange = Interval.Empty;
-    private value: Segs.Segment = { index: 0, start: 0, end: 0 };
+    private value: Segs.Segment<I> = { index: 0 as I, start: 0, end: 0 };
 
 
     hasNext: boolean = false;
     hasNext: boolean = false;
 
 
     move() {
     move() {
         while (this.hasNext) {
         while (this.hasNext) {
             if (this.updateValue()) {
             if (this.updateValue()) {
-                this.value.index = this.segmentMin++;
+                this.value.index = this.segmentMin++ as I;
                 this.hasNext = this.segmentMax >= this.segmentMin && Interval.size(this.setRange) > 0;
                 this.hasNext = this.segmentMax >= this.segmentMin && Interval.size(this.setRange) > 0;
                 break;
                 break;
             } else {
             } else {
@@ -87,14 +88,12 @@ export class SegmentIterator implements Iterator<Segs.Segment> {
             this.hasNext = false;
             this.hasNext = false;
             return;
             return;
         }
         }
-
         this.segmentMin = this.segmentMap[OrderedSet.getAt(this.set, sMin)];
         this.segmentMin = this.segmentMap[OrderedSet.getAt(this.set, sMin)];
         this.segmentMax = this.segmentMap[OrderedSet.getAt(this.set, sMax)];
         this.segmentMax = this.segmentMap[OrderedSet.getAt(this.set, sMax)];
-
         this.hasNext = this.segmentMax >= this.segmentMin;
         this.hasNext = this.segmentMax >= this.segmentMin;
     }
     }
 
 
-    setSegment(segment: Segs.Segment) {
+    setSegment(segment: Segs.Segment<number>) {
         this.setRange = Interval.ofBounds(segment.start, segment.end);
         this.setRange = Interval.ofBounds(segment.start, segment.end);
         this.updateSegmentRange();
         this.updateSegmentRange();
     }
     }
@@ -107,5 +106,5 @@ export class SegmentIterator implements Iterator<Segs.Segment> {
 
 
 export function segments(segs: Segmentation, set: OrderedSet, segment?: Segs.Segment) {
 export function segments(segs: Segmentation, set: OrderedSet, segment?: Segs.Segment) {
     const int = typeof segment !== 'undefined' ? Interval.ofBounds(segment.start, segment.end) : Interval.ofBounds(0, OrderedSet.size(set));
     const int = typeof segment !== 'undefined' ? Interval.ofBounds(segment.start, segment.end) : Interval.ofBounds(0, OrderedSet.size(set));
-    return new SegmentIterator(segs.segments, segs.segmentMap, set, int);
+    return new SegmentIterator(segs.offsets, segs.index, set, int);
 }
 }

+ 60 - 20
src/mol-data/int/impl/sorted-array.ts

@@ -4,7 +4,7 @@
  * @author David Sehnal <david.sehnal@gmail.com>
  * @author David Sehnal <david.sehnal@gmail.com>
  */
  */
 
 
-import { sortArray, hash3, hash4 } from '../../util'
+import { sortArray, hash3, hash4, createRangeArray } from '../../util'
 import Interval from '../interval'
 import Interval from '../interval'
 
 
 type Nums = ArrayLike<number>
 type Nums = ArrayLike<number>
@@ -71,6 +71,7 @@ export function findPredecessorIndexInInterval(xs: Nums, v: number, bounds: Inte
     const sv = xs[s];
     const sv = xs[s];
     if (v <= sv) return s;
     if (v <= sv) return s;
     if (e > s && v > xs[e - 1]) return e;
     if (e > s && v > xs[e - 1]) return e;
+    // do a linear search if there are only 10 or less items remaining
     if (v - sv <= 11) return linearSearchPredInRange(xs, v, s + 1, e);
     if (v - sv <= 11) return linearSearchPredInRange(xs, v, s + 1, e);
     return binarySearchPredIndexRange(xs, v, s, e);
     return binarySearchPredIndexRange(xs, v, s, e);
 }
 }
@@ -82,6 +83,7 @@ export function findRange(xs: Nums, min: number, max: number) {
 function binarySearchRange(xs: Nums, value: number, start: number, end: number) {
 function binarySearchRange(xs: Nums, value: number, start: number, end: number) {
     let min = start, max = end - 1;
     let min = start, max = end - 1;
     while (min <= max) {
     while (min <= max) {
+        // do a linear search if there are only 10 or less items remaining
         if (min + 11 > max) {
         if (min + 11 > max) {
             for (let i = min; i <= max; i++) {
             for (let i = min; i <= max; i++) {
                 if (value === xs[i]) return i;
                 if (value === xs[i]) return i;
@@ -101,6 +103,7 @@ function binarySearchRange(xs: Nums, value: number, start: number, end: number)
 function binarySearchPredIndexRange(xs: Nums, value: number, start: number, end: number) {
 function binarySearchPredIndexRange(xs: Nums, value: number, start: number, end: number) {
     let min = start, max = end - 1;
     let min = start, max = end - 1;
     while (min < max) {
     while (min < max) {
+        // do a linear search if there are only 10 or less items remaining
         if (min + 11 > max) {
         if (min + 11 > max) {
             for (let i = min; i <= max; i++) {
             for (let i = min; i <= max; i++) {
                 if (value <= xs[i]) return i;
                 if (value <= xs[i]) return i;
@@ -158,15 +161,8 @@ export function isSubset(a: Nums, b: Nums) {
 export function union(a: Nums, b: Nums) {
 export function union(a: Nums, b: Nums) {
     if (a === b) return a;
     if (a === b) return a;
 
 
-    const { startI: sI, startJ: sJ, endI, endJ } = getSuitableIntersectionRange(a, b);
-    let i = sI, j = sJ;
-    let commonCount = 0;
-    while (i < endI && j < endJ) {
-        const x = a[i], y = b[j];
-        if (x < y) { i++; }
-        else if (x > y) { j++; }
-        else { i++; j++; commonCount++; }
-    }
+    const { startI, startJ, endI, endJ } = getSuitableIntersectionRange(a, b);
+    const commonCount = getCommonCount(a, b, startI, startJ, endI, endJ);
 
 
     const lenA = a.length, lenB = b.length;
     const lenA = a.length, lenB = b.length;
     // A === B || B is subset of A ==> A
     // A === B || B is subset of A ==> A
@@ -178,12 +174,12 @@ export function union(a: Nums, b: Nums) {
     let offset = 0;
     let offset = 0;
 
 
     // insert the "prefixes"
     // insert the "prefixes"
-    for (let k = 0; k < sI; k++) indices[offset++] = a[k];
-    for (let k = 0; k < sJ; k++) indices[offset++] = b[k];
+    for (let k = 0; k < startI; k++) indices[offset++] = a[k];
+    for (let k = 0; k < startJ; k++) indices[offset++] = b[k];
 
 
     // insert the common part
     // insert the common part
-    i = sI;
-    j = sJ;
+    let i = startI;
+    let j = startJ;
     while (i < endI && j < endJ) {
     while (i < endI && j < endJ) {
         const x = a[i], y = b[j];
         const x = a[i], y = b[j];
         if (x < y) { indices[offset++] = x; i++; }
         if (x < y) { indices[offset++] = x; i++; }
@@ -198,11 +194,14 @@ export function union(a: Nums, b: Nums) {
     return ofSortedArray(indices);
     return ofSortedArray(indices);
 }
 }
 
 
-export function intersect(a: Nums, b: Nums) {
-    if (a === b) return a;
+export function intersectionSize(a: Nums, b: Nums) {
+    if (a === b) return size(a);
+    const { startI, startJ, endI, endJ } = getSuitableIntersectionRange(a, b);
+    return getCommonCount(a, b, startI, startJ, endI, endJ);
+}
 
 
-    const { startI: sI, startJ: sJ, endI, endJ } = getSuitableIntersectionRange(a, b);
-    let i = sI, j = sJ;
+function getCommonCount(a: Nums, b: Nums, startI: number, startJ: number, endI: number, endJ: number) {
+    let i = startI, j = startJ;
     let commonCount = 0;
     let commonCount = 0;
     while (i < endI && j < endJ) {
     while (i < endI && j < endJ) {
         const x = a[i], y = b[j];
         const x = a[i], y = b[j];
@@ -210,6 +209,14 @@ export function intersect(a: Nums, b: Nums) {
         else if (x > y) { j++; }
         else if (x > y) { j++; }
         else { i++; j++; commonCount++; }
         else { i++; j++; commonCount++; }
     }
     }
+    return commonCount;
+}
+
+export function intersect(a: Nums, b: Nums) {
+    if (a === b) return a;
+
+    const { startI, startJ, endI, endJ } = getSuitableIntersectionRange(a, b);
+    const commonCount = getCommonCount(a, b, startI, startJ, endI, endJ);
 
 
     const lenA = a.length, lenB = b.length;
     const lenA = a.length, lenB = b.length;
     // no common elements
     // no common elements
@@ -221,8 +228,8 @@ export function intersect(a: Nums, b: Nums) {
 
 
     const indices = new Int32Array(commonCount);
     const indices = new Int32Array(commonCount);
     let offset = 0;
     let offset = 0;
-    i = sI;
-    j = sJ;
+    let i = startI;
+    let j = startJ;
     while (i < endI && j < endJ) {
     while (i < endI && j < endJ) {
         const x = a[i], y = b[j];
         const x = a[i], y = b[j];
         if (x < y) { i++; }
         if (x < y) { i++; }
@@ -289,6 +296,39 @@ export function deduplicate(xs: Nums) {
     return ret;
     return ret;
 }
 }
 
 
+export function indicesOf(a: Nums, b: Nums): Nums {
+    if (a === b) return ofSortedArray(createRangeArray(0, a.length - 1));
+
+    const { startI: sI, startJ: sJ, endI, endJ } = getSuitableIntersectionRange(a, b);
+    let i = sI, j = sJ;
+    let commonCount = 0;
+    while (i < endI && j < endJ) {
+        const x = a[i], y = b[j];
+        if (x < y) { i++; }
+        else if (x > y) { j++; }
+        else { i++; j++; commonCount++; }
+    }
+
+    const lenA = a.length;
+    // no common elements
+    if (!commonCount) return Empty;
+    // A is subset of B ==> A
+    if (commonCount === lenA) return ofSortedArray(createRangeArray(0, a.length - 1));
+
+    const indices = new Int32Array(commonCount);
+    let offset = 0;
+    i = sI;
+    j = sJ;
+    while (i < endI && j < endJ) {
+        const x = a[i], y = b[j];
+        if (x < y) { i++; }
+        else if (x > y) { j++; }
+        else { indices[offset++] = i; i++; j++; }
+    }
+
+    return ofSortedArray(indices);
+}
+
 const _maxIntRangeRet = { startI: 0, startJ: 0, endI: 0, endJ: 0 };
 const _maxIntRangeRet = { startI: 0, startJ: 0, endI: 0, endJ: 0 };
 // for small sets, just gets the whole range, for large sets does a bunch of binary searches
 // for small sets, just gets the whole range, for large sets does a bunch of binary searches
 function getSuitableIntersectionRange(a: Nums, b: Nums) {
 function getSuitableIntersectionRange(a: Nums, b: Nums) {

+ 28 - 26
src/mol-data/int/interval.ts

@@ -9,48 +9,50 @@ import * as Impl from './impl/interval'
 namespace Interval {
 namespace Interval {
     export const Empty: Interval = Impl.Empty as any;
     export const Empty: Interval = Impl.Empty as any;
 
 
-    export const ofSingleton: (value: number) => Interval = (v) => Impl.ofRange(v, v) as any;
+    export const ofSingleton: <T extends number = number>(value: T) => Interval<T> = (v) => Impl.ofRange(v, v) as any;
     /** Create interval from range [min, max] */
     /** Create interval from range [min, max] */
-    export const ofRange: (min: number, max: number) => Interval = Impl.ofRange as any;
+    export const ofRange: <T extends number = number>(min: T, max: T) => Interval<T> = Impl.ofRange as any;
     /** Create interval from bounds [start, end), i.e. [start, end - 1] */
     /** Create interval from bounds [start, end), i.e. [start, end - 1] */
-    export const ofBounds: (start: number, end: number) => Interval = Impl.ofBounds as any;
-    export const is: (v: any) => v is Interval = Impl.is as any;
+    export const ofBounds: <T extends number = number>(start: T, end: T) => Interval<T> = Impl.ofBounds as any;
+    export const is: <T extends number = number>(v: any) => v is Interval<T> = Impl.is as any;
 
 
     /** Test if a value is within the bounds of the interval */
     /** Test if a value is within the bounds of the interval */
-    export const has: (interval: Interval, x: number) => boolean = Impl.has as any;
-    export const indexOf: (interval: Interval, x: number) => number = Impl.indexOf as any;
-    export const getAt: (interval: Interval, i: number) => number = Impl.getAt as any;
-
-    /** Start value of the interval, same as min value */
-    export const start: (interval: Interval) => number = Impl.start as any;
-    /** End value of the interval, same as max + 1 */
-    export const end: (interval: Interval) => number = Impl.end as any;
-    /** Min value of the interval, same as start value */
-    export const min: (interval: Interval) => number = Impl.min as any;
-    /** Max value of the interval, same as end - 1 */
-    export const max: (interval: Interval) => number = Impl.max as any;
+    export const has: <T extends number = number>(interval: Interval<T>, x: T) => boolean = Impl.has as any;
+    export const indexOf: <T extends number = number>(interval: Interval<T>, x: T) => number = Impl.indexOf as any;
+    export const getAt: <T extends number = number>(interval: Interval<T>, i: number) => T = Impl.getAt as any;
+
+    /** Start value of the Interval<T>, same as min value */
+    export const start: <T extends number = number>(interval: Interval<T>) => T = Impl.start as any;
+    /** End value of the Interval<T>, same as max + 1 */
+    export const end: <T extends number = number>(interval: Interval<T>) => T = Impl.end as any;
+    /** Min value of the Interval<T>, same as start value */
+    export const min: <T extends number = number>(interval: Interval<T>) => T = Impl.min as any;
+    /** Max value of the Interval<T>, same as end - 1 */
+    export const max: <T extends number = number>(interval: Interval<T>) => T = Impl.max as any;
     /** Number of values in the interval */
     /** Number of values in the interval */
-    export const size: (interval: Interval) => number = Impl.size as any;
+    export const size: <T extends number = number>(interval: Interval<T>) => number = Impl.size as any;
     /** Hash code describing the interval */
     /** Hash code describing the interval */
-    export const hashCode: (interval: Interval) => number = Impl.hashCode as any;
+    export const hashCode: <T extends number = number>(interval: Interval<T>) => number = Impl.hashCode as any;
 
 
     /** Test if two intervals are identical */
     /** Test if two intervals are identical */
-    export const areEqual: (a: Interval, b: Interval) => boolean = Impl.areEqual as any;
+    export const areEqual: <T extends number = number>(a: Interval<T>, b: Interval<T>) => boolean = Impl.areEqual as any;
     /** Test if two intervals are intersecting, i.e. their bounds overlap */
     /** Test if two intervals are intersecting, i.e. their bounds overlap */
-    export const areIntersecting: (a: Interval, b: Interval) => boolean = Impl.areIntersecting as any;
+    export const areIntersecting: <T extends number = number>(a: Interval<T>, b: Interval<T>) => boolean = Impl.areIntersecting as any;
 
 
     /** Test if interval b is fully included in interval a */
     /** Test if interval b is fully included in interval a */
-    export const isSubInterval: (a: Interval, b: Interval) => boolean = Impl.isSubInterval as any;
+    export const isSubInterval: <T extends number = number>(a: Interval<T>, b: Interval<T>) => boolean = Impl.isSubInterval as any;
 
 
-    export const findPredecessorIndex: (interval: Interval, x: number) => number = Impl.findPredecessorIndex as any;
-    export const findPredecessorIndexInInterval: (interval: Interval, x: number, bounds: Interval) => number = Impl.findPredecessorIndexInInterval as any;
-    export const findRange: (interval: Interval, min: number, max: number) => Interval = Impl.findRange as any;
+    export const findPredecessorIndex: <T extends number = number>(interval: Interval<T>, x: T) => number = Impl.findPredecessorIndex as any;
+    export const findPredecessorIndexInInterval: <T extends number = number>(interval: Interval<T>, x: T, bounds: Interval) => number = Impl.findPredecessorIndexInInterval as any;
+    export const findRange: <T extends number = number>(interval: Interval<T>, min: T, max: T) => Interval = Impl.findRange as any;
+    /** Size of the intersection of the two intervals */
+    export const intersectionSize: <T extends number = number>(a: Interval<T>, b: Interval<T>) => number = Impl.intersectionSize as any;
 
 
     /** Get a new interval that is the intersection of the two intervals */
     /** Get a new interval that is the intersection of the two intervals */
-    export const intersect: (a: Interval, b: Interval) => Interval = Impl.intersect as any;
+    export const intersect: <T extends number = number>(a: Interval<T>, b: Interval<T>) => Interval<T> = Impl.intersect as any;
 }
 }
 
 
 /** Interval describing a range [min, max] of values */
 /** Interval describing a range [min, max] of values */
-interface Interval { '@type': 'int-interval' }
+interface Interval<T extends number = number> { '@type': 'int-interval' }
 
 
 export default Interval
 export default Interval

+ 47 - 21
src/mol-data/int/ordered-set.ts

@@ -6,37 +6,63 @@
 
 
 import * as Base from './impl/ordered-set'
 import * as Base from './impl/ordered-set'
 import Interval from './interval'
 import Interval from './interval'
+import SortedArray from './sorted-array';
 
 
+/** test */
 namespace OrderedSet {
 namespace OrderedSet {
     export const Empty: OrderedSet = Base.Empty as any;
     export const Empty: OrderedSet = Base.Empty as any;
-    export const ofSingleton: (value: number) => OrderedSet = Base.ofSingleton as any;
-    export const ofRange: (min: number, max: number) => OrderedSet = Base.ofRange as any;
-    export const ofBounds: (min: number, max: number) => OrderedSet = Base.ofBounds as any;
+    export const ofSingleton: <T extends number = number>(value: T) => OrderedSet<T> = Base.ofSingleton as any;
+    export const ofRange: <T extends number = number>(min: T, max: T) => OrderedSet<T> = Base.ofRange as any;
+    export const ofBounds: <T extends number = number>(min: T, max: T) => OrderedSet<T> = Base.ofBounds as any;
     /** It is the responsibility of the caller to ensure the array is sorted and contains unique values. */
     /** It is the responsibility of the caller to ensure the array is sorted and contains unique values. */
-    export const ofSortedArray: (xs: ArrayLike<number>) => OrderedSet = Base.ofSortedArray as any;
+    export const ofSortedArray: <T extends number = number>(xs: ArrayLike<T>) => OrderedSet<T> = Base.ofSortedArray as any;
 
 
-    export const has: (set: OrderedSet, x: number) => boolean = Base.has as any;
-    export const indexOf: (set: OrderedSet, x: number) => number = Base.indexOf as any;
-    export const getAt: (set: OrderedSet, i: number) => number = Base.getAt as any;
+    export const has: <T extends number = number>(set: OrderedSet<T>, x: T) => boolean = Base.has as any;
+    export const indexOf: <T extends number = number>(set: OrderedSet<T>, x: T) => number = Base.indexOf as any;
+    export const getAt: <T extends number = number>(set: OrderedSet<T>, i: number) => T = Base.getAt as any;
 
 
-    export const min: (set: OrderedSet) => number = Base.min as any;
-    export const max: (set: OrderedSet) => number = Base.max as any;
-    export const size: (set: OrderedSet) => number = Base.size as any;
-    export const hashCode: (set: OrderedSet) => number = Base.hashCode as any;
+    export const min: <T extends number = number>(set: OrderedSet<T>) => T = Base.min as any;
+    export const max: <T extends number = number>(set: OrderedSet<T>) => T = Base.max as any;
+    export const start: <T extends number = number>(set: OrderedSet<T>) => T = Base.start as any;
+    export const end: <T extends number = number>(set: OrderedSet<T>) => T = Base.end as any;
+    export const size: <T extends number = number>(set: OrderedSet<T>) => number = Base.size as any;
+    export const hashCode: <T extends number = number>(set: OrderedSet<T>) => number = Base.hashCode as any;
 
 
-    export const areEqual: (a: OrderedSet, b: OrderedSet) => boolean = Base.areEqual as any;
-    export const areIntersecting: (a: OrderedSet, b: OrderedSet) => boolean = Base.areIntersecting as any;
-    export const isSubset: (a: OrderedSet, b: OrderedSet) => boolean = Base.isSubset as any;
+    export const areEqual: <T extends number = number>(a: OrderedSet<T>, b: OrderedSet<T>) => boolean = Base.areEqual as any;
+    export const areIntersecting: <T extends number = number>(a: OrderedSet<T>, b: OrderedSet<T>) => boolean = Base.areIntersecting as any;
+    /** Check if the 2nd argument is a subset of the 1st */
+    export const isSubset: <T extends number = number>(a: OrderedSet<T>, b: OrderedSet<T>) => boolean = Base.isSubset as any;
 
 
-    export const union: (a: OrderedSet, b: OrderedSet) => OrderedSet = Base.union as any;
-    export const intersect: (a: OrderedSet, b: OrderedSet) => OrderedSet = Base.intersect as any;
-    export const subtract: (a: OrderedSet, b: OrderedSet) => OrderedSet = Base.subtract as any;
+    export const union: <T extends number = number>(a: OrderedSet<T>, b: OrderedSet<T>) => OrderedSet<T> = Base.union as any;
+    export const intersect: <T extends number = number>(a: OrderedSet<T>, b: OrderedSet<T>) => OrderedSet<T> = Base.intersect as any;
+    export const subtract: <T extends number = number>(a: OrderedSet<T>, b: OrderedSet<T>) => OrderedSet<T> = Base.subtract as any;
 
 
-    export const findPredecessorIndex: (set: OrderedSet, x: number) => number = Base.findPredecessorIndex as any;
-    export const findPredecessorIndexInInterval: (set: OrderedSet, x: number, range: Interval) => number = Base.findPredecessorIndexInInterval as any;
-    export const findRange: (set: OrderedSet, min: number, max: number) => Interval = Base.findRange as any;
+    export const findPredecessorIndex: <T extends number = number>(set: OrderedSet<T>, x: number) => number = Base.findPredecessorIndex as any;
+    export const findPredecessorIndexInInterval: <T extends number = number>(set: OrderedSet<T>, x: T, range: Interval) => number = Base.findPredecessorIndexInInterval as any;
+    export const findRange: <T extends number = number>(set: OrderedSet<T>, min: T, max: T) => Interval = Base.findRange as any;
+    export const intersectionSize: <T extends number = number>(a: OrderedSet<T>, b: OrderedSet<T>) => number = Base.intersectionSize as any;
+
+    export function forEach<T extends number, Ctx>(set: OrderedSet<T>, f: (v: T, i: number, ctx: Ctx) => void, ctx?: Ctx): Ctx {
+        return Base.forEach(set as any, f as any, ctx);
+    }
+
+    export function isInterval<T extends number = number>(set: OrderedSet<T>): set is Interval<T> {
+        return Interval.is(set);
+    }
+
+    export function isSortedArray<T extends number = number>(set: OrderedSet<T>): set is SortedArray<T> {
+        return !Interval.is(set);
+    }
+
+    export function toArray<T extends number = number>(set: OrderedSet<T>): T[] {
+        const array: T[] = []
+        OrderedSet.forEach(set, v => array.push(v))
+        return array
+    }
 }
 }
 
 
-interface OrderedSet { '@type': 'int-interval' | 'int-sorted-array' }
+/** Represents bla */
+type OrderedSet<T extends number = number> = SortedArray<T> | Interval<T>
+
 
 
 export default OrderedSet
 export default OrderedSet

+ 14 - 10
src/mol-data/int/segmentation.ts

@@ -9,23 +9,27 @@ import OrderedSet from './ordered-set'
 import * as Impl from './impl/segmentation'
 import * as Impl from './impl/segmentation'
 
 
 namespace Segmentation {
 namespace Segmentation {
-    export interface Segment { index: number, start: number, end: number }
+    export interface Segment<I extends number = number> { index: I, start: number, end: number }
 
 
-    export const create: (segs: ArrayLike<number>) => Segmentation = Impl.create as any;
-    export const ofOffsets: (offsets: ArrayLike<number>, bounds: Interval) => Segmentation = Impl.ofOffsets as any;
+    export const create: <T extends number = number, I extends number = number>(segs: ArrayLike<T>) => Segmentation<T, I> = Impl.create as any;
+    export const ofOffsets: <T extends number = number, I extends number = number>(offsets: ArrayLike<T>, bounds: Interval) => Segmentation<T, I> = Impl.ofOffsets as any;
 
 
-    export const count: (segs: Segmentation) => number = Impl.count as any;
-    export const getSegment: (segs: Segmentation, value: number) => number = Impl.getSegment as any;
-    export const projectValue: (segs: Segmentation, set: OrderedSet, value: number) => Interval = Impl.projectValue as any;
+    export const count: <T extends number = number, I extends number = number>(segs: Segmentation<T, I>) => number = Impl.count as any;
+    export const getSegment: <T extends number = number, I extends number = number>(segs: Segmentation<T, I>, value: T) => number = Impl.getSegment as any;
+    export const projectValue: <T extends number = number, I extends number = number>(segs: Segmentation<T, I>, set: OrderedSet<T>, value: T) => Interval = Impl.projectValue as any;
 
 
     // Segment iterator that mutates a single segment object to mark all the segments.
     // Segment iterator that mutates a single segment object to mark all the segments.
-    export const transientSegments: (segs: Segmentation, set: OrderedSet, segment?: Segment) => Impl.SegmentIterator = Impl.segments as any;
+    export const transientSegments: <T extends number = number, I extends number = number>(segs: Segmentation<T, I>, set: OrderedSet<T>, segment?: Segment) => Impl.SegmentIterator<I> = Impl.segments as any;
+
+    export type SegmentIterator<I extends number = number> = Impl.SegmentIterator<I>
 }
 }
 
 
-interface Segmentation {
+interface Segmentation<T extends number = number, I extends number = number> {
     '@type': 'segmentation',
     '@type': 'segmentation',
-    readonly segments: ArrayLike<number>,
-    readonly segmentMap: ArrayLike<number>,
+    /** All segments are defined by offsets [offsets[i], offsets[i + 1]) for i \in [0, count - 1] */
+    readonly offsets: ArrayLike<T>,
+    /** Segment index of the i-th element */
+    readonly index: ArrayLike<I>,
     readonly count: number
     readonly count: number
 }
 }
 
 

+ 38 - 33
src/mol-data/int/sorted-array.ts

@@ -9,41 +9,46 @@ import Interval from './interval'
 
 
 namespace SortedArray {
 namespace SortedArray {
     export const Empty: SortedArray = Impl.Empty as any;
     export const Empty: SortedArray = Impl.Empty as any;
-    export const ofUnsortedArray: (xs: ArrayLike<number>) => SortedArray = Impl.ofUnsortedArray as any;
-    export const ofSingleton: (v: number) => SortedArray = Impl.ofSingleton as any;
-    export const ofSortedArray: (xs: ArrayLike<number>) => SortedArray = Impl.ofSortedArray as any;
+    export const ofUnsortedArray: <T extends number = number>(xs: ArrayLike<number>) => SortedArray<T> = Impl.ofUnsortedArray as any;
+    export const ofSingleton: <T extends number = number>(v: number) => SortedArray<T> = Impl.ofSingleton as any;
+    export const ofSortedArray: <T extends number = number>(xs: ArrayLike<number>) => SortedArray<T> = Impl.ofSortedArray as any;
     // create sorted array [min, max] (it DOES contain the max value)
     // create sorted array [min, max] (it DOES contain the max value)
-    export const ofRange: (min: number, max: number) => SortedArray = Impl.ofRange as any;
-    // create sorted array [min, max) (it DOES not contain the max value)
-    export const ofBounds: (min: number, max: number) => SortedArray = (min, max) => Impl.ofRange(min, max - 1) as any;
-    export const is: (v: any) => v is Interval = Impl.is as any;
-
-    export const has: (array: SortedArray, x: number) => boolean = Impl.has as any;
-    export const indexOf: (array: SortedArray, x: number) => number = Impl.indexOf as any;
-    export const indexOfInInterval: (array: SortedArray, x: number, bounds: Interval) => number = Impl.indexOfInInterval as any;
-
-    export const start: (array: SortedArray) => number = Impl.start as any;
-    export const end: (array: SortedArray) => number = Impl.end as any;
-    export const min: (array: SortedArray) => number = Impl.min as any;
-    export const max: (array: SortedArray) => number = Impl.max as any;
-    export const size: (array: SortedArray) => number = Impl.size as any;
-    export const hashCode: (array: SortedArray) => number = Impl.hashCode as any;
-
-    export const areEqual: (a: SortedArray, b: SortedArray) => boolean = Impl.areEqual as any;
-    export const areIntersecting: (a: SortedArray, b: SortedArray) => boolean = Impl.areIntersecting as any;
-    export const isSubset: (a: SortedArray, b: SortedArray) => boolean = Impl.isSubset as any;
-
-    export const union: (a: SortedArray, b: SortedArray) => SortedArray = Impl.union as any;
-    export const intersect: (a: SortedArray, b: SortedArray) => SortedArray = Impl.intersect as any;
-    export const subtract: (a: SortedArray, b: SortedArray) => SortedArray = Impl.subtract as any;
-
-    export const findPredecessorIndex: (array: SortedArray, x: number) => number = Impl.findPredecessorIndex as any;
-    export const findPredecessorIndexInInterval: (array: SortedArray, x: number, bounds: Interval) => number = Impl.findPredecessorIndexInInterval as any;
-    export const findRange: (array: SortedArray, min: number, max: number) => Interval = Impl.findRange as any;
-
-    export const deduplicate: (arrat: SortedArray) => SortedArray = Impl.deduplicate as any;
+    export const ofRange: <T extends number = number>(min: T, max: T) => SortedArray<T> = Impl.ofRange as any;
+    // create sorted array [min, max) (it does NOT contain the max value)
+    export const ofBounds: <T extends number = number>(min: T, max: T) => SortedArray<T> = (min, max) => Impl.ofRange(min, max - 1) as any;
+    export const is: <T extends number = number>(v: any) => v is SortedArray<T> = Impl.is as any;
+
+    export const has: <T extends number = number>(array: SortedArray<T>, x: T) => boolean = Impl.has as any;
+    export const indexOf: <T extends number = number>(array: SortedArray<T>, x: T) => number = Impl.indexOf as any;
+    export const indexOfInInterval: <T extends number = number>(array: SortedArray<T>, x: number, bounds: Interval) => number = Impl.indexOfInInterval as any;
+
+    // array[0]
+    export const start: <T extends number = number>(array: SortedArray<T>) => T = Impl.start as any;
+    // array[array.length - 1] + 1
+    export const end: <T extends number = number>(array: SortedArray<T>) => T = Impl.end as any;
+    export const min: <T extends number = number>(array: SortedArray<T>) => T = Impl.min as any;
+    export const max: <T extends number = number>(array: SortedArray<T>) => T = Impl.max as any;
+    export const size: <T extends number = number>(array: SortedArray<T>) => number = Impl.size as any;
+    export const hashCode: <T extends number = number>(array: SortedArray<T>) => number = Impl.hashCode as any;
+
+    export const areEqual: <T extends number = number>(a: SortedArray<T>, b: SortedArray<T>) => boolean = Impl.areEqual as any;
+    export const areIntersecting: <T extends number = number>(a: SortedArray<T>, b: SortedArray<T>) => boolean = Impl.areIntersecting as any;
+    export const isSubset: <T extends number = number>(a: SortedArray<T>, b: SortedArray<T>) => boolean = Impl.isSubset as any;
+
+    export const union: <T extends number = number>(a: SortedArray<T>, b: SortedArray<T>) => SortedArray<T> = Impl.union as any;
+    export const intersect: <T extends number = number>(a: SortedArray<T>, b: SortedArray<T>) => SortedArray<T> = Impl.intersect as any;
+    export const subtract: <T extends number = number>(a: SortedArray<T>, b: SortedArray<T>) => SortedArray<T> = Impl.subtract as any;
+
+    export const findPredecessorIndex: <T extends number = number>(array: SortedArray<T>, x: T) => number = Impl.findPredecessorIndex as any;
+    export const findPredecessorIndexInInterval: <T extends number = number>(array: SortedArray<T>, x: T, bounds: Interval) => number = Impl.findPredecessorIndexInInterval as any;
+    export const findRange: <T extends number = number>(array: SortedArray<T>, min: T, max: T) => Interval = Impl.findRange as any;
+    export const intersectionSize: <T extends number = number>(a: SortedArray<T>, b: SortedArray<T>) => number = Impl.intersectionSize as any;
+
+    export const deduplicate: <T extends number = number>(array: SortedArray<T>) => SortedArray<T> = Impl.deduplicate as any;
+    /** Returns indices of xs in the array. E.g. indicesOf([10, 11, 12], [10, 12]) ==> [0, 2] */
+    export const indicesOf: <T extends number = number, I extends number = number>(array: SortedArray<T>, xs: SortedArray<T>) => SortedArray<I> = Impl.indicesOf as any;
 }
 }
 
 
-interface SortedArray extends ArrayLike<number> { '@type': 'int-sorted-array' }
+interface SortedArray<T extends number = number> extends ArrayLike<T> { '@type': 'int-sorted-array' }
 
 
 export default SortedArray
 export default SortedArray

+ 86 - 0
src/mol-data/int/sorted-ranges.ts

@@ -0,0 +1,86 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+import { Segmentation, OrderedSet, SortedArray, Interval } from '../int';
+import _Iterator from '../iterator';
+
+/** Pairs of min and max indices of sorted, non-overlapping ranges */
+type SortedRanges<T extends number = number> = SortedArray<T>
+
+namespace SortedRanges {
+    export function ofSortedRanges<T extends number = number>(array: ArrayLike<T>) { return SortedArray.ofSortedArray<T>(array) }
+    export function start<T extends number = number>(ranges: SortedRanges<T>) { return ranges[0] }
+    export function end<T extends number = number>(ranges: SortedRanges<T>) { return ranges[ranges.length - 1] + 1 }
+    export function min<T extends number = number>(ranges: SortedRanges<T>) { return ranges[0] }
+    export function max<T extends number = number>(ranges: SortedRanges<T>) { return ranges[ranges.length - 1] }
+    export function size<T extends number = number>(ranges: SortedRanges<T>) {
+        let size = 0
+        for(let i = 0, il = ranges.length; i < il; i += 2) {
+            size += ranges[i + 1] - ranges[i] + 1
+        }
+        return size
+    }
+
+    export function transientSegments<T extends number = number, I extends number = number>(ranges: SortedRanges<T>, set: OrderedSet<T>) {
+        return new Iterator<T, I>(ranges, set)
+    }
+
+    export class Iterator<T extends number = number, I extends number = number> implements _Iterator<Segmentation.Segment<I>> {
+        private value: Segmentation.Segment<I> = { index: 0 as I, start: 0 as T, end: 0 as T }
+
+        private curIndex = 0
+        private maxIndex = 0
+        private interval: Interval<T>
+        private curMin: T = 0 as T
+
+        hasNext: boolean = false;
+
+        updateInterval() {
+            this.interval = Interval.ofRange(this.ranges[this.curIndex], this.ranges[this.curIndex + 1])
+        }
+
+        updateValue() {
+            this.value.index = this.curIndex / 2 as I
+            this.value.start = OrderedSet.findPredecessorIndex(this.set, this.ranges[this.curIndex])
+            this.value.end = OrderedSet.findPredecessorIndex(this.set, this.ranges[this.curIndex + 1]) + 1
+        }
+
+        move() {
+            if (this.hasNext) {
+                this.updateValue()
+                while (this.curIndex <= this.maxIndex) {
+                    this.curIndex += 2
+                    this.curMin = Interval.end(this.interval)
+                    this.updateInterval()
+                    if (Interval.min(this.interval) >= this.curMin && OrderedSet.areIntersecting(this.interval, this.set)) break
+                }
+                this.hasNext = this.curIndex <= this.maxIndex
+            }
+            return this.value;
+        }
+
+        getRangeIndex(value: number) {
+            const index = SortedArray.findPredecessorIndex(this.ranges, value)
+            return (index % 2 === 1) ? index - 1 : index
+        }
+
+        constructor(private ranges: SortedRanges<T>, private set: OrderedSet<T>) {
+            // TODO cleanup, refactor to make it clearer
+            const min = SortedArray.findPredecessorIndex(this.ranges, OrderedSet.min(set))
+            const max = SortedArray.findPredecessorIndex(this.ranges, OrderedSet.max(set))
+            if (ranges.length && min !== max) {
+                this.curIndex = this.getRangeIndex(OrderedSet.min(set))
+                this.maxIndex = Math.min(ranges.length - 2, this.getRangeIndex(OrderedSet.max(set)))
+                this.curMin = this.ranges[this.curIndex]
+                this.updateInterval()
+            }
+
+            this.hasNext = ranges.length > 0 && min !== max && this.curIndex <= this.maxIndex
+        }
+    }
+}
+
+export default SortedRanges

+ 1 - 0
src/mol-data/util.ts

@@ -5,6 +5,7 @@
  */
  */
 
 
 export * from './util/chunked-array'
 export * from './util/chunked-array'
+export * from './util/buckets'
 export * from './util/equivalence-classes'
 export * from './util/equivalence-classes'
 export * from './util/hash-functions'
 export * from './util/hash-functions'
 export * from './util/sort'
 export * from './util/sort'

+ 43 - 0
src/mol-data/util/_spec/buckets.spec.ts

@@ -0,0 +1,43 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author David Sehnal <david.sehnal@gmail.com>
+ */
+
+import { createRangeArray } from '../array';
+import { makeBuckets } from '../buckets';
+
+describe('buckets', () => {
+
+    function reorder(order: ArrayLike<number>, data: any[]): any[] {
+        const ret = [];
+        for (const i of (order as number[])) ret[ret.length] = data[i];
+        return ret;
+    }
+
+    it('full range', () => {
+        const xs = [1, 1, 2, 2, 3, 1];
+        const range = createRangeArray(0, xs.length - 1);
+        const bs = makeBuckets(range, i => xs[i]);
+
+        expect(reorder(range, xs)).toEqual([1, 1, 1, 2, 2, 3]);
+        expect(Array.from(bs)).toEqual([0, 3, 5, 6]);
+    });
+
+    it('sort', () => {
+        const xs = [3, 1, 2, 1, 2, 3];
+        const range = createRangeArray(0, xs.length - 1);
+        makeBuckets(range, i => xs[i], { sort: true });
+
+        expect(reorder(range, xs)).toEqual([1, 1, 2, 2, 3, 3]);
+    });
+
+    it('subrange', () => {
+        const xs = [2, 1, 2, 1, 2, 3, 1];
+        const range = createRangeArray(0, xs.length - 1);
+        const bs = makeBuckets(range, i => xs[i], { sort: false, start: 1, end: 5 });
+
+        expect(reorder(range, xs)).toEqual([2, 1, 1, 2, 2, 3, 1]);
+        expect(Array.from(bs)).toEqual([1, 3, 5]);
+    })
+})

+ 29 - 0
src/mol-data/util/_spec/combination.spec.ts

@@ -0,0 +1,29 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+import { combinations } from '../combination'
+
+describe('Combination', () => {
+    it('test123-2', () => {
+        const c = combinations([1, 2, 3], 2)
+        expect(c).toEqual([[1, 2], [1, 3], [2, 3]]);
+    });
+
+    it('test1234-2', () => {
+        const c = combinations([1, 2, 3, 4], 2)
+        expect(c).toEqual([[1, 2], [1, 3], [2, 3], [1, 4], [2, 4], [3, 4]]);
+    });
+
+    it('test1234-1', () => {
+        const c = combinations([1, 2, 3, 4], 1)
+        expect(c).toEqual([[1], [2], [3], [4]]);
+    });
+
+    it('test1234-4', () => {
+        const c = combinations([1, 2, 3, 4], 4)
+        expect(c).toEqual([[1, 2, 3, 4]]);
+    });
+});

+ 33 - 0
src/mol-data/util/_spec/interval-iterator.spec.ts

@@ -0,0 +1,33 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+import { Interval, OrderedSet, SortedArray } from '../../int';
+import { IntervalIterator } from '../interval-iterator';
+
+ describe('interval', () => {
+    function testIterator(name: string, interval: Interval, set: OrderedSet, expectedValues: { index: number[], start: number[], end: number[]}) {
+        it(`iterator, ${name}`, () => {
+            const intervalIt = new IntervalIterator(interval, set)
+            const { index, start, end } = expectedValues
+    
+            let i = 0
+            while (intervalIt.hasNext) {
+                const segment = intervalIt.move()
+                expect(segment.index).toBe(index[i])
+                expect(segment.start).toBe(start[i])
+                expect(segment.end).toBe(end[i])
+                ++i
+            }
+            expect(i).toBe(index.length)
+        })
+    }
+
+    testIterator('basic',
+        Interval.ofRange(0, 5),
+        SortedArray.ofSortedArray([1, 3, 7, 8]),
+        { index: [1, 3], start: [0, 1], end: [1, 2] }
+    )
+});

+ 24 - 0
src/mol-data/util/array.ts

@@ -22,4 +22,28 @@ export function iterableToArray<T>(it: IterableIterator<T>): T[] {
         ret[ret.length] = value;
         ret[ret.length] = value;
     }
     }
     return ret;
     return ret;
+}
+
+/** Fills the array so that array[0] = start and array[array.length - 1] = end */
+export function createRangeArray(start: number, end: number, ctor?: Helpers.ArrayCtor<number>) {
+    const len = end - start + 1;
+    const array = ctor ? new ctor(len) : new Int32Array(len);
+    for (let i = 0; i < len; i++) {
+        array[i] = i + start;
+    }
+    return array;
+}
+
+export function arrayPickIndices<T>(array: ArrayLike<T>, indices: ArrayLike<number>) {
+    const ret = new (arrayGetCtor(array))(indices.length);
+    for (let i = 0, _i = indices.length; i < _i; i++) {
+        ret[i] = array[indices[i]];
+    }
+    return ret;
+}
+
+export function arrayGetCtor<T>(data: ArrayLike<T>): Helpers.ArrayCtor<T> {
+    const ret = (data as any).constructor;
+    if (!ret) throw new Error('data does not define a constructor and it should');
+    return ret;
 }
 }

+ 104 - 0
src/mol-data/util/buckets.ts

@@ -0,0 +1,104 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author David Sehnal <david.sehnal@gmail.com>
+ */
+
+import { sort, arraySwap } from './sort';
+
+type Bucket = {
+    key: any,
+    count: number,
+    offset: number
+}
+
+function sortAsc(bs: Bucket[], i: number, j: number) { return bs[i].key < bs[j].key ? -1 : 1; }
+
+function _makeBuckets(indices: Helpers.ArrayLike<number>,
+    getKey: (i: number) => any, sortBuckets: boolean, start: number, end: number) {
+
+    const buckets = new Map<any, Bucket>();
+    const bucketList: Bucket[] = [];
+
+    let prevKey = getKey(indices[0]);
+    let isBucketed = true;
+    for (let i = start; i < end; i++) {
+        const key = getKey(indices[i]);
+        if (buckets.has(key)) {
+            buckets.get(key)!.count++;
+            if (prevKey !== key) isBucketed = false;
+        } else {
+            const bucket: Bucket = { key, count: 1, offset: i };
+            buckets.set(key, bucket);
+            bucketList[bucketList.length] = bucket;
+        }
+        prevKey = key;
+    }
+
+    const bucketOffsets = new Int32Array(bucketList.length + 1);
+    bucketOffsets[bucketList.length] = end;
+
+    let sorted = true;
+    if (sortBuckets) {
+        for (let i = 1, _i = bucketList.length; i < _i; i++) {
+            if (bucketList[i - 1].key > bucketList[i].key) {
+                sorted = false;
+                break;
+            }
+        }
+    }
+
+    if (isBucketed && sorted) {
+        for (let i = 0; i < bucketList.length; i++) bucketOffsets[i] = bucketList[i].offset;
+        return bucketOffsets;
+    }
+
+    if (sortBuckets && !sorted) {
+        sort(bucketList, 0, bucketList.length, sortAsc, arraySwap);
+    }
+
+    let offset = 0;
+    for (let i = 0; i < bucketList.length; i++) {
+        const b = bucketList[i];
+        b.offset = offset;
+        offset += b.count;
+    }
+
+    const reorderedIndices = new Int32Array(end - start);
+    for (let i = start; i < end; i++) {
+        const key = getKey(indices[i]);
+        const bucket = buckets.get(key)!;
+        reorderedIndices[bucket.offset++] = indices[i];
+    }
+
+    for (let i = 0, _i = reorderedIndices.length; i < _i; i++) {
+        indices[i + start] = reorderedIndices[i];
+    }
+
+    bucketOffsets[0] = start;
+    for (let i = 1; i < bucketList.length; i++) bucketOffsets[i] = bucketList[i - 1].offset + start;
+
+    return bucketOffsets;
+}
+
+export interface MakeBucketsOptions<K> {
+    // If specified, will be sorted
+    sort?: boolean,
+    // inclusive start indidex
+    start?: number,
+    // exclusive end index
+    end?: number
+}
+
+/**
+ * Reorders indices so that the same keys are next to each other, [start, end)
+ * Returns the offsets of buckets. So that [offsets[i], offsets[i + 1]) determines the range.
+ */
+export function makeBuckets<K extends string | number>(
+    indices: Helpers.ArrayLike<number>, getKey: (i: number) => K, options?: MakeBucketsOptions<K>): ArrayLike<number> {
+    const s = (options && options.start) || 0;
+    const e = (options && options.end) || indices.length;
+    if (e - s <= 0) throw new Error('Can only bucket non-empty collections.');
+
+    return _makeBuckets(indices, getKey, !!(options && options.sort), s, e);
+}

+ 65 - 0
src/mol-data/util/combination.ts

@@ -0,0 +1,65 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+// adpated from https://github.com/dankogai/js-combinatorics, MIT 2013-2016 Dan Kogai
+
+import Iterator from '../iterator'
+
+function P(m: number, n: number) {
+    let p = 1
+    while (n--) p *= m--
+    return p
+}
+
+function C(m: number, n: number) {
+    if (n > m) return 0
+    return P(m, n) / P(n, n)
+}
+
+function nextIndex(n: number) {
+    const smallest = n & -n
+    const ripple = n + smallest
+    const newSmallest = ripple & -ripple
+    const ones = ((newSmallest / smallest) >> 1) - 1
+    return ripple | ones
+};
+
+export class CombinationIterator<T> implements Iterator<T[]> {
+    private value: T[]
+    private index: number
+    private maxIndex: number
+
+    size: number
+    hasNext: boolean = false;
+
+    move() {
+        if (this.hasNext) {
+            let i = 0, j = 0, n = this.index
+            for (; n; n >>>= 1, i++) {
+                if (n & 1) this.value[j++] = this.array[i]
+            }
+            this.index = nextIndex(this.index)
+            this.hasNext = this.index < this.maxIndex
+        }
+        return this.value;
+    }
+
+    constructor(private array: T[], count: number) {
+        this.index = (1 << count) - 1
+        this.size = C(array.length, count)
+        this.maxIndex = 1 << array.length,
+
+        this.value = new Array(count)
+        this.hasNext = count > 0 && count <= array.length
+    }
+}
+
+export function combinations<T>(array: T[], count: number): T[][] {
+    const out: T[][] = []
+    const combinationIt = new CombinationIterator(array, count)
+    while (combinationIt.hasNext) out.push(combinationIt.move().slice())
+    return out
+}

+ 8 - 0
src/mol-data/util/hash-functions.ts

@@ -59,4 +59,12 @@ export function hashString(s: string) {
  */
  */
 export function cantorPairing(a: number, b: number) {
 export function cantorPairing(a: number, b: number) {
     return (a + b) * (a + b + 1) / 2 + b;
     return (a + b) * (a + b + 1) / 2 + b;
+}
+
+/**
+ * A unique number for each sorted pair of integers
+ * Biggest representable pair is (67108863, 67108863) (limit imposed by Number.MAX_SAFE_INTEGER)
+ */
+export function sortedCantorPairing(a: number, b: number) {
+    return a < b ? cantorPairing(a, b) : cantorPairing(b, a);
 }
 }

+ 45 - 0
src/mol-data/util/interval-iterator.ts

@@ -0,0 +1,45 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+import Iterator from '../iterator'
+import { OrderedSet, Interval, Segmentation } from '../int';
+
+/** Emits a segment of length one for each element in the interval that is also in the set */
+export class IntervalIterator<I extends number = number> implements Iterator<Segmentation.Segment<I>> {
+    private value: Segmentation.Segment<I> = { index: 0 as I, start: 0, end: 0 }
+
+    private curIndex = 0
+    private maxIndex = 0
+
+    hasNext: boolean = false;
+
+    updateValue() {
+        this.value.index = this.curIndex as I
+        this.value.start = OrderedSet.findPredecessorIndex(this.set, Interval.getAt(this.interval, this.curIndex))
+        this.value.end = this.value.start + 1
+    }
+
+    move() {
+        if (this.hasNext) {
+            this.updateValue()
+            while (this.curIndex <= this.maxIndex) {
+                ++this.curIndex
+                if (OrderedSet.has(this.set, this.curIndex)) break
+            }
+            this.hasNext = this.curIndex <= this.maxIndex
+        }
+        return this.value;
+    }
+
+    constructor(private interval: Interval<I>, private set: OrderedSet<I>) {
+        if (Interval.size(interval)) {
+            this.curIndex = Interval.findPredecessorIndex(interval, OrderedSet.min(set))
+            this.maxIndex = Interval.findPredecessorIndex(interval, OrderedSet.max(set))
+        }
+
+        this.hasNext = OrderedSet.areIntersecting(this.interval, this.set)
+    }
+}

+ 45 - 77
src/mol-geo/primitive/box.ts

@@ -7,101 +7,69 @@
 // adapted from three.js, MIT License Copyright 2010-2018 three.js authors
 // adapted from three.js, MIT License Copyright 2010-2018 three.js authors
 
 
 import { Vec3 } from 'mol-math/linear-algebra'
 import { Vec3 } from 'mol-math/linear-algebra'
+import { Primitive } from './primitive';
 
 
 export const DefaultBoxProps = {
 export const DefaultBoxProps = {
     width: 1,
     width: 1,
     height: 1,
     height: 1,
-    depth: 1,
-    widthSegments: 1,
-    heightSegments: 1,
-    depthSegments: 1
+    depth: 1
 }
 }
 export type BoxProps = Partial<typeof DefaultBoxProps>
 export type BoxProps = Partial<typeof DefaultBoxProps>
 
 
-export default function Box(props?: BoxProps) {
-    const { width, height, depth, widthSegments, heightSegments, depthSegments } = { ...DefaultBoxProps, ...props }
+const tmpVector = Vec3.zero();
+
+export function Box(props?: BoxProps): Primitive {
+    const { width, height, depth } = { ...DefaultBoxProps, ...props }
 
 
     // buffers
     // buffers
-    const indices: number[] = [];
-    const vertices: number[] = [];
-    const normals: number[] = [];
+    const vertices = new Float32Array(72);
+    const normals = new Float32Array(72);
+    const indices = new Uint32Array(36);
 
 
     // helper variables
     // helper variables
-    let numberOfVertices = 0;
+    let vertexCount = 0;
 
 
     // build each side of the box geometry
     // build each side of the box geometry
-    buildPlane(2, 1, 0, -1, -1, depth, height, width, depthSegments, heightSegments); // px
-    buildPlane(2, 1, 0, 1, -1, depth, height, -width, depthSegments, heightSegments); // nx
-    buildPlane(0, 2, 1, 1, 1, width, depth, height, widthSegments, depthSegments); // py
-    buildPlane(0, 2, 1, 1, -1, width, depth, -height, widthSegments, depthSegments); // ny
-    buildPlane(0, 1, 2, 1, -1, width, height, depth, widthSegments, heightSegments); // pz
-    buildPlane(0, 1, 2, -1, -1, width, height, -depth, widthSegments, heightSegments); // nz
-
-    return {
-        vertices: new Float32Array(vertices),
-        normals: new Float32Array(normals),
-        indices: new Uint32Array(indices)
-    }
-
-    function buildPlane(u: number, v: number, w: number, udir: number, vdir: number, width: number, height: number, depth: number, gridX: number, gridY: number) {
-
-        const segmentWidth = width / gridX;
-        const segmentHeight = height / gridY;
+    buildPlane(2, 1, 0, -1, -1, depth, height, width); // px
+    buildPlane(2, 1, 0, 1, -1, depth, height, -width); // nx
+    buildPlane(0, 2, 1, 1, 1, width, depth, height); // py
+    buildPlane(0, 2, 1, 1, -1, width, depth, -height); // ny
+    buildPlane(0, 1, 2, 1, -1, width, height, depth); // pz
+    buildPlane(0, 1, 2, -1, -1, width, height, -depth); // nz
 
 
-        const widthHalf = width / 2;
-        const heightHalf = height / 2;
-        const depthHalf = depth / 2;
-
-        const gridX1 = gridX + 1;
-        const gridY1 = gridY + 1;
-
-        let vertexCounter = 0;
-
-        const vector = Vec3.zero();
+    return { vertices, normals, indices }
 
 
+    function buildPlane(u: number, v: number, w: number, udir: number, vdir: number, width: number, height: number, depth: number) {
         // generate vertices and normals
         // generate vertices and normals
-        for (let iy = 0; iy < gridY1; ++iy) {
-            const y = iy * segmentHeight - heightHalf;
-            for (let ix = 0; ix < gridX1; ++ix) {
-                const x = ix * segmentWidth - widthHalf;
-
-                // set values to correct vector component
-                vector[ u ] = x * udir;
-                vector[ v ] = y * vdir;
-                vector[ w ] = depthHalf;
-
-                // now apply vector to vertex buffer
-                vertices.push(...vector);
-
-                // set values to correct vector component
-                vector[ u ] = 0;
-                vector[ v ] = 0;
-                vector[ w ] = depth > 0 ? 1 : -1;
-
-                // now apply vector to normal buffer
-                normals.push(...vector);
-
-                vertexCounter += 1;
-            }
-        }
-
-        // indices
-        // 1. you need three indices to draw a single face
-        // 2. a single segment consists of two faces
-        // 3. so we need to generate six (2*3) indices per segment
-        for (let iy = 0; iy < gridY; ++iy) {
-            for (let ix = 0; ix < gridX; ++ix) {
-                const a = numberOfVertices + ix + gridX1 * iy;
-                const b = numberOfVertices + ix + gridX1 * (iy + 1);
-                const c = numberOfVertices + (ix + 1) + gridX1 * (iy + 1);
-                const d = numberOfVertices + (ix + 1) + gridX1 * iy;
-
-                // faces
-                indices.push(a, b, d);
-                indices.push(b, c, d);
+        for (let iy = 0; iy < 2; ++iy) {
+            const y = iy * height - height / 2;
+            for (let ix = 0; ix < 2; ++ix) {
+                const x = ix * width - width / 2;
+
+                // set values to correct vector component and add to vertex buffer
+                tmpVector[u] = x * udir;
+                tmpVector[v] = y * vdir;
+                tmpVector[w] = depth / 2;
+                Vec3.toArray(tmpVector, vertices, vertexCount * 3);
+
+                // set values to correct vector component and add to normal buffer
+                tmpVector[u] = 0;
+                tmpVector[v] = 0;
+                tmpVector[w] = depth > 0 ? 1 : -1;
+                Vec3.toArray(tmpVector, normals, vertexCount * 3);
+
+                ++vertexCount;
             }
             }
         }
         }
 
 
-        numberOfVertices += vertexCounter;
+        // faces
+        const vc = vertexCount - 4
+        const iidx = (vc / 2) * 3
+        indices[iidx] = vc
+        indices[iidx + 1] = vc + 2
+        indices[iidx + 2] = vc + 1
+        indices[iidx + 3] = vc + 2
+        indices[iidx + 4] = vc + 3
+        indices[iidx + 5] = vc + 1
     }
     }
 }
 }

+ 7 - 7
src/mol-geo/primitive/cylinder.ts

@@ -7,6 +7,7 @@
 // adapted from three.js, MIT License Copyright 2010-2018 three.js authors
 // adapted from three.js, MIT License Copyright 2010-2018 three.js authors
 
 
 import { Vec3 } from 'mol-math/linear-algebra'
 import { Vec3 } from 'mol-math/linear-algebra'
+import { Primitive } from './primitive';
 
 
 export const DefaultCylinderProps = {
 export const DefaultCylinderProps = {
     radiusTop: 1,
     radiusTop: 1,
@@ -14,14 +15,15 @@ export const DefaultCylinderProps = {
     height: 1,
     height: 1,
     radialSegments: 8,
     radialSegments: 8,
     heightSegments: 1,
     heightSegments: 1,
-    openEnded: false,
+    topCap: false,
+    bottomCap: false,
     thetaStart: 0.0,
     thetaStart: 0.0,
     thetaLength: Math.PI * 2
     thetaLength: Math.PI * 2
 }
 }
 export type CylinderProps = Partial<typeof DefaultCylinderProps>
 export type CylinderProps = Partial<typeof DefaultCylinderProps>
 
 
-export default function Cylinder(props?: CylinderProps) {
-    const { radiusTop, radiusBottom, height, radialSegments, heightSegments, openEnded, thetaStart, thetaLength } = { ...DefaultCylinderProps, ...props };
+export function Cylinder(props?: CylinderProps): Primitive {
+    const { radiusTop, radiusBottom, height, radialSegments, heightSegments, topCap, bottomCap, thetaStart, thetaLength } = { ...DefaultCylinderProps, ...props };
 
 
     // buffers
     // buffers
     const indices: number[] = [];
     const indices: number[] = [];
@@ -36,10 +38,8 @@ export default function Cylinder(props?: CylinderProps) {
     // generate geometry
     // generate geometry
     generateTorso();
     generateTorso();
 
 
-    if (openEnded === false) {
-        if (radiusTop > 0) generateCap(true);
-        if (radiusBottom > 0) generateCap(false);
-    }
+    if (topCap && radiusTop > 0) generateCap(true);
+    if (bottomCap && radiusBottom > 0) generateCap(false);
 
 
     return {
     return {
         vertices: new Float32Array(vertices),
         vertices: new Float32Array(vertices),

+ 12 - 24
src/mol-geo/primitive/icosahedron.ts

@@ -4,35 +4,23 @@
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
  */
 
 
-// adapted from three.js, MIT License Copyright 2010-2018 three.js authors
-
-import Polyhedron from './polyhedron'
+import { createPrimitive, Primitive } from './primitive';
 
 
 const t = ( 1 + Math.sqrt( 5 ) ) / 2;
 const t = ( 1 + Math.sqrt( 5 ) ) / 2;
 
 
-const vertices = [
-    - 1, t, 0, 	1, t, 0, 	- 1, - t, 0, 	1, - t, 0,
-     0, - 1, t, 	0, 1, t,	0, - 1, - t, 	0, 1, - t,
-     t, 0, - 1, 	t, 0, 1, 	- t, 0, - 1, 	- t, 0, 1
+const icosahedronVertices: ReadonlyArray<number> = [
+    -1, t, 0,   1, t, 0,  -1, -t, 0,   1, -t, 0,
+     0, -1, t,  0, 1, t,   0, -1, -t,  0, 1, -t,
+     t, 0, -1,  t, 0, 1,  -t, 0, -1,  -t, 0, 1
 ];
 ];
 
 
-const indices = [
-     0, 11, 5, 	0, 5, 1, 	0, 1, 7, 	0, 7, 10, 	0, 10, 11,
-     1, 5, 9, 	5, 11, 4,	11, 10, 2,	10, 7, 6,	7, 1, 8,
-     3, 9, 4, 	3, 4, 2,	3, 2, 6,	3, 6, 8,	3, 8, 9,
-     4, 9, 5, 	2, 4, 11,	6, 2, 10,	8, 6, 7,	9, 8, 1
+const icosahedronIndices: ReadonlyArray<number> = [
+    0, 11, 5,  0, 5, 1,    0, 1, 7,    0, 7, 10,  0, 10, 11,
+    1, 5, 9,   5, 11, 4,  11, 10, 2,  10, 7, 6,   7, 1, 8,
+    3, 9, 4,   3, 4, 2,    3, 2, 6,    3, 6, 8,   3, 8, 9,
+    4, 9, 5,   2, 4, 11,   6, 2, 10,   8, 6, 7,   9, 8, 1
 ];
 ];
 
 
-export function icosahedronVertexCount(detail: number) {
-    return 10 * Math.pow(Math.pow(2, detail), 2) + 2
-}
-
-export const DefaultIcosahedronProps = {
-    radius: 1,
-    detail: 0
-}
-export type IcosahedronProps = Partial<typeof DefaultIcosahedronProps>
+const icosahedron = createPrimitive(icosahedronVertices, icosahedronIndices)
 
 
-export default function Icosahedron(props?: IcosahedronProps) {
-    return Polyhedron(vertices, indices, { ...DefaultIcosahedronProps, ...props })
-}
+export function Icosahedron(): Primitive { return icosahedron }

+ 22 - 0
src/mol-geo/primitive/octahedron.ts

@@ -0,0 +1,22 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+import { createPrimitive, Primitive } from './primitive';
+
+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
+];
+
+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
+];
+
+const octahedron = createPrimitive(octahedronVertices, octahedronIndices)
+
+export function Octahedron(): Primitive { return octahedron }

+ 36 - 0
src/mol-geo/primitive/plane.ts

@@ -0,0 +1,36 @@
+import { Primitive } from './primitive';
+
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+export const DefaultPlaneProps = {
+    width: 1,
+    height: 1
+}
+export type PlaneProps = Partial<typeof DefaultPlaneProps>
+
+export function Plane(props?: PlaneProps): Primitive {
+    const { width, height } = { ...DefaultPlaneProps, ...props }
+
+    return {
+        vertices: new Float32Array([
+            -width / 2, height / 2, 0,
+            width / 2, height / 2, 0,
+            -width / 2, -height / 2, 0,
+            width / 2, -height / 2, 0
+        ]),
+        normals: new Float32Array([
+            0, 0, 1,
+            0, 0, 1,
+            0, 0, 1,
+            0, 0, 1
+        ]),
+        indices: new Uint32Array([
+            0, 2, 1,
+            1, 2, 3
+        ])
+    }
+}

+ 23 - 0
src/mol-geo/primitive/polygon.ts

@@ -0,0 +1,23 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+/**
+ * Create 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 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
+    }
+    return points
+}

+ 2 - 16
src/mol-geo/primitive/polyhedron.ts

@@ -8,6 +8,7 @@
 
 
 import { Vec3 } from 'mol-math/linear-algebra'
 import { Vec3 } from 'mol-math/linear-algebra'
 import { computeIndexedVertexNormals, appplyRadius } from '../util'
 import { computeIndexedVertexNormals, appplyRadius } from '../util'
+import { Primitive } from './primitive';
 
 
 export const DefaultPolyhedronProps = {
 export const DefaultPolyhedronProps = {
     radius: 1,
     radius: 1,
@@ -15,7 +16,7 @@ export const DefaultPolyhedronProps = {
 }
 }
 export type PolyhedronProps = Partial<typeof DefaultPolyhedronProps>
 export type PolyhedronProps = Partial<typeof DefaultPolyhedronProps>
 
 
-export default function Polyhedron(_vertices: Helpers.NumberArray, _indices: Helpers.NumberArray, props?: PolyhedronProps) {
+export function Polyhedron(_vertices: ArrayLike<number>, _indices: ArrayLike<number>, props?: PolyhedronProps): Primitive {
     const { radius, detail } = { ...DefaultPolyhedronProps, ...props }
     const { radius, detail } = { ...DefaultPolyhedronProps, ...props }
     const builder = createBuilder()
     const builder = createBuilder()
     const { vertices, indices } = builder
     const { vertices, indices } = builder
@@ -28,7 +29,6 @@ export default function Polyhedron(_vertices: Helpers.NumberArray, _indices: Hel
 
 
     const normals = new Float32Array(vertices.length);
     const normals = new Float32Array(vertices.length);
     computeIndexedVertexNormals(vertices, indices, normals)
     computeIndexedVertexNormals(vertices, indices, normals)
-    // this.normalizeNormals(); // smooth normals
 
 
     return {
     return {
         vertices: new Float32Array(vertices),
         vertices: new Float32Array(vertices),
@@ -85,20 +85,6 @@ export default function Polyhedron(_vertices: Helpers.NumberArray, _indices: Hel
             }
             }
         }
         }
 
 
-        // // construct all of the faces
-        // for (let i = 0; i < cols; ++i) {
-        //     for (let j = 0; j < 2 * (cols - i) - 1; ++j) {
-        //         const k = Math.floor(j / 2)
-        //         if (j % 2 === 0) {
-        //             vertices.push(...v[i][k + 1], ...v[i + 1][k], ...v[i][k])
-        //         } else {
-        //             vertices.push(...v[i][k + 1], ...v[i + 1][k + 1], ...v[i + 1][k])
-        //         }
-        //         const l = vertices.length / 3
-        //         indices.push(l - 3, l - 2, l - 1)
-        //     }
-        // }
-
         // construct all of the faces
         // construct all of the faces
         for (let i = 0; i < cols; ++i) {
         for (let i = 0; i < cols; ++i) {
             for (let j = 0; j < 2 * (cols - i) - 1; ++j) {
             for (let j = 0; j < 2 * (cols - i) - 1; ++j) {

+ 59 - 0
src/mol-geo/primitive/primitive.ts

@@ -0,0 +1,59 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+import { Vec3 } from 'mol-math/linear-algebra';
+
+export interface Primitive {
+    vertices: ArrayLike<number>
+    normals: ArrayLike<number>
+    indices: ArrayLike<number>
+}
+
+const a = Vec3.zero(), b = Vec3.zero(), c = Vec3.zero()
+
+/** Create primitive with face normals from vertices and indices */
+export function createPrimitive(vertices: ArrayLike<number>, indices: ArrayLike<number>): Primitive {
+    const count = indices.length
+    const builder = PrimitiveBuilder(count / 3)
+
+    for (let i = 0; i < count; i += 3) {
+        Vec3.fromArray(a, vertices, indices[i] * 3)
+        Vec3.fromArray(b, vertices, indices[i + 1] * 3)
+        Vec3.fromArray(c, vertices, indices[i + 2] * 3)
+        builder.add(a, b, c)
+    }
+    return builder.getPrimitive()
+}
+
+export interface PrimitiveBuilder {
+    add(a: Vec3, b: Vec3, c: Vec3): void
+    getPrimitive(): Primitive
+}
+
+const vn = Vec3.zero()
+
+/** Builder to create primitive with face normals */
+export function PrimitiveBuilder(triangleCount: number): PrimitiveBuilder {
+    const vertices = new Float32Array(triangleCount * 3 * 3)
+    const normals = new Float32Array(triangleCount * 3 * 3)
+    const indices = new Uint32Array(triangleCount * 3)
+    let offset = 0
+
+    return {
+        add: (a: Vec3, b: Vec3, c: Vec3) => {
+            Vec3.toArray(a, vertices, offset)
+            Vec3.toArray(b, vertices, offset + 3)
+            Vec3.toArray(c, vertices, offset + 6)
+            Vec3.triangleNormal(vn, a, b, c)
+            for (let j = 0; j < 3; ++j) {
+                Vec3.toArray(vn, normals, offset + 3 * j)
+                indices[offset / 3 + j] = offset / 3 + j
+            }
+            offset += 9
+        },
+        getPrimitive: () => ({ vertices, normals, indices })
+    }
+}

+ 100 - 0
src/mol-geo/primitive/prism.ts

@@ -0,0 +1,100 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+import { Vec3 } from 'mol-math/linear-algebra'
+import { Primitive, PrimitiveBuilder } from './primitive';
+import { polygon } from './polygon'
+
+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
+ */
+export function Prism(points: ArrayLike<number>): Primitive {
+    const sideCount = points.length / 2
+    const baseCount = sideCount === 3 ? 1 : sideCount === 4 ? 2 : sideCount
+    const count = 2 * baseCount + 2 * sideCount
+    const builder = PrimitiveBuilder(count)
+
+    // 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)
+        builder.add(a, b, c)
+        builder.add(c, d, a)
+    }
+
+    // create bases
+    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)
+        builder.add(a, b, 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)
+        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)
+        builder.add(a, b, c)
+        builder.add(c, d, 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(d, points[6], points[7], 0.5)
+        builder.add(a, b, c)
+        builder.add(c, d, a)
+    } 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)
+            builder.add(a, b, on)
+            Vec3.set(a, points[i * 2], points[i * 2 + 1], 0.5)
+            Vec3.set(b, points[ni * 2], points[ni * 2 + 1], 0.5)
+            builder.add(op, b, a)
+        }
+    }
+
+    return builder.getPrimitive()
+}
+
+let wedge: Primitive
+export function Wedge() {
+    if (!wedge) wedge = Prism(polygon(3, false))
+    return wedge
+}
+
+let box: Primitive
+export function Box() {
+    if (!box) box = Prism(polygon(4, true))
+    return box
+}
+
+let diamond: Primitive
+export function DiamondPrism() {
+    if (!diamond) diamond = Prism(polygon(4, false))
+    return diamond
+}
+
+let pentagonalPrism: Primitive
+export function PentagonalPrism() {
+    if (!pentagonalPrism) pentagonalPrism = Prism(polygon(5, false))
+    return pentagonalPrism
+}
+
+let hexagonalPrism: Primitive
+export function HexagonalPrism() {
+    if (!hexagonalPrism) hexagonalPrism = Prism(polygon(6, true))
+    return hexagonalPrism
+}

+ 60 - 0
src/mol-geo/primitive/pyramid.ts

@@ -0,0 +1,60 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+import { Vec3 } from 'mol-math/linear-algebra'
+import { Primitive, PrimitiveBuilder } from './primitive';
+import { polygon } from './polygon'
+
+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 pyramide with a poligonal base
+ */
+export function Pyramide(points: ArrayLike<number>): Primitive {
+    const sideCount = points.length / 2
+    const baseCount = sideCount === 3 ? 1 : sideCount === 4 ? 2 : sideCount
+    const count = 2 * baseCount + 2 * sideCount
+    const builder = PrimitiveBuilder(count)
+
+    // 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)
+        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)
+        builder.add(a, b, c)
+    } 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)
+        builder.add(a, b, c)
+        builder.add(c, d, a)
+    } 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)
+            builder.add(a, b, on)
+        }
+    }
+
+    return builder.getPrimitive()
+}
+
+let octagonalPyramide: Primitive
+export function OctagonalPyramide() {
+    if (!octagonalPyramide) octagonalPyramide = Pyramide(polygon(8, true))
+    return octagonalPyramide
+}

+ 208 - 0
src/mol-geo/primitive/sheet.ts

@@ -0,0 +1,208 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ * @author David Sehnal <david.sehnal@gmail.com>
+ */
+
+import { Vec3 } from 'mol-math/linear-algebra';
+import { ChunkedArray } from 'mol-data/util';
+import { MeshBuilderState } from '../shape/mesh-builder';
+
+const tA = Vec3.zero()
+const tB = Vec3.zero()
+const tV = Vec3.zero()
+
+const horizontalVector = Vec3.zero()
+const verticalVector = Vec3.zero()
+const normalOffset = Vec3.zero()
+const positionVector = Vec3.zero()
+const normalVector = Vec3.zero()
+const torsionVector = Vec3.zero()
+
+const arrowVerticalVector = Vec3.zero()
+const p1 = Vec3.zero()
+const p2 = Vec3.zero()
+const p3 = Vec3.zero()
+const p4 = Vec3.zero()
+const p5 = Vec3.zero()
+const p6 = Vec3.zero()
+const p7 = Vec3.zero()
+const p8 = Vec3.zero()
+
+export function addSheet(controlPoints: ArrayLike<number>, normalVectors: ArrayLike<number>, binormalVectors: ArrayLike<number>, linearSegments: number, width: number, height: number, arrowHeight: number, startCap: boolean, endCap: boolean, state: MeshBuilderState) {
+    const { vertices, normals, indices } = state
+
+    let vertexCount = vertices.elementCount
+    let offsetLength = 0
+
+    if (arrowHeight > 0) {
+        Vec3.fromArray(tA, controlPoints, 0)
+        Vec3.fromArray(tB, controlPoints, linearSegments * 3)
+        offsetLength = arrowHeight / Vec3.magnitude(Vec3.sub(tV, tB, tA))
+    }
+
+    for (let i = 0; i <= linearSegments; ++i) {
+        const actualHeight = arrowHeight === 0 ? height : arrowHeight * (1 - i / linearSegments);
+        const i3 = i * 3
+
+        Vec3.fromArray(verticalVector, normalVectors, i3)
+        Vec3.scale(verticalVector, verticalVector, actualHeight);
+
+        Vec3.fromArray(horizontalVector, binormalVectors, i3)
+        Vec3.scale(horizontalVector, horizontalVector, width);
+
+        if (arrowHeight > 0) {
+            Vec3.fromArray(tA, normalVectors, i3)
+            Vec3.fromArray(tB, binormalVectors, i3)
+            Vec3.scale(normalOffset, Vec3.cross(normalOffset, tA, tB), offsetLength)
+        }
+
+        Vec3.fromArray(positionVector, controlPoints, i3)
+        Vec3.fromArray(normalVector, normalVectors, i3)
+        Vec3.fromArray(torsionVector, binormalVectors, i3)
+
+        Vec3.add(tA, Vec3.add(tA, positionVector, horizontalVector), verticalVector)
+        Vec3.copy(tB, normalVector)
+        ChunkedArray.add3(vertices, tA[0], tA[1], tA[2])
+        ChunkedArray.add3(normals, tB[0], tB[1], tB[2])
+
+        Vec3.add(tA, Vec3.sub(tA, positionVector, horizontalVector), verticalVector)
+        ChunkedArray.add3(vertices, tA[0], tA[1], tA[2])
+        ChunkedArray.add3(normals, tB[0], tB[1], tB[2])
+
+        // Vec3.add(tA, Vec3.sub(tA, positionVector, horizontalVector), verticalVector) // reuse tA
+        Vec3.add(tB, Vec3.negate(tB, torsionVector), normalOffset)
+        ChunkedArray.add3(vertices, tA[0], tA[1], tA[2])
+        ChunkedArray.add3(normals, tB[0], tB[1], tB[2])
+
+        Vec3.sub(tA, Vec3.sub(tA, positionVector, horizontalVector), verticalVector)
+        ChunkedArray.add3(vertices, tA[0], tA[1], tA[2])
+        ChunkedArray.add3(normals, tB[0], tB[1], tB[2])
+
+        // Vec3.sub(tA, Vec3.sub(tA, positionVector, horizontalVector), verticalVector) // reuse tA
+        Vec3.negate(tB, normalVector)
+        ChunkedArray.add3(vertices, tA[0], tA[1], tA[2])
+        ChunkedArray.add3(normals, tB[0], tB[1], tB[2])
+
+        Vec3.sub(tA, Vec3.add(tA, positionVector, horizontalVector), verticalVector)
+        ChunkedArray.add3(vertices, tA[0], tA[1], tA[2])
+        ChunkedArray.add3(normals, tB[0], tB[1], tB[2])
+
+        // Vec3.sub(tA, Vec3.add(tA, positionVector, horizontalVector), verticalVector) // reuse tA
+        Vec3.add(tB, torsionVector, normalOffset)
+        ChunkedArray.add3(vertices, tA[0], tA[1], tA[2])
+        ChunkedArray.add3(normals, tB[0], tB[1], tB[2])
+
+        Vec3.add(tA, Vec3.add(tA, positionVector, horizontalVector), verticalVector)
+        ChunkedArray.add3(vertices, tA[0], tA[1], tA[2])
+        ChunkedArray.add3(normals, tB[0], tB[1], tB[2])
+    }
+
+    for (let i = 0; i < linearSegments; ++i) {
+        for (let j = 0; j < 4; j++) {
+            ChunkedArray.add3(
+                indices,
+                vertexCount + i * 8 + 2 * j,
+                vertexCount + (i + 1) * 8 + 2 * j + 1,
+                vertexCount + i * 8 + 2 * j + 1
+            );
+            ChunkedArray.add3(
+                indices,
+                vertexCount + i * 8 + 2 * j,
+                vertexCount + (i + 1) * 8 + 2 * j,
+                vertexCount + (i + 1) * 8 + 2 * j + 1
+            );
+        }
+    }
+
+    if (startCap) {
+        const offset = 0
+        vertexCount = vertices.elementCount
+
+        Vec3.fromArray(verticalVector, normalVectors, offset)
+        Vec3.scale(verticalVector, verticalVector, height);
+
+        Vec3.fromArray(horizontalVector, binormalVectors, offset)
+        Vec3.scale(horizontalVector, horizontalVector, width);
+
+        Vec3.fromArray(positionVector, controlPoints, offset)
+
+        Vec3.add(p1, Vec3.add(p1, positionVector, horizontalVector), verticalVector)
+        Vec3.sub(p2, Vec3.add(p2, positionVector, horizontalVector), verticalVector)
+        Vec3.sub(p3, Vec3.sub(p3, positionVector, horizontalVector), verticalVector)
+        Vec3.add(p4, Vec3.sub(p4, positionVector, horizontalVector), verticalVector)
+
+        ChunkedArray.add3(vertices, p1[0], p1[1], p1[2])
+        ChunkedArray.add3(vertices, p2[0], p2[1], p2[2])
+        ChunkedArray.add3(vertices, p3[0], p3[1], p3[2])
+        ChunkedArray.add3(vertices, p4[0], p4[1], p4[2])
+
+        Vec3.cross(normalVector, horizontalVector, verticalVector)
+
+        if (arrowHeight === 0) {
+            for (let i = 0; i < 4; ++i) {
+                ChunkedArray.add3(normals, normalVector[0], normalVector[1], normalVector[2])
+            }
+
+            ChunkedArray.add3(indices, vertexCount + 2, vertexCount + 1, vertexCount);
+            ChunkedArray.add3(indices, vertexCount, vertexCount + 3, vertexCount + 2);
+        } else {
+            Vec3.fromArray(arrowVerticalVector, normalVectors, offset)
+            Vec3.scale(arrowVerticalVector, verticalVector, arrowHeight);
+
+            Vec3.add(p5, Vec3.add(p5, positionVector, horizontalVector), arrowVerticalVector)
+            Vec3.sub(p6, Vec3.add(p6, positionVector, horizontalVector), arrowVerticalVector)
+            Vec3.sub(p7, Vec3.sub(p7, positionVector, horizontalVector), arrowVerticalVector)
+            Vec3.add(p8, Vec3.sub(p8, positionVector, horizontalVector), arrowVerticalVector)
+
+            ChunkedArray.add3(vertices, p5[0], p5[1], p5[2])
+            ChunkedArray.add3(vertices, p6[0], p6[1], p6[2])
+            ChunkedArray.add3(vertices, p7[0], p7[1], p7[2])
+            ChunkedArray.add3(vertices, p8[0], p8[1], p8[2])
+
+            for (let i = 0; i < 8; ++i) {
+                ChunkedArray.add3(normals, normalVector[0], normalVector[1], normalVector[2])
+            }
+
+            ChunkedArray.add3(indices, vertexCount + 7, vertexCount, vertexCount + 4);
+            ChunkedArray.add3(indices, vertexCount + 7, vertexCount + 3, vertexCount);
+            ChunkedArray.add3(indices, vertexCount + 5, vertexCount + 1, vertexCount + 6);
+            ChunkedArray.add3(indices, vertexCount + 1, vertexCount + 2, vertexCount + 6);
+        }
+    }
+
+    if (endCap && arrowHeight === 0) {
+        const offset = linearSegments * 3
+        vertexCount = vertices.elementCount
+
+        Vec3.fromArray(verticalVector, normalVectors, offset)
+        Vec3.scale(verticalVector, verticalVector, height);
+
+        Vec3.fromArray(horizontalVector, binormalVectors, offset)
+        Vec3.scale(horizontalVector, horizontalVector, width);
+
+        Vec3.fromArray(positionVector, controlPoints, offset)
+
+        Vec3.add(p1, Vec3.add(p1, positionVector, horizontalVector), verticalVector)
+        Vec3.sub(p2, Vec3.add(p2, positionVector, horizontalVector), verticalVector)
+        Vec3.sub(p3, Vec3.sub(p3, positionVector, horizontalVector), verticalVector)
+        Vec3.add(p4, Vec3.sub(p4, positionVector, horizontalVector), verticalVector)
+
+        ChunkedArray.add3(vertices, p1[0], p1[1], p1[2])
+        ChunkedArray.add3(vertices, p2[0], p2[1], p2[2])
+        ChunkedArray.add3(vertices, p3[0], p3[1], p3[2])
+        ChunkedArray.add3(vertices, p4[0], p4[1], p4[2])
+
+        Vec3.cross(normalVector, horizontalVector, verticalVector)
+
+        for (let i = 0; i < 4; ++i) {
+            ChunkedArray.add3(normals, normalVector[0], normalVector[1], normalVector[2])
+        }
+
+        ChunkedArray.add3(indices, vertexCount + 2, vertexCount + 1, vertexCount);
+        ChunkedArray.add3(indices, vertexCount, vertexCount + 3, vertexCount + 2);
+    }
+
+    return (linearSegments + 1) * 8 + (startCap ? (arrowHeight === 0 ? 4 : 8) : 0) + (endCap && arrowHeight === 0 ? 4 : 0)
+}

+ 27 - 0
src/mol-geo/primitive/sphere.ts

@@ -0,0 +1,27 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+import { Polyhedron } from './polyhedron'
+import { Icosahedron } from  './icosahedron'
+import { Primitive } from './primitive';
+
+const { vertices, indices } = Icosahedron()
+
+/** Calculate vertex count for subdived icosahedron */
+export function sphereVertexCount(detail: number) {
+    return 10 * Math.pow(Math.pow(2, detail), 2) + 2
+}
+
+export const DefaultSphereProps = {
+    radius: 1,
+    detail: 0
+}
+export type SphereProps = Partial<typeof DefaultSphereProps>
+
+/** Create sphere by subdividing an icosahedron */
+export function Sphere(props?: SphereProps): Primitive {
+    return Polyhedron(vertices, indices, { ...DefaultSphereProps, ...props })
+}

+ 55 - 0
src/mol-geo/primitive/star.ts

@@ -0,0 +1,55 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+import { Vec3 } from 'mol-math/linear-algebra'
+import { Primitive, PrimitiveBuilder } from './primitive';
+
+export const DefaultStarProps = {
+    pointCount: 5,
+    outerRadius: 1,
+    innerRadius: 0.5,
+    thickness: 0.3
+}
+export type StarProps = Partial<typeof DefaultStarProps>
+
+const op = Vec3.zero(), on = Vec3.zero()
+const a = Vec3.zero(), b = Vec3.zero(), c = Vec3.zero()
+
+export function Star(props?: StarProps): Primitive {
+    const { outerRadius, innerRadius, thickness, pointCount } = { ...DefaultStarProps, ...props }
+
+    const triangleCount = pointCount * 2 * 2
+    const builder = PrimitiveBuilder(triangleCount)
+
+    const innerPoints = new Float32Array(pointCount * 2)
+    const outerPoints = new Float32Array(pointCount * 2)
+
+    for (let i = 0; i < pointCount; ++i) {
+        const io = i * 2, ii = i * 2 + 1
+        const co = (io + 1) / pointCount * Math.PI, ci = (ii + 1) / pointCount * Math.PI
+        outerPoints[io] = Math.cos(co) * outerRadius
+        outerPoints[ii] = Math.sin(co) * outerRadius
+        innerPoints[io] = Math.cos(ci) * innerRadius
+        innerPoints[ii] = Math.sin(ci) * innerRadius
+    }
+
+    Vec3.set(op, 0, 0, thickness / 2)
+    Vec3.set(on, 0, 0, -thickness / 2)
+
+    for (let i = 0; i < pointCount; ++i) {
+        const ni = (i + 1) % pointCount
+        Vec3.set(a, outerPoints[i * 2], outerPoints[i * 2 + 1], 0)
+        Vec3.set(b, innerPoints[i * 2], innerPoints[i * 2 + 1], 0)
+        Vec3.set(c, outerPoints[ni * 2], outerPoints[ni * 2 + 1], 0)
+
+        builder.add(op, a, b)
+        builder.add(on, a, b)
+        builder.add(op, b, c)
+        builder.add(on, b, c)
+    }
+
+    return builder.getPrimitive()
+}

+ 154 - 0
src/mol-geo/primitive/tube.ts

@@ -0,0 +1,154 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ * @author David Sehnal <david.sehnal@gmail.com>
+ */
+
+import { Vec3 } from 'mol-math/linear-algebra';
+import { ChunkedArray } from 'mol-data/util';
+import { MeshBuilderState } from '../shape/mesh-builder';
+
+const normalVector = Vec3.zero()
+const binormalVector = Vec3.zero()
+const controlPoint = Vec3.zero()
+const tempPos = Vec3.zero()
+const a = Vec3.zero()
+const b = Vec3.zero()
+const u = Vec3.zero()
+const v = Vec3.zero()
+
+export function addTube(controlPoints: ArrayLike<number>, normalVectors: ArrayLike<number>, binormalVectors: ArrayLike<number>, linearSegments: number, radialSegments: number, width: number, height: number, waveFactor: number, startCap: boolean, endCap: boolean, state: MeshBuilderState) {
+    const { vertices, normals, indices } = state
+
+    let vertexCount = vertices.elementCount
+    const di = 1 / linearSegments
+
+    for (let i = 0; i <= linearSegments; ++i) {
+        const i3 = i * 3
+        Vec3.fromArray(u, normalVectors, i3)
+        Vec3.fromArray(v, binormalVectors, i3)
+
+        const tt = di * i - 0.5;
+        const ff = 1 + (waveFactor - 1) * (Math.cos(2 * Math.PI * tt) + 1);
+        const w = ff * width, h = ff * height;
+
+        for (let j = 0; j < radialSegments; ++j) {
+            const t = 2 * Math.PI * j / radialSegments;
+
+            Vec3.copy(a, u)
+            Vec3.copy(b, v)
+            Vec3.add(
+                normalVector,
+                Vec3.scale(a, a, w * Math.cos(t)),
+                Vec3.scale(b, b, h * Math.sin(t))
+            )
+
+            Vec3.copy(a, u)
+            Vec3.copy(b, v)
+            Vec3.add(
+                binormalVector,
+                Vec3.scale(a, a, h * Math.cos(t)),
+                Vec3.scale(b, b, w * Math.sin(t))
+            )
+            Vec3.normalize(normalVector, normalVector)
+
+            Vec3.fromArray(tempPos, controlPoints, i3)
+            Vec3.add(tempPos, tempPos, binormalVector)
+
+            ChunkedArray.add3(vertices, tempPos[0], tempPos[1], tempPos[2]);
+            ChunkedArray.add3(normals, normalVector[0], normalVector[1], normalVector[2]);
+        }
+    }
+
+    for (let i = 0; i < linearSegments; ++i) {
+        for (let j = 0; j < radialSegments; ++j) {
+            ChunkedArray.add3(
+                indices,
+                vertexCount + i * radialSegments + (j + 1) % radialSegments,
+                vertexCount + (i + 1) * radialSegments + (j + 1) % radialSegments,
+                vertexCount + i * radialSegments + j
+            );
+            ChunkedArray.add3(
+                indices,
+                vertexCount + (i + 1) * radialSegments + (j + 1) % radialSegments,
+                vertexCount + (i + 1) * radialSegments + j,
+                vertexCount + i * radialSegments + j
+            );
+        }
+    }
+
+    if (startCap) {
+        const offset = 0
+        vertexCount = vertices.elementCount
+        Vec3.fromArray(u, normalVectors, offset)
+        Vec3.fromArray(v, binormalVectors, offset)
+        Vec3.fromArray(controlPoint, controlPoints, offset)
+        Vec3.cross(normalVector, u, v)
+
+        ChunkedArray.add3(vertices, controlPoint[0], controlPoint[1], controlPoint[2]);
+        ChunkedArray.add3(normals, normalVector[0], normalVector[1], normalVector[2]);
+
+        for (let i = 0; i < radialSegments; ++i) {
+            const t = 2 * Math.PI * i / radialSegments;
+
+            Vec3.copy(a, u)
+            Vec3.copy(b, v)
+            Vec3.add(
+                tempPos,
+                Vec3.scale(a, a, height * Math.cos(t)),
+                Vec3.scale(b, b, width * Math.sin(t))
+            )
+            Vec3.add(tempPos, controlPoint, tempPos)
+
+            ChunkedArray.add3(vertices, tempPos[0], tempPos[1], tempPos[2]);
+            ChunkedArray.add3(normals, normalVector[0], normalVector[1], normalVector[2]);
+
+            ChunkedArray.add3(
+                indices,
+                vertexCount,
+                vertexCount + i + 1,
+                vertexCount + (i + 1) % radialSegments + 1
+            );
+        }
+    }
+
+    if (endCap) {
+        const offset = linearSegments * 3
+        vertexCount = vertices.elementCount
+        Vec3.fromArray(u, normalVectors, offset)
+        Vec3.fromArray(v, binormalVectors, offset)
+        Vec3.fromArray(controlPoint, controlPoints, offset)
+        Vec3.cross(normalVector, u, v)
+
+        ChunkedArray.add3(vertices, controlPoint[0], controlPoint[1], controlPoint[2]);
+        ChunkedArray.add3(normals, normalVector[0], normalVector[1], normalVector[2]);
+
+        for (let i = 0; i < radialSegments; ++i) {
+            const t = 2 * Math.PI * i / radialSegments;
+
+            Vec3.copy(a, u)
+            Vec3.copy(b, v)
+            Vec3.add(
+                tempPos,
+                Vec3.scale(a, a, height * Math.cos(t)),
+                Vec3.scale(b, b, width * Math.sin(t))
+            )
+            Vec3.add(tempPos, controlPoint, tempPos)
+
+            ChunkedArray.add3(vertices, tempPos[0], tempPos[1], tempPos[2]);
+            ChunkedArray.add3(normals, normalVector[0], normalVector[1], normalVector[2]);
+
+            if (i < radialSegments - 2) {
+                ChunkedArray.add3(
+                    indices,
+                    vertexCount + i + 1,
+                    vertexCount + (i + 1) % radialSegments + 1,
+                    vertexCount
+                );
+            }
+        }
+    }
+
+    return (linearSegments + 1) * radialSegments + (startCap ? radialSegments + 1 : 0) + (endCap ? radialSegments + 1 : 0)
+}

+ 122 - 0
src/mol-geo/primitive/wedge.ts

@@ -0,0 +1,122 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+import { Vec3 } from 'mol-math/linear-algebra'
+import { Primitive } from './primitive';
+
+export const DefaultWedgeProps = {
+    width: 1,
+    height: 1,
+    depth: 1
+}
+export type WedgeProps = Partial<typeof DefaultWedgeProps>
+
+const _a = Vec3.create(0, 0.5, 0.5)
+const _b = Vec3.create(0.5, -0.5, 0.5)
+const _c = Vec3.create(-0.5, -0.5, 0.5)
+const _d = Vec3.create(0, 0.5, -0.5)
+const _e = Vec3.create(0.5, -0.5, -0.5)
+const _f = Vec3.create(-0.5, -0.5, -0.5)
+
+const a = Vec3.zero(), b = Vec3.zero(), c = Vec3.zero()
+const d = Vec3.zero(), e = Vec3.zero(), f = Vec3.zero()
+
+const nabc = Vec3.create(0, 0, 1)
+const ndef = Vec3.create(0, 0, -1)
+const nabde = Vec3.zero()
+const nbcef = Vec3.create(0, -1, 0)
+const nacdf = Vec3.zero()
+
+const s = Vec3.zero()
+
+export function Wedge(props?: WedgeProps): Primitive {
+    const { width, height, depth } = { ...DefaultWedgeProps, ...props }
+
+    const vertices = new Float32Array(54)
+    const normals = new Float32Array(54)
+    const indices = new Uint32Array(24)
+
+    Vec3.set(s, width, height, depth)
+    Vec3.mul(a, _a, s); Vec3.mul(b, _b, s); Vec3.mul(c, _c, s)
+    Vec3.mul(d, _d, s); Vec3.mul(e, _e, s); Vec3.mul(f, _f, s)
+
+    Vec3.sub(nabde, b, a)
+    Vec3.normalize(nabde, Vec3.set(nabde, -nabde[1], nabde[0], 0))
+    Vec3.sub(nacdf, c, a)
+    Vec3.normalize(nacdf, Vec3.set(nacdf, nacdf[1], -nacdf[0], 0))
+
+    let vc = 0
+    let ic = 0
+
+    // abc
+    Vec3.toArray(a, vertices, vc + 0)
+    Vec3.toArray(c, vertices, vc + 3)
+    Vec3.toArray(b, vertices, vc + 6)
+    for (let i = 0; i < 3; ++i) Vec3.toArray(nabc, normals, vc + i * 3)
+    indices[ic + 0] = vc / 3 + 0
+    indices[ic + 1] = vc / 3 + 1
+    indices[ic + 2] = vc / 3 + 2
+    vc += 9
+    ic += 3
+
+    // def
+    Vec3.toArray(d, vertices, vc + 0)
+    Vec3.toArray(e, vertices, vc + 3)
+    Vec3.toArray(f, vertices, vc + 6)
+    for (let i = 0; i < 3; ++i) Vec3.toArray(ndef, normals, vc + i * 3)
+    indices[ic + 0] = vc / 3 + 0
+    indices[ic + 1] = vc / 3 + 1
+    indices[ic + 2] = vc / 3 + 2
+    vc += 9
+    ic += 3
+
+    // abde
+    Vec3.toArray(a, vertices, vc + 0)
+    Vec3.toArray(d, vertices, vc + 3)
+    Vec3.toArray(e, vertices, vc + 6)
+    Vec3.toArray(b, vertices, vc + 9)
+    for (let i = 0; i < 4; ++i) Vec3.toArray(nabde, normals, vc + i * 3)
+    indices[ic + 0] = vc / 3 + 2
+    indices[ic + 1] = vc / 3 + 1
+    indices[ic + 2] = vc / 3 + 0
+    indices[ic + 3] = vc / 3 + 0
+    indices[ic + 4] = vc / 3 + 3
+    indices[ic + 5] = vc / 3 + 2
+    vc += 12
+    ic += 6
+
+    // acdf
+    Vec3.toArray(d, vertices, vc + 0)
+    Vec3.toArray(a, vertices, vc + 3)
+    Vec3.toArray(c, vertices, vc + 6)
+    Vec3.toArray(f, vertices, vc + 9)
+    for (let i = 0; i < 4; ++i) Vec3.toArray(nacdf, normals, vc + i * 3)
+    indices[ic + 0] = vc / 3 + 2
+    indices[ic + 1] = vc / 3 + 1
+    indices[ic + 2] = vc / 3 + 0
+    indices[ic + 3] = vc / 3 + 0
+    indices[ic + 4] = vc / 3 + 3
+    indices[ic + 5] = vc / 3 + 2
+    vc += 12
+    ic += 6
+
+    // bcef
+    Vec3.toArray(e, vertices, vc + 0)
+    Vec3.toArray(f, vertices, vc + 3)
+    Vec3.toArray(c, vertices, vc + 6)
+    Vec3.toArray(b, vertices, vc + 9)
+    for (let i = 0; i < 4; ++i) Vec3.toArray(nbcef, normals, vc + i * 3)
+    indices[ic + 0] = vc / 3 + 2
+    indices[ic + 1] = vc / 3 + 1
+    indices[ic + 2] = vc / 3 + 0
+    indices[ic + 3] = vc / 3 + 0
+    indices[ic + 4] = vc / 3 + 3
+    indices[ic + 5] = vc / 3 + 2
+    vc += 12
+    ic += 6
+
+    return { vertices, normals, indices }
+}

+ 19 - 5
src/mol-geo/representation/index.ts

@@ -4,15 +4,29 @@
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
  */
 
 
-import { Task } from 'mol-task'
-import { RenderObject } from 'mol-gl/render-object';
-import { PickingId, PickingInfo } from '../util/picking';
+import { Task, RuntimeContext } from 'mol-task'
+import { RenderObject } from 'mol-gl/render-object'
+import { PickingId } from '../util/picking';
+import { Loci } from 'mol-model/loci';
+import { MarkerAction } from '../util/marker-data';
 
 
 export interface RepresentationProps {}
 export interface RepresentationProps {}
 
 
 export interface Representation<D, P extends RepresentationProps = {}> {
 export interface Representation<D, P extends RepresentationProps = {}> {
-    renderObjects: ReadonlyArray<RenderObject>
+    readonly renderObjects: ReadonlyArray<RenderObject>
+    readonly props: Readonly<P>
     create: (data: D, props?: P) => Task<void>
     create: (data: D, props?: P) => Task<void>
     update: (props: P) => Task<void>
     update: (props: P) => Task<void>
-    getLabel: (pickingId: PickingId) => PickingInfo | null
+    getLoci: (pickingId: PickingId) => Loci
+    mark: (loci: Loci, action: MarkerAction) => void
+    destroy: () => void
+}
+
+export interface Visual<D, P extends RepresentationProps = {}> {
+    readonly renderObject: RenderObject
+    create: (ctx: RuntimeContext, data: D, props: P) => Promise<void>
+    update: (ctx: RuntimeContext, props: P) => Promise<boolean>
+    getLoci: (pickingId: PickingId) => Loci
+    mark: (loci: Loci, action: MarkerAction) => void
+    destroy: () => void
 }
 }

+ 52 - 0
src/mol-geo/representation/structure/backbone.ts

@@ -0,0 +1,52 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+import { StructureRepresentation, StructureUnitsRepresentation } from '.';
+import { PickingId } from '../../util/picking';
+import { Structure } from 'mol-model/structure';
+import { Task } from 'mol-task';
+import { Loci } from 'mol-model/loci';
+import { MarkerAction } from '../../util/marker-data';
+import { PolymerBackboneVisual, DefaultPolymerBackboneProps } from './visual/polymer-backbone-cylinder';
+
+export const DefaultBackboneProps = {
+    ...DefaultPolymerBackboneProps
+}
+export type BackboneProps = Partial<typeof DefaultBackboneProps>
+
+export function BackboneRepresentation(): StructureRepresentation<BackboneProps> {
+    const traceRepr = StructureUnitsRepresentation(PolymerBackboneVisual)
+
+    return {
+        get renderObjects() {
+            return [ ...traceRepr.renderObjects ]
+        },
+        get props() {
+            return { ...traceRepr.props }
+        },
+        create: (structure: Structure, props: BackboneProps = {} as BackboneProps) => {
+            const p = Object.assign({}, DefaultBackboneProps, props)
+            return Task.create('BackboneRepresentation', async ctx => {
+                await traceRepr.create(structure, p).runInContext(ctx)
+            })
+        },
+        update: (props: BackboneProps) => {
+            const p = Object.assign({}, props)
+            return Task.create('Updating BackboneRepresentation', async ctx => {
+                await traceRepr.update(p).runInContext(ctx)
+            })
+        },
+        getLoci: (pickingId: PickingId) => {
+            return traceRepr.getLoci(pickingId)
+        },
+        mark: (loci: Loci, action: MarkerAction) => {
+            traceRepr.mark(loci, action)
+        },
+        destroy() {
+            traceRepr.destroy()
+        }
+    }
+}

+ 80 - 0
src/mol-geo/representation/structure/ball-and-stick.ts

@@ -0,0 +1,80 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+import { StructureRepresentation, StructureUnitsRepresentation } from '.';
+import { ElementSphereVisual, DefaultElementSphereProps } from './visual/element-sphere';
+import { IntraUnitLinkVisual, DefaultIntraUnitLinkProps } from './visual/intra-unit-link-cylinder';
+import { PickingId } from '../../util/picking';
+import { Structure, Unit } from 'mol-model/structure';
+import { Task } from 'mol-task';
+import { Loci, isEmptyLoci } from 'mol-model/loci';
+import { MarkerAction } from '../../util/marker-data';
+import { SizeTheme } from '../../theme';
+import { InterUnitLinkVisual } from './visual/inter-unit-link-cylinder';
+
+export const DefaultBallAndStickProps = {
+    ...DefaultElementSphereProps,
+    ...DefaultIntraUnitLinkProps,
+
+    sizeTheme: { name: 'uniform', value: 0.25 } as SizeTheme,
+    unitKinds: [ Unit.Kind.Atomic ] as Unit.Kind[]
+}
+export type BallAndStickProps = Partial<typeof DefaultBallAndStickProps>
+
+export function BallAndStickRepresentation(): StructureRepresentation<BallAndStickProps> {
+    const elmementRepr = StructureUnitsRepresentation(ElementSphereVisual)
+    const intraLinkRepr = StructureUnitsRepresentation(IntraUnitLinkVisual)
+    const interLinkRepr = StructureRepresentation(InterUnitLinkVisual)
+
+    return {
+        get renderObjects() {
+            return [ ...elmementRepr.renderObjects, ...intraLinkRepr.renderObjects, ...interLinkRepr.renderObjects ]
+        },
+        get props() {
+            return { ...elmementRepr.props, ...intraLinkRepr.props, ...interLinkRepr.props }
+        },
+        create: (structure: Structure, props: BallAndStickProps = {} as BallAndStickProps) => {
+            const p = Object.assign({}, DefaultBallAndStickProps, props)
+            return Task.create('DistanceRestraintRepresentation', async ctx => {
+                await elmementRepr.create(structure, p).runInContext(ctx)
+                await intraLinkRepr.create(structure, p).runInContext(ctx)
+                await interLinkRepr.create(structure, p).runInContext(ctx)
+            })
+        },
+        update: (props: BallAndStickProps) => {
+            const p = Object.assign({}, props)
+            return Task.create('Updating BallAndStickRepresentation', async ctx => {
+                await elmementRepr.update(p).runInContext(ctx)
+                await intraLinkRepr.update(p).runInContext(ctx)
+                await interLinkRepr.update(p).runInContext(ctx)
+            })
+        },
+        getLoci: (pickingId: PickingId) => {
+            const sphereLoci = elmementRepr.getLoci(pickingId)
+            const intraLinkLoci = intraLinkRepr.getLoci(pickingId)
+            const interLinkLoci = interLinkRepr.getLoci(pickingId)
+            if (isEmptyLoci(sphereLoci)) {
+                if (isEmptyLoci(intraLinkLoci)) {
+                    return interLinkLoci
+                } else {
+                    return intraLinkLoci
+                }
+            } else {
+                return sphereLoci
+            }
+        },
+        mark: (loci: Loci, action: MarkerAction) => {
+            elmementRepr.mark(loci, action)
+            intraLinkRepr.mark(loci, action)
+            interLinkRepr.mark(loci, action)
+        },
+        destroy() {
+            elmementRepr.destroy()
+            intraLinkRepr.destroy()
+            interLinkRepr.destroy()
+        }
+    }
+}

+ 88 - 0
src/mol-geo/representation/structure/cartoon.ts

@@ -0,0 +1,88 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+import { StructureRepresentation, StructureUnitsRepresentation } from '.';
+import { PickingId } from '../../util/picking';
+import { Structure } from 'mol-model/structure';
+import { Task } from 'mol-task';
+import { Loci, isEmptyLoci } from 'mol-model/loci';
+import { MarkerAction } from '../../util/marker-data';
+import { PolymerTraceVisual, DefaultPolymerTraceProps } from './visual/polymer-trace-mesh';
+import { PolymerGapVisual, DefaultPolymerGapProps } from './visual/polymer-gap-cylinder';
+import { NucleotideBlockVisual, DefaultNucleotideBlockProps } from './visual/nucleotide-block-mesh';
+import { PolymerDirectionVisual, DefaultPolymerDirectionProps } from './visual/polymer-direction-wedge';
+import { CarbohydrateSymbolVisual } from './visual/carbohydrate-symbol-mesh';
+
+export const DefaultCartoonProps = {
+    ...DefaultPolymerTraceProps,
+    ...DefaultPolymerGapProps,
+    ...DefaultNucleotideBlockProps,
+    ...DefaultPolymerDirectionProps
+}
+export type CartoonProps = Partial<typeof DefaultCartoonProps>
+
+export function CartoonRepresentation(): StructureRepresentation<CartoonProps> {
+    const traceRepr = StructureUnitsRepresentation(PolymerTraceVisual)
+    const gapRepr = StructureUnitsRepresentation(PolymerGapVisual)
+    const blockRepr = StructureUnitsRepresentation(NucleotideBlockVisual)
+    const directionRepr = StructureUnitsRepresentation(PolymerDirectionVisual)
+    const carbohydrateRepr = StructureRepresentation(CarbohydrateSymbolVisual)
+
+    return {
+        get renderObjects() {
+            return [ ...traceRepr.renderObjects, ...gapRepr.renderObjects, ...blockRepr.renderObjects, ...directionRepr.renderObjects, ...carbohydrateRepr.renderObjects ]
+        },
+        get props() {
+            return { ...traceRepr.props, ...gapRepr.props, ...blockRepr.props, ...carbohydrateRepr.props }
+        },
+        create: (structure: Structure, props: CartoonProps = {} as CartoonProps) => {
+            const p = Object.assign({}, DefaultCartoonProps, props)
+            return Task.create('CartoonRepresentation', async ctx => {
+                await traceRepr.create(structure, p).runInContext(ctx)
+                await gapRepr.create(structure, p).runInContext(ctx)
+                await blockRepr.create(structure, p).runInContext(ctx)
+                await directionRepr.create(structure, p).runInContext(ctx)
+                await carbohydrateRepr.create(structure, p).runInContext(ctx)
+            })
+        },
+        update: (props: CartoonProps) => {
+            const p = Object.assign({}, props)
+            return Task.create('Updating CartoonRepresentation', async ctx => {
+                await traceRepr.update(p).runInContext(ctx)
+                await gapRepr.update(p).runInContext(ctx)
+                await blockRepr.update(p).runInContext(ctx)
+                await directionRepr.update(p).runInContext(ctx)
+                await carbohydrateRepr.update(p).runInContext(ctx)
+            })
+        },
+        getLoci: (pickingId: PickingId) => {
+            const traceLoci = traceRepr.getLoci(pickingId)
+            const gapLoci = gapRepr.getLoci(pickingId)
+            const blockLoci = blockRepr.getLoci(pickingId)
+            const directionLoci = directionRepr.getLoci(pickingId)
+            const carbohydrateRepr = directionRepr.getLoci(pickingId)
+            return !isEmptyLoci(traceLoci) ? traceLoci
+                : !isEmptyLoci(gapLoci) ? gapLoci
+                : !isEmptyLoci(blockLoci) ? blockLoci
+                : !isEmptyLoci(directionLoci) ? directionLoci
+                : carbohydrateRepr
+        },
+        mark: (loci: Loci, action: MarkerAction) => {
+            traceRepr.mark(loci, action)
+            gapRepr.mark(loci, action)
+            blockRepr.mark(loci, action)
+            directionRepr.mark(loci, action)
+            carbohydrateRepr.mark(loci, action)
+        },
+        destroy() {
+            traceRepr.destroy()
+            gapRepr.destroy()
+            blockRepr.destroy()
+            directionRepr.destroy()
+            carbohydrateRepr.destroy()
+        }
+    }
+}

+ 55 - 0
src/mol-geo/representation/structure/distance-restraint.ts

@@ -0,0 +1,55 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+import { StructureRepresentation } from '.';
+import { PickingId } from '../../util/picking';
+import { Structure } from 'mol-model/structure';
+import { Task } from 'mol-task';
+import { Loci } from 'mol-model/loci';
+import { MarkerAction } from '../../util/marker-data';
+import { SizeTheme } from '../../theme';
+import { CrossLinkRestraintVisual, DefaultCrossLinkRestraintProps } from './visual/cross-link-restraint-cylinder';
+
+export const DefaultDistanceRestraintProps = {
+    ...DefaultCrossLinkRestraintProps,
+
+    sizeTheme: { name: 'uniform', value: 0.25 } as SizeTheme,
+}
+export type DistanceRestraintProps = Partial<typeof DefaultDistanceRestraintProps>
+
+export function DistanceRestraintRepresentation(): StructureRepresentation<DistanceRestraintProps> {
+    const crossLinkRepr = StructureRepresentation(CrossLinkRestraintVisual)
+
+    return {
+        get renderObjects() {
+            return [ ...crossLinkRepr.renderObjects ]
+        },
+        get props() {
+            return { ...crossLinkRepr.props }
+        },
+        create: (structure: Structure, props: DistanceRestraintProps = {} as DistanceRestraintProps) => {
+            const p = Object.assign({}, DefaultDistanceRestraintProps, props)
+            return Task.create('DistanceRestraintRepresentation', async ctx => {
+                await crossLinkRepr.create(structure, p).runInContext(ctx)
+            })
+        },
+        update: (props: DistanceRestraintProps) => {
+            const p = Object.assign({}, props)
+            return Task.create('Updating DistanceRestraintRepresentation', async ctx => {
+                await crossLinkRepr.update(p).runInContext(ctx)
+            })
+        },
+        getLoci: (pickingId: PickingId) => {
+            return crossLinkRepr.getLoci(pickingId)
+        },
+        mark: (loci: Loci, action: MarkerAction) => {
+            crossLinkRepr.mark(loci, action)
+        },
+        destroy() {
+            crossLinkRepr.destroy()
+        }
+    }
+}

+ 169 - 103
src/mol-geo/representation/structure/index.ts

@@ -5,126 +5,192 @@
  * @author David Sehnal <david.sehnal@gmail.com>
  * @author David Sehnal <david.sehnal@gmail.com>
  */
  */
 
 
-import { Structure, StructureSymmetry, Unit, Element, Queries } from 'mol-model/structure';
+import { Structure, Unit } from 'mol-model/structure';
 import { Task } from 'mol-task'
 import { Task } from 'mol-task'
 import { RenderObject } from 'mol-gl/render-object';
 import { RenderObject } from 'mol-gl/render-object';
-import { Representation, RepresentationProps } from '..';
-import { ColorTheme } from '../../theme';
-import { PickingId, PickingInfo } from '../../util/picking';
-
-export interface UnitsRepresentation<P> {
-    renderObjects: ReadonlyArray<RenderObject>
-    create: (group: Unit.SymmetryGroup, props: P) => Task<void>
-    update: (props: P) => Task<boolean>
-    getLocation: (pickingId: PickingId) => Element.Location | null
-}
-
-export interface StructureRepresentation<P extends RepresentationProps = {}> extends Representation<Structure, P> {
-    renderObjects: ReadonlyArray<RenderObject>
-    create: (structure: Structure, props?: P) => Task<void>
-    update: (props: P) => Task<void>
-    getLocation: (pickingId: PickingId) => Element.Location | null
-    getLabel: (pickingId: PickingId) => PickingInfo | null
-}
+import { Representation, RepresentationProps, Visual } from '..';
+import { ColorTheme, SizeTheme } from '../../theme';
+import { PickingId } from '../../util/picking';
+import { Loci, EmptyLoci, isEmptyLoci } from 'mol-model/loci';
+import { MarkerAction } from '../../util/marker-data';
+import { getQualityProps, DefaultBaseProps } from '../util';
 
 
-interface GroupRepresentation<T> {
-    repr: UnitsRepresentation<T>
-    group: Unit.SymmetryGroup
-}
+export interface UnitsVisual<P extends RepresentationProps = {}> extends Visual<Unit.SymmetryGroup, P> { }
+export interface  StructureVisual<P extends RepresentationProps = {}> extends Visual<Structure, P> { }
 
 
-function label(loc: Element.Location) {
-    const model = loc.unit.model.label
-    const instance = loc.unit.conformation.operator.name
-    let element = ''
-
-    if (Unit.isAtomic(loc.unit)) {
-        const asym_id = Queries.props.chain.auth_asym_id(loc)
-        const seq_id = Queries.props.residue.auth_seq_id(loc)
-        const comp_id = Queries.props.residue.auth_comp_id(loc)
-        const atom_id = Queries.props.atom.auth_atom_id(loc)
-        element = `[${comp_id}]${seq_id}:${asym_id}.${atom_id}`
-    } else if (Unit.isCoarse(loc.unit)) {
-        const asym_id = Queries.props.coarse.asym_id(loc)
-        const seq_id_begin = Queries.props.coarse.seq_id_begin(loc)
-        const seq_id_end = Queries.props.coarse.seq_id_end(loc)
-        if (seq_id_begin === seq_id_end) {
-            const entityKey = Queries.props.coarse.entityKey(loc)
-            const seq = loc.unit.model.sequence.byEntityKey[entityKey]
-            const comp_id = seq.compId.value(seq_id_begin)
-            element = `[${comp_id}]${seq_id_begin}:${asym_id}`
-        } else {
-            element = `${seq_id_begin}-${seq_id_end}:${asym_id}`
-        }
-    } else {
-        element = 'unknown'
-    }
-
-    return { label: `${model} ${instance} ${element}` }
-}
+export interface StructureRepresentation<P extends RepresentationProps = {}> extends Representation<Structure, P> { }
 
 
 export const DefaultStructureProps = {
 export const DefaultStructureProps = {
+    ...DefaultBaseProps,
     colorTheme: { name: 'instance-index' } as ColorTheme,
     colorTheme: { name: 'instance-index' } as ColorTheme,
-    alpha: 1,
-    visible: true,
-    doubleSided: false,
-    depthMask: true,
-    hoverSelection: { objectId: -1, instanceId: -1, elementId: -1 } as PickingId
+    sizeTheme: { name: 'physical' } as SizeTheme,
 }
 }
 export type StructureProps = Partial<typeof DefaultStructureProps>
 export type StructureProps = Partial<typeof DefaultStructureProps>
 
 
-export function StructureRepresentation<P extends StructureProps>(reprCtor: () => UnitsRepresentation<P>): StructureRepresentation<P> {
-    const renderObjects: RenderObject[] = []
-    const groupReprs: GroupRepresentation<P>[] = []
-    // let currentProps: typeof DefaultStructureProps
-
-    function getLocation(pickingId: PickingId) {
-        for (let i = 0, il = groupReprs.length; i < il; ++i) {
-            const loc = groupReprs[i].repr.getLocation(pickingId)
-            if (loc) return loc
-        }
-        return null
+export function StructureRepresentation<P extends StructureProps>(visualCtor: () => StructureVisual<P>): StructureRepresentation<P> {
+    let visual: StructureVisual<P>
+
+    let _props: Required<P>
+    let _structure: Structure
+
+    function create(structure: Structure, props: P = {} as P) {
+        _props = Object.assign({}, DefaultStructureProps, _props, props, getQualityProps(props, structure))
+
+        return Task.create('Creating StructureRepresentation', async ctx => {
+            if (!_structure) {
+                visual = visualCtor()
+                await visual.create(ctx, structure, _props)
+            } else {
+                if (_structure.hashCode === structure.hashCode) {
+                    await update(_props)
+                } else {
+                    if (!await visual.update(ctx, _props)) {
+                        await visual.create(ctx, _structure, _props)
+                    }
+                }
+            }
+            _structure = structure
+        });
+    }
+
+    function update(props: P) {
+        return Task.create('Updating StructureRepresentation', async ctx => {
+            _props = Object.assign({}, DefaultStructureProps, _props, props, getQualityProps(props, _structure))
+
+            if (!await visual.update(ctx, _props)) {
+                await visual.create(ctx, _structure, _props)
+            }
+        })
+    }
+
+    function getLoci(pickingId: PickingId) {
+        let loci: Loci = EmptyLoci
+        const _loci = visual.getLoci(pickingId)
+        if (!isEmptyLoci(_loci)) loci = _loci
+        return loci
+    }
+
+    function mark(loci: Loci, action: MarkerAction) {
+        visual.mark(loci, action)
+    }
+
+    function destroy() {
+        visual.destroy()
     }
     }
 
 
     return {
     return {
-        renderObjects,
-        create(structure: Structure, props: P = {} as P) {
-            // currentProps = Object.assign({}, DefaultStructureProps, props)
-
-            return Task.create('StructureRepresentation.create', async ctx => {
-                // const { query } = currentProps
-                // const qs = await query(structure).runAsChild(ctx)
-                // const subStructure = Selection.unionStructure(qs)
-
-                const groups = StructureSymmetry.getTransformGroups(structure);
-                for (let i = 0; i < groups.length; i++) {
-                    const group = groups[i];
-                    const repr = reprCtor()
-                    groupReprs.push({ repr, group })
-                    await repr.create(group, props).runAsChild(ctx, { message: 'Building structure unit representations...', current: i, max: groups.length });
-                    renderObjects.push(...repr.renderObjects)
+        get renderObjects() { return [ visual.renderObject ] },
+        get props() { return _props },
+        create,
+        update,
+        getLoci,
+        mark,
+        destroy
+    }
+}
+
+export function StructureUnitsRepresentation<P extends StructureProps>(visualCtor: () => UnitsVisual<P>): StructureRepresentation<P> {
+    let visuals = new Map<number, { group: Unit.SymmetryGroup, visual: UnitsVisual<P> }>()
+
+    let _props: Required<P>
+    let _structure: Structure
+    let _groups: ReadonlyArray<Unit.SymmetryGroup>
+
+    function create(structure: Structure, props: P = {} as P) {
+        _props = Object.assign({}, DefaultStructureProps, _props, props, getQualityProps(props, structure))
+
+        return Task.create('Creating StructureRepresentation', async ctx => {
+            if (!_structure) {
+                _groups = structure.unitSymmetryGroups;
+                for (let i = 0; i < _groups.length; i++) {
+                    const group = _groups[i];
+                    const visual = visualCtor()
+                    await visual.create(ctx, group, _props)
+                    visuals.set(group.hashCode, { visual, group })
                 }
                 }
-            });
-        },
-        update(props: P) {
-            return Task.create('StructureRepresentation.update', async ctx => {
-                renderObjects.length = 0 // clear
-
-                for (let i = 0, il = groupReprs.length; i < il; ++i) {
-                    const groupRepr = groupReprs[i]
-                    const { repr, group } = groupRepr
-                    const state = { message: 'Updating structure unit representations...', current: i, max: il };
-                    if (!await repr.update(props).runAsChild(ctx, state)) {
-                        console.log('update failed, need to rebuild')
-                        await repr.create(group, props).runAsChild(ctx, state)
+            } else {
+                if (_structure.hashCode === structure.hashCode) {
+                    await update(_props)
+                } else {
+                    _groups = structure.unitSymmetryGroups;
+                    const newGroups: Unit.SymmetryGroup[] = []
+                    const oldUnitsVisuals = visuals
+                    visuals = new Map()
+                    for (let i = 0; i < _groups.length; i++) {
+                        const group = _groups[i];
+                        const visualGroup = oldUnitsVisuals.get(group.hashCode)
+                        if (visualGroup) {
+                            const { visual, group } = visualGroup
+                            if (!await visual.update(ctx, _props)) {
+                                await visual.create(ctx, group, _props)
+                            }
+                            oldUnitsVisuals.delete(group.hashCode)
+                        } else {
+                            newGroups.push(group)
+                            const visual = visualCtor()
+                            await visual.create(ctx, group, _props)
+                            visuals.set(group.hashCode, { visual, group })
+                        }
                     }
                     }
-                    renderObjects.push(...repr.renderObjects)
+
+                    // for new groups, reuse leftover visuals
+                    const unusedVisuals: UnitsVisual<P>[] = []
+                    oldUnitsVisuals.forEach(({ visual }) => unusedVisuals.push(visual))
+                    newGroups.forEach(async group => {
+                        const visual = unusedVisuals.pop() || visualCtor()
+                        await visual.create(ctx, group, _props)
+                        visuals.set(group.hashCode, { visual, group })
+                    })
+                    unusedVisuals.forEach(visual => visual.destroy())
+                }
+            }
+            _structure = structure
+        });
+    }
+
+    function update(props: P) {
+        return Task.create('Updating StructureRepresentation', async ctx => {
+            _props = Object.assign({}, DefaultStructureProps, _props, props, getQualityProps(props, _structure))
+
+            visuals.forEach(async ({ visual, group }) => {
+                if (!await visual.update(ctx, _props)) {
+                    await visual.create(ctx, group, _props)
                 }
                 }
             })
             })
+        })
+    }
+
+    function getLoci(pickingId: PickingId) {
+        let loci: Loci = EmptyLoci
+        visuals.forEach(({ visual }) => {
+            const _loci = visual.getLoci(pickingId)
+            if (!isEmptyLoci(_loci)) loci = _loci
+        })
+        return loci
+    }
+
+    function mark(loci: Loci, action: MarkerAction) {
+        visuals.forEach(({ visual }) => visual.mark(loci, action))
+    }
+
+    function destroy() {
+        visuals.forEach(({ visual }) => visual.destroy())
+        visuals.clear()
+    }
+
+    return {
+        get renderObjects() {
+            const renderObjects: RenderObject[] = []
+            visuals.forEach(({ visual }) => renderObjects.push(visual.renderObject))
+            return renderObjects
+        },
+        get props() {
+            return _props
         },
         },
-        getLocation,
-        getLabel(pickingId: PickingId) {
-            const loc = getLocation(pickingId)
-            return loc ? label(loc) : null
-        }
+        create,
+        update,
+        getLoci,
+        mark,
+        destroy
     }
     }
 }
 }

+ 0 - 169
src/mol-geo/representation/structure/point.ts

@@ -1,169 +0,0 @@
-/**
- * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
- *
- * @author Alexander Rose <alexander.rose@weirdbyte.de>
- * @author David Sehnal <david.sehnal@gmail.com>
- */
-
-import { ValueCell } from 'mol-util/value-cell'
-import { createPointRenderObject, RenderObject, PointRenderObject } from 'mol-gl/render-object'
-import { Unit, Element } from 'mol-model/structure';
-import { Task } from 'mol-task'
-import { fillSerial } from 'mol-gl/renderable/util';
-
-import { UnitsRepresentation, DefaultStructureProps } from './index';
-import VertexMap from '../../shape/vertex-map';
-import { SizeTheme } from '../../theme';
-import { createTransforms, createColors, createSizes, createFlags } from './utils';
-import { deepEqual, defaults } from 'mol-util';
-import { SortedArray } from 'mol-data/int';
-import { RenderableState, PointValues } from 'mol-gl/renderable';
-import { PickingId } from '../../util/picking';
-
-export const DefaultPointProps = {
-    ...DefaultStructureProps,
-    sizeTheme: { name: 'vdw' } as SizeTheme
-}
-export type PointProps = Partial<typeof DefaultPointProps>
-
-export function createPointVertices(unit: Unit) {
-    const elements = unit.elements
-    const elementCount = elements.length
-    const vertices = new Float32Array(elementCount * 3)
-
-    const { x, y, z } = unit.conformation
-    const l = Element.Location()
-    l.unit = unit
-
-    for (let i = 0; i < elementCount; i++) {
-        l.element = elements[i];
-        const i3 = i * 3
-        vertices[i3] = x(l.element)
-        vertices[i3 + 1] = y(l.element)
-        vertices[i3 + 2] = z(l.element)
-    }
-    return vertices
-}
-
-export default function Point(): UnitsRepresentation<PointProps> {
-    const renderObjects: RenderObject[] = []
-    let points: PointRenderObject
-    let currentProps = DefaultPointProps
-    let currentGroup: Unit.SymmetryGroup
-
-    let _units: ReadonlyArray<Unit>
-    let _elements: SortedArray
-
-    return {
-        renderObjects,
-        create(group: Unit.SymmetryGroup, props: PointProps = {}) {
-            currentProps = Object.assign({}, DefaultPointProps, props)
-
-            return Task.create('Point.create', async ctx => {
-                renderObjects.length = 0 // clear
-                currentGroup = group
-
-                _units = group.units
-                _elements = group.elements;
-
-                const { colorTheme, sizeTheme, hoverSelection } = currentProps
-                const elementCount = _elements.length
-
-                const vertexMap = VertexMap.create(
-                    elementCount,
-                    elementCount + 1,
-                    fillSerial(new Uint32Array(elementCount)),
-                    fillSerial(new Uint32Array(elementCount + 1))
-                )
-
-                await ctx.update('Computing point vertices');
-                const vertices = createPointVertices(_units[0])
-
-                await ctx.update('Computing point transforms');
-                const transforms = createTransforms(group)
-
-                await ctx.update('Computing point colors');
-                const color = createColors(group, vertexMap, colorTheme)
-
-                await ctx.update('Computing point sizes');
-                const size = createSizes(group, vertexMap, sizeTheme)
-
-                await ctx.update('Computing spacefill flags');
-                const flag = createFlags(group, hoverSelection.instanceId, hoverSelection.elementId)
-
-                const instanceCount = group.units.length
-
-                const values: PointValues = {
-                    aPosition: ValueCell.create(vertices),
-                    aElementId: ValueCell.create(fillSerial(new Float32Array(elementCount))),
-                    aTransform: transforms,
-                    aInstanceId: ValueCell.create(fillSerial(new Float32Array(instanceCount))),
-                    ...color,
-                    ...flag,
-                    ...size,
-
-                    uAlpha: ValueCell.create(defaults(props.alpha, 1.0)),
-                    uInstanceCount: ValueCell.create(instanceCount),
-                    uElementCount: ValueCell.create(group.elements.length),
-
-                    drawCount: ValueCell.create(vertices.length / 3),
-                    instanceCount: ValueCell.create(instanceCount),
-
-                    dPointSizeAttenuation: ValueCell.create(true)
-                }
-                const state: RenderableState = {
-                    depthMask: defaults(props.depthMask, true),
-                    visible: defaults(props.visible, true)
-                }
-
-                points = createPointRenderObject(values, state)
-                renderObjects.push(points)
-            })
-        },
-        update(props: PointProps) {
-            return Task.create('Point.update', async ctx => {
-                if (!points || !_units || !_elements) return false
-
-                const newProps = { ...currentProps, ...props }
-                if (deepEqual(currentProps, newProps)) {
-                    console.log('props identical, nothing to change')
-                    return true
-                }
-
-                // const elementCount = OrderedSet.size(_elementGroup.elements)
-                // const unitCount = _units.length
-
-                // const vertexMap = VertexMap.create(
-                //     elementCount,
-                //     elementCount + 1,
-                //     fillSerial(new Uint32Array(elementCount)),
-                //     fillSerial(new Uint32Array(elementCount + 1))
-                // )
-
-                if (!deepEqual(currentProps.colorTheme, newProps.colorTheme)) {
-                    console.log('colorTheme changed', currentProps.colorTheme, newProps.colorTheme)
-                    // await ctx.update('Computing point colors');
-                    // const color = createColors(_units, _elementGroup, vertexMap, newProps.colorTheme)
-                    // ValueCell.update(points.props.color, color)
-                }
-
-                if (!deepEqual(currentProps.sizeTheme, newProps.sizeTheme)) {
-                    console.log('sizeTheme changed', currentProps.sizeTheme, newProps.sizeTheme)
-                }
-
-                currentProps = newProps
-                return false
-            })
-        },
-        getLocation(pickingId: PickingId) {
-            const { objectId, instanceId, elementId } = pickingId
-            if (points.id === objectId) {
-                const l = Element.Location()
-                l.unit = currentGroup.units[instanceId]
-                l.element = currentGroup.elements[elementId]
-                return l
-            }
-            return null
-        }
-    }
-}

+ 6 - 153
src/mol-geo/representation/structure/spacefill.ts

@@ -2,163 +2,16 @@
  * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
  * Copyright (c) 2018 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>
  */
  */
 
 
-import { ValueCell } from 'mol-util/value-cell'
-
-import { RenderObject, createMeshRenderObject, MeshRenderObject } from 'mol-gl/render-object'
-import { Unit, Element, Queries } from 'mol-model/structure';
-import { UnitsRepresentation, DefaultStructureProps } from './index';
-import { Task } from 'mol-task'
-import { createTransforms, createColors, createFlags, createEmptyFlags, createSphereMesh } from './utils';
-import VertexMap from '../../shape/vertex-map';
-import { deepEqual, defaults } from 'mol-util';
-import { fillSerial } from 'mol-gl/renderable/util';
-import { RenderableState, MeshValues } from 'mol-gl/renderable';
-import { getMeshData } from '../../util/mesh-data';
-import { Mesh } from '../../shape/mesh';
-import { PickingId } from '../../util/picking';
-
-function createSpacefillMesh(unit: Unit, detail: number, mesh?: Mesh) {
-    let radius: Element.Property<number>
-    if (Unit.isAtomic(unit)) {
-        radius = Queries.props.atom.vdw_radius
-    } else if (Unit.isSpheres(unit)) {
-        radius = Queries.props.coarse.sphere_radius
-    } else {
-        console.warn('Unsupported unit type')
-        return Task.constant('Empty mesh', Mesh.createEmpty(mesh))
-    }
-    return createSphereMesh(unit, radius, detail, mesh)
-}
+import { StructureUnitsRepresentation } from '.';
+import { ElementSphereVisual, DefaultElementSphereProps } from './visual/element-sphere';
 
 
 export const DefaultSpacefillProps = {
 export const DefaultSpacefillProps = {
-    ...DefaultStructureProps,
-    flipSided: false,
-    flatShaded: false,
-    detail: 0,
+    ...DefaultElementSphereProps,
 }
 }
 export type SpacefillProps = Partial<typeof DefaultSpacefillProps>
 export type SpacefillProps = Partial<typeof DefaultSpacefillProps>
 
 
-export default function Spacefill(): UnitsRepresentation<SpacefillProps> {
-    const renderObjects: RenderObject[] = []
-    let spheres: MeshRenderObject
-    let currentProps: typeof DefaultSpacefillProps
-    let mesh: Mesh
-    let currentGroup: Unit.SymmetryGroup
-    let vertexMap: VertexMap
-
-    return {
-        renderObjects,
-        create(group: Unit.SymmetryGroup, props: SpacefillProps = {}) {
-            currentProps = Object.assign({}, DefaultSpacefillProps, props)
-
-            return Task.create('Spacefill.create', async ctx => {
-                renderObjects.length = 0 // clear
-                currentGroup = group
-
-                const { detail, colorTheme, hoverSelection } = { ...DefaultSpacefillProps, ...props }
-
-                mesh = await createSpacefillMesh(group.units[0], detail).runAsChild(ctx, 'Computing spacefill mesh')
-                // console.log(mesh)
-                vertexMap = VertexMap.fromMesh(mesh)
-
-                await ctx.update('Computing spacefill transforms');
-                const transforms = createTransforms(group)
-
-                await ctx.update('Computing spacefill colors');
-                const color = createColors(group, vertexMap, colorTheme)
-
-                await ctx.update('Computing spacefill flags');
-                const flag = createFlags(group, hoverSelection.instanceId, hoverSelection.elementId)
-
-                const instanceCount = group.units.length
-
-                const values: MeshValues = {
-                    ...getMeshData(mesh),
-                    aTransform: transforms,
-                    aInstanceId: ValueCell.create(fillSerial(new Float32Array(instanceCount))),
-                    ...color,
-                    ...flag,
-
-                    uAlpha: ValueCell.create(defaults(props.alpha, 1.0)),
-                    uInstanceCount: ValueCell.create(instanceCount),
-                    uElementCount: ValueCell.create(group.elements.length),
-
-                    elements: mesh.indexBuffer,
-
-                    drawCount: ValueCell.create(mesh.triangleCount * 3),
-                    instanceCount: ValueCell.create(instanceCount),
-
-                    dDoubleSided: ValueCell.create(defaults(props.doubleSided, true)),
-                    dFlatShaded: ValueCell.create(defaults(props.flatShaded, false)),
-                    dFlipSided: ValueCell.create(defaults(props.flipSided, false)),
-                }
-                const state: RenderableState = {
-                    depthMask: defaults(props.depthMask, true),
-                    visible: defaults(props.visible, true)
-                }
-
-                spheres = createMeshRenderObject(values, state)
-                renderObjects.push(spheres)
-            })
-        },
-        update(props: SpacefillProps) {
-            const newProps = Object.assign({}, currentProps, props)
-
-            return Task.create('Spacefill.update', async ctx => {
-                if (!spheres) return false
-
-                let updateColor = false
-
-                if (newProps.detail !== currentProps.detail) {
-                    mesh = await createSpacefillMesh(currentGroup.units[0], newProps.detail, mesh).runAsChild(ctx, 'Computing spacefill mesh')
-                    ValueCell.update(spheres.values.drawCount, mesh.triangleCount * 3)
-                    // TODO update in-place
-                    vertexMap = VertexMap.fromMesh(mesh)
-                    updateColor = true
-                }
-
-                if (!deepEqual(newProps.colorTheme, currentProps.colorTheme)) {
-                    updateColor = true
-                }
-
-                if (updateColor) {
-                    await ctx.update('Computing spacefill colors');
-                    createColors(currentGroup, vertexMap, newProps.colorTheme, spheres.values)
-                }
-
-                if (newProps.hoverSelection !== currentProps.hoverSelection) {
-                    await ctx.update('Computing spacefill flags');
-                    if (newProps.hoverSelection.objectId === spheres.id) {
-                        createFlags(currentGroup, newProps.hoverSelection.instanceId, newProps.hoverSelection.elementId, spheres.values)
-                    } else {
-                        createEmptyFlags(spheres.values)
-                    }
-                }
-
-                ValueCell.updateIfChanged(spheres.values.uAlpha, newProps.alpha)
-                ValueCell.updateIfChanged(spheres.values.dDoubleSided, newProps.doubleSided)
-                ValueCell.updateIfChanged(spheres.values.dFlipSided, newProps.flipSided)
-                ValueCell.updateIfChanged(spheres.values.dFlatShaded, newProps.flatShaded)
-
-                spheres.state.visible = newProps.visible
-                spheres.state.depthMask = newProps.depthMask
-
-                currentProps = newProps
-                return true
-            })
-        },
-        getLocation(pickingId: PickingId) {
-            const { objectId, instanceId, elementId } = pickingId
-            if (spheres.id === objectId) {
-                const l = Element.Location()
-                l.unit = currentGroup.units[instanceId]
-                l.element = currentGroup.elements[elementId]
-                return l
-            }
-            return null
-        }
-    }
-}
+export function SpacefillRepresentation() {
+    return StructureUnitsRepresentation(ElementSphereVisual)
+}

+ 0 - 133
src/mol-geo/representation/structure/utils.ts

@@ -1,133 +0,0 @@
-/**
- * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
- *
- * @author Alexander Rose <alexander.rose@weirdbyte.de>
- * @author David Sehnal <david.sehnal@gmail.com>
- */
-
-import { Unit, Element } from 'mol-model/structure';
-import { Mat4, Vec2, Vec3 } from 'mol-math/linear-algebra'
-
-import { createUniformColor, ColorData } from '../../util/color-data';
-import { createUniformSize } from '../../util/size-data';
-import { elementSizeData } from '../../theme/structure/size/element';
-import VertexMap from '../../shape/vertex-map';
-import { ColorTheme, SizeTheme } from '../../theme';
-import { elementIndexColorData, elementSymbolColorData, instanceIndexColorData, chainIdColorData } from '../../theme/structure/color';
-import { ValueCell } from 'mol-util';
-import { TextureImage, createTextureImage } from 'mol-gl/renderable/util';
-import { Mesh } from '../../shape/mesh';
-import { Task } from 'mol-task';
-import { icosahedronVertexCount } from '../../primitive/icosahedron';
-import { MeshBuilder } from '../../shape/mesh-builder';
-
-export function createTransforms({ units }: Unit.SymmetryGroup, transforms?: ValueCell<Float32Array>) {
-    const unitCount = units.length
-    const n = unitCount * 16
-    const array = transforms && transforms.ref.value.length >= n ? transforms.ref.value : new Float32Array(n)
-    for (let i = 0; i < unitCount; i++) {
-        Mat4.toArray(units[i].conformation.operator.matrix, array, i * 16)
-    }
-    return transforms ? ValueCell.update(transforms, array) : ValueCell.create(array)
-}
-
-export function createColors(group: Unit.SymmetryGroup, vertexMap: VertexMap, props: ColorTheme, colorData?: ColorData) {
-    switch (props.name) {
-        case 'atom-index':
-            return elementIndexColorData({ group, vertexMap }, colorData)
-        case 'chain-id':
-            return chainIdColorData({ group, vertexMap }, colorData)
-        case 'element-symbol':
-            return elementSymbolColorData({ group, vertexMap }, colorData)
-        case 'instance-index':
-            return instanceIndexColorData({ group, vertexMap }, colorData)
-        case 'uniform':
-            return createUniformColor(props, colorData)
-    }
-}
-
-export function createSizes(group: Unit.SymmetryGroup, vertexMap: VertexMap, props: SizeTheme) {
-    switch (props.name) {
-        case 'uniform':
-            return createUniformSize(props)
-        case 'vdw':
-            return elementSizeData({ group, vertexMap })
-    }
-}
-
-export type FlagData = {
-    tFlag: ValueCell<TextureImage>
-    uFlagTexSize: ValueCell<Vec2>
-}
-
-export function createFlags(group: Unit.SymmetryGroup, instanceId: number, elementId: number, flagData?: FlagData): FlagData {
-    const instanceCount = group.units.length
-    const elementCount = group.elements.length
-    const count = instanceCount * elementCount
-    const flags = flagData && flagData.tFlag.ref.value.array.length >= count ? flagData.tFlag.ref.value : createTextureImage(count, 1)
-    let flagOffset = 0
-    for (let i = 0; i < instanceCount; i++) {
-        for (let j = 0, jl = elementCount; j < jl; ++j) {
-            flags.array[flagOffset] = (i === instanceId && j === elementId) ? 255 : 0
-            flagOffset += 1
-        }
-    }
-    // console.log(flags, instanceCount, elementCount)
-    if (flagData) {
-        ValueCell.update(flagData.tFlag, flags)
-        ValueCell.update(flagData.uFlagTexSize, Vec2.create(flags.width, flags.height))
-        return flagData
-    } else {
-        return {
-            tFlag: ValueCell.create(flags),
-            uFlagTexSize: ValueCell.create(Vec2.create(flags.width, flags.height)),
-        }
-    }
-}
-
-const emptyFlagTexture = { array: new Uint8Array(1), width: 1, height: 1 }
-export function createEmptyFlags(flagData?: FlagData) {
-    if (flagData) {
-        ValueCell.update(flagData.tFlag, emptyFlagTexture)
-        ValueCell.update(flagData.uFlagTexSize, Vec2.create(1, 1))
-        return flagData
-    } else {
-        return {
-            tFlag: ValueCell.create(emptyFlagTexture),
-            uFlagTexSize: ValueCell.create(Vec2.create(1, 1)),
-        }
-    }
-}
-
-export function createSphereMesh(unit: Unit, radius: Element.Property<number>, detail: number, mesh?: Mesh) {
-    return Task.create('Sphere mesh', async ctx => {
-        const { elements } = unit;
-        const elementCount = elements.length;
-        const vertexCount = elementCount * icosahedronVertexCount(detail)
-        const meshBuilder = MeshBuilder.create(vertexCount, vertexCount / 2, mesh)
-
-        const v = Vec3.zero()
-        const m = Mat4.identity()
-
-        const { x, y, z } = unit.conformation
-        const l = Element.Location()
-        l.unit = unit
-
-        for (let i = 0; i < elementCount; i++) {
-            l.element = elements[i]
-            v[0] = x(l.element)
-            v[1] = y(l.element)
-            v[2] = z(l.element)
-            Mat4.setTranslation(m, v)
-
-            meshBuilder.setId(i)
-            meshBuilder.addIcosahedron(m, { radius: radius(l), detail })
-
-            if (i % 10000 === 0 && ctx.shouldUpdate) {
-                await ctx.update({ message: 'Sphere mesh', current: i, max: elementCount });
-            }
-        }
-
-        return meshBuilder.getMesh()
-    })
-}

+ 214 - 0
src/mol-geo/representation/structure/visual/carbohydrate-symbol-mesh.ts

@@ -0,0 +1,214 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+import { ValueCell } from 'mol-util/value-cell'
+
+import { createMeshRenderObject, MeshRenderObject } from 'mol-gl/render-object'
+import { Unit, Structure } from 'mol-model/structure';
+import { DefaultStructureProps, StructureVisual } from '..';
+import { RuntimeContext } from 'mol-task'
+import { createIdentityTransform } 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 { 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 { createUniformColor } from '../../../util/color-data';
+import { getSaccharideShape, SaccharideShapes } from 'mol-model/structure/structure/carbohydrates/constants';
+
+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) {
+    const builder = MeshBuilder.create(256, 128, mesh)
+
+    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)
+
+        switch (shapeType) {
+            case SaccharideShapes.FilledSphere:
+                builder.addSphere(cGeo.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)
+                break
+        }
+    }
+
+    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()
+}
+
+export const DefaultCarbohydrateSymbolProps = {
+    ...DefaultMeshProps,
+    ...DefaultStructureProps,
+    sizeTheme: { name: 'physical', factor: 1 } as SizeTheme,
+    detail: 0,
+    unitKinds: [ Unit.Kind.Atomic, Unit.Kind.Spheres ] as Unit.Kind[]
+}
+export type CarbohydrateSymbolProps = Partial<typeof DefaultCarbohydrateSymbolProps>
+
+export function CarbohydrateSymbolVisual(): StructureVisual<CarbohydrateSymbolProps> {
+    let renderObject: MeshRenderObject
+    let currentProps: typeof DefaultCarbohydrateSymbolProps
+    let mesh: Mesh
+    let currentStructure: Structure
+
+    return {
+        get renderObject () { return renderObject },
+        async create(ctx: RuntimeContext, structure: Structure, props: CarbohydrateSymbolProps = {}) {
+            currentProps = Object.assign({}, DefaultCarbohydrateSymbolProps, props)
+            currentStructure = structure
+
+            const instanceCount = 1
+            const elementCount = currentStructure.elementCount
+
+            mesh = await createCarbohydrateSymbolMesh(ctx, currentStructure, mesh)
+            // console.log(mesh)
+
+            const transforms = createIdentityTransform()
+            const color = createUniformColor({ value: 0x999911 }) // TODO
+            const marker = createMarkers(instanceCount * elementCount)
+
+            const counts = { drawCount: mesh.triangleCount * 3, elementCount, instanceCount }
+
+            const values: MeshValues = {
+                ...getMeshData(mesh),
+                ...color,
+                ...marker,
+                aTransform: transforms,
+                elements: mesh.indexBuffer,
+                ...createMeshValues(currentProps, counts),
+                aColor: ValueCell.create(new Float32Array(mesh.vertexCount * 3))
+            }
+            const state = createRenderableState(currentProps)
+
+            renderObject = createMeshRenderObject(values, state)
+        },
+        async update(ctx: RuntimeContext, props: CarbohydrateSymbolProps) {
+            const newProps = Object.assign({}, currentProps, props)
+
+            if (!renderObject) return false
+
+            updateMeshValues(renderObject.values, newProps)
+            updateRenderableState(renderObject.state, newProps)
+
+            return false
+        },
+        getLoci(pickingId: PickingId) {
+            return EmptyLoci
+        },
+        mark(loci: Loci, action: MarkerAction) {
+            // TODO
+        },
+        destroy() {
+            // TODO
+        }
+    }
+}

+ 165 - 0
src/mol-geo/representation/structure/visual/cross-link-restraint-cylinder.ts

@@ -0,0 +1,165 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+import { ValueCell } from 'mol-util/value-cell'
+
+import { createMeshRenderObject, MeshRenderObject } from 'mol-gl/render-object'
+import { Link, Structure } from 'mol-model/structure';
+import { DefaultStructureProps, StructureVisual } from '..';
+import { RuntimeContext } from 'mol-task'
+import { LinkCylinderProps, DefaultLinkCylinderProps, createLinkCylinderMesh } from './util/link';
+import { MeshValues } from 'mol-gl/renderable';
+import { getMeshData } from '../../../util/mesh-data';
+import { Mesh } from '../../../shape/mesh';
+import { PickingId } from '../../../util/picking';
+import { Vec3 } from 'mol-math/linear-algebra';
+import { createUniformColor } from '../../../util/color-data';
+import { Loci, isEveryLoci, EmptyLoci } from 'mol-model/loci';
+import { MarkerAction, applyMarkerAction, createMarkers, MarkerData } from '../../../util/marker-data';
+import { SizeTheme } from '../../../theme';
+import { createIdentityTransform } from './util/common';
+import { updateMeshValues, updateRenderableState, createMeshValues, createRenderableState } from '../../util';
+// import { chainIdLinkColorData } from '../../../theme/structure/color/chain-id';
+
+async function createCrossLinkRestraintCylinderMesh(ctx: RuntimeContext, structure: Structure, props: LinkCylinderProps, mesh?: Mesh) {
+
+    const crossLinks = structure.crossLinkRestraints
+    if (!crossLinks.count) return Mesh.createEmpty(mesh)
+
+    const builderProps = {
+        linkCount: crossLinks.count,
+        referencePosition: (edgeIndex: number) => null,
+        position: (posA: Vec3, posB: Vec3, edgeIndex: number) => {
+            const b = crossLinks.pairs[edgeIndex]
+            // console.log(b)
+            const uA = b.unitA, uB = b.unitB
+            uA.conformation.position(uA.elements[b.indexA], posA)
+            uB.conformation.position(uB.elements[b.indexB], posB)
+            // console.log(posA, posB)
+        },
+        order: (edgeIndex: number) => 1,
+        flags: (edgeIndex: number) => 0
+    }
+
+    return createLinkCylinderMesh(ctx, builderProps, props, mesh)
+}
+
+export const DefaultCrossLinkRestraintProps = {
+    ...DefaultStructureProps,
+    ...DefaultLinkCylinderProps,
+    sizeTheme: { name: 'physical', factor: 0.3 } as SizeTheme,
+    flipSided: false,
+    flatShaded: false,
+}
+export type CrossLinkRestraintProps = Partial<typeof DefaultCrossLinkRestraintProps>
+
+export function CrossLinkRestraintVisual(): StructureVisual<CrossLinkRestraintProps> {
+    let renderObject: MeshRenderObject
+    let currentProps: typeof DefaultCrossLinkRestraintProps
+    let mesh: Mesh
+    let currentStructure: Structure
+
+    return {
+        get renderObject () { return renderObject },
+        async create(ctx: RuntimeContext, structure: Structure, props: CrossLinkRestraintProps = {}) {
+            currentProps = Object.assign({}, DefaultCrossLinkRestraintProps, props)
+            currentStructure = structure
+
+            const elementCount = structure.crossLinkRestraints.count
+            const instanceCount = 1
+
+            mesh = await createCrossLinkRestraintCylinderMesh(ctx, structure, currentProps)
+
+            const transforms = createIdentityTransform()
+            const color = createUniformColor({ value: 0x119911 }) // TODO
+            const marker = createMarkers(instanceCount * elementCount)
+
+            const counts = { drawCount: mesh.triangleCount * 3, elementCount, instanceCount }
+
+            const values: MeshValues = {
+                ...getMeshData(mesh),
+                ...color,
+                ...marker,
+                aTransform: transforms,
+                elements: mesh.indexBuffer,
+                ...createMeshValues(currentProps, counts),
+            }
+            const state = createRenderableState(currentProps)
+
+            renderObject = createMeshRenderObject(values, state)
+        },
+        async update(ctx: RuntimeContext, props: CrossLinkRestraintProps) {
+            const newProps = Object.assign({}, currentProps, props)
+
+            if (!renderObject) return false
+
+            // TODO create in-place
+            if (currentProps.radialSegments !== newProps.radialSegments) return false
+
+            updateMeshValues(renderObject.values, newProps)
+            updateRenderableState(renderObject.state, newProps)
+
+            return false
+        },
+        getLoci(pickingId: PickingId) {
+            return getLinkLoci(pickingId, currentStructure, renderObject.id)
+        },
+        mark(loci: Loci, action: MarkerAction) {
+            markLink(loci, action, currentStructure, renderObject.values)
+        },
+        destroy() {
+            // TODO
+        }
+    }
+}
+
+function getLinkLoci(pickingId: PickingId, structure: Structure, id: number) {
+    const { objectId, elementId } = pickingId
+    if (id === objectId) {
+        const pair = structure.crossLinkRestraints.pairs[elementId]
+        if (pair) {
+            return Link.Loci([{
+                aUnit: pair.unitA,
+                aIndex: pair.indexA,
+                bUnit: pair.unitB,
+                bIndex: pair.indexB
+            }])
+        }
+    }
+    return EmptyLoci
+}
+
+function markLink(loci: Loci, action: MarkerAction, structure: Structure, values: MarkerData) {
+    const tMarker = values.tMarker
+
+    const crossLinks = structure.crossLinkRestraints
+    const elementCount = crossLinks.count
+    const instanceCount = 1
+
+    let changed = false
+    const array = tMarker.ref.value.array
+    if (isEveryLoci(loci)) {
+        applyMarkerAction(array, 0, elementCount * instanceCount, action)
+        changed = true
+    } else if (Link.isLoci(loci)) {
+        for (const b of loci.links) {
+            const indices = crossLinks.getPairIndices(b.aIndex, b.aUnit, b.bIndex, b.bUnit)
+            if (indices) {
+                for (let i = 0, il = indices.length; i < il; ++i) {
+                    const idx = indices[i]
+                    if (applyMarkerAction(array, idx, idx + 1, action) && !changed) {
+                        changed = true
+                    }
+                }
+            }
+        }
+    } else {
+        return
+    }
+    if (changed) {
+        ValueCell.update(tMarker, tMarker.ref.value)
+    }
+}

+ 141 - 0
src/mol-geo/representation/structure/visual/element-point.ts

@@ -0,0 +1,141 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ * @author David Sehnal <david.sehnal@gmail.com>
+ */
+
+import { ValueCell } from 'mol-util/value-cell'
+import { createPointRenderObject, PointRenderObject } from 'mol-gl/render-object'
+import { Unit } from 'mol-model/structure';
+import { RuntimeContext } from 'mol-task'
+
+import { UnitsVisual, DefaultStructureProps } from '..';
+import VertexMap from '../../../shape/vertex-map';
+import { SizeTheme } from '../../../theme';
+import { markElement, getElementLoci } from './util/element';
+import { createTransforms, createColors, createSizes } from './util/common';
+import { deepEqual, defaults } from 'mol-util';
+import { SortedArray } from 'mol-data/int';
+import { RenderableState, PointValues } from 'mol-gl/renderable';
+import { PickingId } from '../../../util/picking';
+import { Loci } from 'mol-model/loci';
+import { MarkerAction, createMarkers } from '../../../util/marker-data';
+import { Vec3 } from 'mol-math/linear-algebra';
+import { fillSerial } from 'mol-util/array';
+
+export const DefaultPointProps = {
+    ...DefaultStructureProps,
+    sizeTheme: { name: 'physical' } as SizeTheme
+}
+export type PointProps = Partial<typeof DefaultPointProps>
+
+export function createPointVertices(unit: Unit) {
+    const elements = unit.elements
+    const elementCount = elements.length
+    const vertices = new Float32Array(elementCount * 3)
+
+    const pos = unit.conformation.invariantPosition
+
+    const p = Vec3.zero()
+    for (let i = 0; i < elementCount; i++) {
+        const i3 = i * 3
+        pos(elements[i], p)
+        vertices[i3] = p[0]
+        vertices[i3 + 1] = p[1]
+        vertices[i3 + 2] = p[2]
+    }
+    return vertices
+}
+
+export default function PointVisual(): UnitsVisual<PointProps> {
+    let renderObject: PointRenderObject
+    let currentProps = DefaultPointProps
+    let currentGroup: Unit.SymmetryGroup
+
+    let _units: ReadonlyArray<Unit>
+    let _elements: SortedArray
+
+    return {
+        get renderObject () { return renderObject },
+        async create(ctx: RuntimeContext, group: Unit.SymmetryGroup, props: PointProps = {}) {
+            currentProps = Object.assign({}, DefaultPointProps, props)
+            currentGroup = group
+
+            _units = group.units
+            _elements = group.elements;
+
+            const { colorTheme, sizeTheme } = currentProps
+            const elementCount = _elements.length
+            const instanceCount = group.units.length
+
+            const vertexMap = VertexMap.create(
+                elementCount,
+                elementCount + 1,
+                fillSerial(new Uint32Array(elementCount)),
+                fillSerial(new Uint32Array(elementCount + 1))
+            )
+
+            const vertices = createPointVertices(_units[0])
+            const transforms = createTransforms(group)
+            const color = createColors(group, elementCount, colorTheme)
+            const size = createSizes(group, vertexMap, sizeTheme)
+            const marker = createMarkers(instanceCount * elementCount)
+
+            const values: PointValues = {
+                aPosition: ValueCell.create(vertices),
+                aElementId: ValueCell.create(fillSerial(new Float32Array(elementCount))),
+                aTransform: transforms,
+                aInstanceId: ValueCell.create(fillSerial(new Float32Array(instanceCount))),
+                ...color,
+                ...marker,
+                ...size,
+
+                uAlpha: ValueCell.create(defaults(props.alpha, 1.0)),
+                uInstanceCount: ValueCell.create(instanceCount),
+                uElementCount: ValueCell.create(group.elements.length),
+
+                drawCount: ValueCell.create(vertices.length / 3),
+                instanceCount: ValueCell.create(instanceCount),
+
+                dPointSizeAttenuation: ValueCell.create(true),
+                dUseFog: ValueCell.create(defaults(props.useFog, true)),
+            }
+            const state: RenderableState = {
+                depthMask: defaults(props.depthMask, true),
+                visible: defaults(props.visible, true)
+            }
+
+            renderObject = createPointRenderObject(values, state)
+        },
+        async update(ctx: RuntimeContext, props: PointProps) {
+            if (!renderObject || !_units || !_elements) return false
+
+            const newProps = { ...currentProps, ...props }
+            if (deepEqual(currentProps, newProps)) {
+                console.log('props identical, nothing to change')
+                return true
+            }
+
+            if (!deepEqual(currentProps.colorTheme, newProps.colorTheme)) {
+                console.log('colorTheme changed', currentProps.colorTheme, newProps.colorTheme)
+            }
+
+            if (!deepEqual(currentProps.sizeTheme, newProps.sizeTheme)) {
+                console.log('sizeTheme changed', currentProps.sizeTheme, newProps.sizeTheme)
+            }
+
+            currentProps = newProps
+            return false
+        },
+        getLoci(pickingId: PickingId) {
+            return getElementLoci(renderObject.id, currentGroup, pickingId)
+        },
+        mark(loci: Loci, action: MarkerAction) {
+            markElement(renderObject.values.tMarker, currentGroup, loci, action)
+        },
+        destroy() {
+            // TODO
+        }
+    }
+}

+ 116 - 0
src/mol-geo/representation/structure/visual/element-sphere.ts

@@ -0,0 +1,116 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ * @author David Sehnal <david.sehnal@gmail.com>
+ */
+
+import { ValueCell } from 'mol-util/value-cell'
+
+import { createMeshRenderObject, MeshRenderObject } from 'mol-gl/render-object'
+import { Unit } from 'mol-model/structure';
+import { DefaultStructureProps, UnitsVisual } from '..';
+import { RuntimeContext } from 'mol-task'
+import { createTransforms, createColors } from './util/common';
+import { createElementSphereMesh, markElement, getElementRadius, getElementLoci } from './util/element';
+import { deepEqual } from 'mol-util';
+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 } from 'mol-model/loci';
+import { SizeTheme } from '../../../theme';
+import { createMeshValues, updateMeshValues, updateRenderableState, createRenderableState, DefaultMeshProps } from '../../util';
+
+export const DefaultElementSphereProps = {
+    ...DefaultMeshProps,
+    ...DefaultStructureProps,
+    sizeTheme: { name: 'physical', factor: 1 } as SizeTheme,
+    detail: 0,
+    unitKinds: [ Unit.Kind.Atomic, Unit.Kind.Spheres ] as Unit.Kind[]
+}
+export type ElementSphereProps = Partial<typeof DefaultElementSphereProps>
+
+export function ElementSphereVisual(): UnitsVisual<ElementSphereProps> {
+    let renderObject: MeshRenderObject
+    let currentProps: typeof DefaultElementSphereProps
+    let mesh: Mesh
+    let currentGroup: Unit.SymmetryGroup
+
+    return {
+        get renderObject () { return renderObject },
+        async create(ctx: RuntimeContext, group: Unit.SymmetryGroup, props: ElementSphereProps = {}) {
+            currentProps = Object.assign({}, DefaultElementSphereProps, props)
+            currentGroup = group
+
+            const { detail, colorTheme, sizeTheme, unitKinds } = { ...DefaultElementSphereProps, ...props }
+            const instanceCount = group.units.length
+            const elementCount = group.elements.length
+            const unit = group.units[0]
+
+            const radius = getElementRadius(unit, sizeTheme)
+            mesh = unitKinds.includes(unit.kind)
+                ? await createElementSphereMesh(ctx, unit, radius, detail, mesh)
+                : Mesh.createEmpty(mesh)
+
+            const transforms = createTransforms(group)
+            const color = createColors(group, elementCount, colorTheme)
+            const marker = createMarkers(instanceCount * elementCount)
+
+            const counts = { drawCount: mesh.triangleCount * 3, elementCount, instanceCount }
+
+            const values: MeshValues = {
+                ...getMeshData(mesh),
+                ...color,
+                ...marker,
+                aTransform: transforms,
+                elements: mesh.indexBuffer,
+                ...createMeshValues(currentProps, counts),
+            }
+            const state = createRenderableState(currentProps)
+
+            renderObject = createMeshRenderObject(values, state)
+        },
+        async update(ctx: RuntimeContext, props: ElementSphereProps) {
+            const newProps = Object.assign({}, currentProps, props)
+
+            if (!renderObject) return false
+
+            let updateColor = false
+
+            if (newProps.detail !== currentProps.detail) {
+                const unit = currentGroup.units[0]
+                const radius = getElementRadius(unit, newProps.sizeTheme)
+                mesh = await createElementSphereMesh(ctx, unit, radius, newProps.detail, mesh)
+                ValueCell.update(renderObject.values.drawCount, mesh.triangleCount * 3)
+                updateColor = true
+            }
+
+            if (!deepEqual(newProps.colorTheme, currentProps.colorTheme)) {
+                updateColor = true
+            }
+
+            if (updateColor) {
+                const elementCount = currentGroup.elements.length
+                if (ctx.shouldUpdate) await ctx.update('Computing sphere colors');
+                createColors(currentGroup, elementCount, newProps.colorTheme, renderObject.values)
+            }
+
+            updateMeshValues(renderObject.values, newProps)
+            updateRenderableState(renderObject.state, newProps)
+
+            currentProps = newProps
+            return true
+        },
+        getLoci(pickingId: PickingId) {
+            return getElementLoci(renderObject.id, currentGroup, pickingId)
+        },
+        mark(loci: Loci, action: MarkerAction) {
+            markElement(renderObject.values.tMarker, currentGroup, loci, action)
+        },
+        destroy() {
+            // TODO
+        }
+    }
+}

+ 158 - 0
src/mol-geo/representation/structure/visual/inter-unit-link-cylinder.ts

@@ -0,0 +1,158 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+import { ValueCell } from 'mol-util/value-cell'
+
+import { createMeshRenderObject, MeshRenderObject } from 'mol-gl/render-object'
+import { Link, Structure } from 'mol-model/structure';
+import { DefaultStructureProps, StructureVisual } from '..';
+import { RuntimeContext } from 'mol-task'
+import { LinkCylinderProps, DefaultLinkCylinderProps, createLinkCylinderMesh } from './util/link';
+import { MeshValues } from 'mol-gl/renderable';
+import { getMeshData } from '../../../util/mesh-data';
+import { Mesh } from '../../../shape/mesh';
+import { PickingId } from '../../../util/picking';
+import { Vec3 } from 'mol-math/linear-algebra';
+import { createUniformColor } from '../../../util/color-data';
+import { Loci, isEveryLoci, EmptyLoci } from 'mol-model/loci';
+import { MarkerAction, applyMarkerAction, createMarkers, MarkerData } from '../../../util/marker-data';
+import { SizeTheme } from '../../../theme';
+import { createIdentityTransform } from './util/common';
+import { updateMeshValues, updateRenderableState, createMeshValues, createRenderableState } from '../../util';
+// import { chainIdLinkColorData } from '../../../theme/structure/color/chain-id';
+
+async function createInterUnitLinkCylinderMesh(ctx: RuntimeContext, structure: Structure, props: LinkCylinderProps, mesh?: Mesh) {
+    const links = structure.links
+    const { bondCount, bonds } = links
+
+    if (!bondCount) return Mesh.createEmpty(mesh)
+
+    const builderProps = {
+        linkCount: bondCount,
+        referencePosition: (edgeIndex: number) => null, // TODO
+        position: (posA: Vec3, posB: Vec3, edgeIndex: number) => {
+            const b = bonds[edgeIndex]
+            const uA = b.unitA, uB = b.unitB
+            uA.conformation.position(uA.elements[b.indexA], posA)
+            uB.conformation.position(uB.elements[b.indexB], posB)
+        },
+        order: (edgeIndex: number) => bonds[edgeIndex].order,
+        flags: (edgeIndex: number) => bonds[edgeIndex].flag
+    }
+
+    return createLinkCylinderMesh(ctx, builderProps, props, mesh)
+}
+
+export const DefaultInterUnitLinkProps = {
+    ...DefaultStructureProps,
+    ...DefaultLinkCylinderProps,
+    sizeTheme: { name: 'physical', factor: 0.3 } as SizeTheme,
+}
+export type InterUnitLinkProps = Partial<typeof DefaultInterUnitLinkProps>
+
+export function InterUnitLinkVisual(): StructureVisual<InterUnitLinkProps> {
+    let renderObject: MeshRenderObject
+    let currentProps: typeof DefaultInterUnitLinkProps
+    let mesh: Mesh
+    let currentStructure: Structure
+
+    return {
+        get renderObject () { return renderObject },
+        async create(ctx: RuntimeContext, structure: Structure, props: InterUnitLinkProps = {}) {
+            currentProps = Object.assign({}, DefaultInterUnitLinkProps, props)
+            currentStructure = structure
+
+            const elementCount = structure.links.bondCount
+            const instanceCount = 1
+
+            mesh = await createInterUnitLinkCylinderMesh(ctx, structure, currentProps)
+
+            const transforms = createIdentityTransform()
+            const color = createUniformColor({ value: 0x999911 }) // TODO
+            const marker = createMarkers(instanceCount * elementCount)
+
+            const counts = { drawCount: mesh.triangleCount * 3, elementCount, instanceCount }
+
+            const values: MeshValues = {
+                ...getMeshData(mesh),
+                ...color,
+                ...marker,
+                aTransform: transforms,
+                elements: mesh.indexBuffer,
+                ...createMeshValues(currentProps, counts),
+            }
+            const state = createRenderableState(currentProps)
+
+            renderObject = createMeshRenderObject(values, state)
+        },
+        async update(ctx: RuntimeContext, props: InterUnitLinkProps) {
+            const newProps = Object.assign({}, currentProps, props)
+
+            if (!renderObject) return false
+
+            // TODO create in-place
+            if (currentProps.radialSegments !== newProps.radialSegments) return false
+
+            updateMeshValues(renderObject.values, newProps)
+            updateRenderableState(renderObject.state, newProps)
+
+            return false
+        },
+        getLoci(pickingId: PickingId) {
+            return getLinkLoci(pickingId, currentStructure, renderObject.id)
+        },
+        mark(loci: Loci, action: MarkerAction) {
+            markLink(loci, action, currentStructure, renderObject.values)
+        },
+        destroy() {
+            // TODO
+        }
+    }
+}
+
+function getLinkLoci(pickingId: PickingId, structure: Structure, id: number) {
+    const { objectId, elementId } = pickingId
+    if (id === objectId) {
+        const bond = structure.links.bonds[elementId]
+        return Link.Loci([{
+            aUnit: bond.unitA,
+            aIndex: bond.indexA,
+            bUnit: bond.unitB,
+            bIndex: bond.indexB
+        }])
+    }
+    return EmptyLoci
+}
+
+function markLink(loci: Loci, action: MarkerAction, structure: Structure, values: MarkerData) {
+    const tMarker = values.tMarker
+
+    const links = structure.links
+    const elementCount = links.bondCount
+    const instanceCount = 1
+
+    let changed = false
+    const array = tMarker.ref.value.array
+    if (isEveryLoci(loci)) {
+        applyMarkerAction(array, 0, elementCount * instanceCount, action)
+        changed = true
+    } else if (Link.isLoci(loci)) {
+        for (const b of loci.links) {
+            const _idx = structure.links.getBondIndex(b.aIndex, b.aUnit, b.bIndex, b.bUnit)
+            if (_idx !== -1) {
+                const idx = _idx
+                if (applyMarkerAction(array, idx, idx + 1, action) && !changed) {
+                    changed = true
+                }
+            }
+        }
+    } else {
+        return
+    }
+    if (changed) {
+        ValueCell.update(tMarker, tMarker.ref.value)
+    }
+}

+ 179 - 0
src/mol-geo/representation/structure/visual/intra-unit-link-cylinder.ts

@@ -0,0 +1,179 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ * @author David Sehnal <david.sehnal@gmail.com>
+ */
+
+import { ValueCell } from 'mol-util/value-cell'
+
+import { createMeshRenderObject, MeshRenderObject } from 'mol-gl/render-object'
+import { Unit, Link } from 'mol-model/structure';
+import { UnitsVisual, DefaultStructureProps } from '..';
+import { RuntimeContext } from 'mol-task'
+import { DefaultLinkCylinderProps, LinkCylinderProps, createLinkCylinderMesh } from './util/link';
+import { MeshValues } from 'mol-gl/renderable';
+import { getMeshData } from '../../../util/mesh-data';
+import { Mesh } from '../../../shape/mesh';
+import { PickingId } from '../../../util/picking';
+import { Vec3 } from 'mol-math/linear-algebra';
+// import { createUniformColor } from '../../../util/color-data';
+import { Loci, isEveryLoci, EmptyLoci } from 'mol-model/loci';
+import { MarkerAction, applyMarkerAction, createMarkers, MarkerData } from '../../../util/marker-data';
+import { SizeTheme } from '../../../theme';
+import { chainIdLinkColorData } from '../../../theme/structure/color/chain-id';
+import { createTransforms } from './util/common';
+import { createMeshValues, createRenderableState, updateMeshValues, updateRenderableState } from '../../util';
+
+async function createIntraUnitLinkCylinderMesh(ctx: RuntimeContext, unit: Unit, props: LinkCylinderProps, mesh?: Mesh) {
+    if (!Unit.isAtomic(unit)) return Mesh.createEmpty(mesh)
+
+    const elements = unit.elements;
+    const links = unit.links
+    const { edgeCount, a, b, edgeProps, offset } = links
+    const { order: _order, flags: _flags } = edgeProps
+
+    if (!edgeCount) return Mesh.createEmpty(mesh)
+
+    const vRef = Vec3.zero()
+    const pos = unit.conformation.invariantPosition
+
+    const builderProps = {
+        linkCount: edgeCount * 2,
+        referencePosition: (edgeIndex: number) => {
+            let aI = a[edgeIndex], bI = b[edgeIndex];
+            if (aI > bI) [aI, bI] = [bI, aI]
+            for (let i = offset[aI], il = offset[aI + 1]; i < il; ++i) {
+                if (b[i] !== bI) return pos(elements[b[i]], vRef)
+            }
+            for (let i = offset[bI], il = offset[bI + 1]; i < il; ++i) {
+                if (a[i] !== aI) return pos(elements[a[i]], vRef)
+            }
+            return null
+        },
+        position: (posA: Vec3, posB: Vec3, edgeIndex: number) => {
+            pos(elements[a[edgeIndex]], posA)
+            pos(elements[b[edgeIndex]], posB)
+        },
+        order: (edgeIndex: number) => _order[edgeIndex],
+        flags: (edgeIndex: number) => _flags[edgeIndex]
+    }
+
+    return createLinkCylinderMesh(ctx, builderProps, props, mesh)
+}
+
+export const DefaultIntraUnitLinkProps = {
+    ...DefaultStructureProps,
+    ...DefaultLinkCylinderProps,
+    sizeTheme: { name: 'physical', factor: 0.3 } as SizeTheme,
+}
+export type IntraUnitLinkProps = Partial<typeof DefaultIntraUnitLinkProps>
+
+export function IntraUnitLinkVisual(): UnitsVisual<IntraUnitLinkProps> {
+    let renderObject: MeshRenderObject
+    let currentProps: typeof DefaultIntraUnitLinkProps
+    let mesh: Mesh
+    let currentGroup: Unit.SymmetryGroup
+
+    return {
+        get renderObject () { return renderObject },
+        async create(ctx: RuntimeContext, group: Unit.SymmetryGroup, props: IntraUnitLinkProps = {}) {
+            currentProps = Object.assign({}, DefaultIntraUnitLinkProps, props)
+            currentGroup = group
+
+            const unit = group.units[0]
+            const elementCount = Unit.isAtomic(unit) ? unit.links.edgeCount * 2 : 0
+            const instanceCount = group.units.length
+
+            mesh = await createIntraUnitLinkCylinderMesh(ctx, unit, currentProps)
+
+            const transforms = createTransforms(group)
+            const color = chainIdLinkColorData({ group, elementCount }) // TODO
+            const marker = createMarkers(instanceCount * elementCount)
+
+            const counts = { drawCount: mesh.triangleCount * 3, elementCount, instanceCount }
+
+            const values: MeshValues = {
+                ...getMeshData(mesh),
+                ...color,
+                ...marker,
+                aTransform: transforms,
+                elements: mesh.indexBuffer,
+                ...createMeshValues(currentProps, counts),
+            }
+            const state = createRenderableState(currentProps)
+
+            renderObject = createMeshRenderObject(values, state)
+        },
+        async update(ctx: RuntimeContext, props: IntraUnitLinkProps) {
+            const newProps = Object.assign({}, currentProps, props)
+
+            if (!renderObject) return false
+
+            // TODO create in-place
+            if (currentProps.radialSegments !== newProps.radialSegments) return false
+
+            updateMeshValues(renderObject.values, newProps)
+            updateRenderableState(renderObject.state, newProps)
+
+            return true
+        },
+        getLoci(pickingId: PickingId) {
+            return getLinkLoci(pickingId, currentGroup, renderObject.id)
+        },
+        mark(loci: Loci, action: MarkerAction) {
+            markLink(loci, action, currentGroup, renderObject.values)
+        },
+        destroy() {
+            // TODO
+        }
+    }
+}
+
+function getLinkLoci(pickingId: PickingId, group: Unit.SymmetryGroup, id: number) {
+    const { objectId, instanceId, elementId } = pickingId
+    const unit = group.units[instanceId]
+    if (id === objectId && Unit.isAtomic(unit)) {
+        return Link.Loci([{
+            aUnit: unit,
+            aIndex: unit.links.a[elementId],
+            bUnit: unit,
+            bIndex: unit.links.b[elementId]
+        }])
+    }
+    return EmptyLoci
+}
+
+function markLink(loci: Loci, action: MarkerAction, group: Unit.SymmetryGroup, values: MarkerData) {
+    const tMarker = values.tMarker
+    const unit = group.units[0]
+    if (!Unit.isAtomic(unit)) return
+
+    const elementCount = unit.links.edgeCount * 2
+    const instanceCount = group.units.length
+
+    let changed = false
+    const array = tMarker.ref.value.array
+    if (isEveryLoci(loci)) {
+        applyMarkerAction(array, 0, elementCount * instanceCount, action)
+        changed = true
+    } else if (Link.isLoci(loci)) {
+        for (const b of loci.links) {
+            const unitIdx = Unit.findUnitById(b.aUnit.id, group.units)
+            if (unitIdx !== -1) {
+                const _idx = unit.links.getDirectedEdgeIndex(b.aIndex, b.bIndex)
+                if (_idx !== -1) {
+                    const idx = _idx
+                    if (applyMarkerAction(array, idx, idx + 1, action) && !changed) {
+                        changed = true
+                    }
+                }
+            }
+        }
+    } else {
+        return
+    }
+    if (changed) {
+        ValueCell.update(tMarker, tMarker.ref.value)
+    }
+}

+ 206 - 0
src/mol-geo/representation/structure/visual/nucleotide-block-mesh.ts

@@ -0,0 +1,206 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+import { ValueCell } from 'mol-util/value-cell'
+
+import { createMeshRenderObject, MeshRenderObject } from 'mol-gl/render-object'
+import { Unit } from 'mol-model/structure';
+import { DefaultStructureProps, UnitsVisual } from '..';
+import { RuntimeContext } from 'mol-task'
+import { createTransforms, createColors } from './util/common';
+import { deepEqual } from 'mol-util';
+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 } from 'mol-model/loci';
+import { SizeTheme } from '../../../theme';
+import { createMeshValues, updateMeshValues, updateRenderableState, createRenderableState, DefaultMeshProps } from '../../util';
+import { MeshBuilder } from '../../../shape/mesh-builder';
+import { getElementLoci, markElement } from './util/element';
+import { Vec3, Mat4 } from 'mol-math/linear-algebra';
+import { Segmentation, SortedArray } from 'mol-data/int';
+import { MoleculeType, isNucleic, isPurinBase, isPyrimidineBase } from 'mol-model/structure/model/types';
+import { getElementIndexForAtomId, getElementIndexForAtomRole } from 'mol-model/structure/util';
+
+const p1 = Vec3.zero()
+const p2 = Vec3.zero()
+const p3 = Vec3.zero()
+const p4 = Vec3.zero()
+const p5 = Vec3.zero()
+const p6 = Vec3.zero()
+const v12 = Vec3.zero()
+const v34 = Vec3.zero()
+const vC = Vec3.zero()
+const center = Vec3.zero()
+const t = Mat4.identity()
+const sVec = Vec3.zero()
+
+async function createNucleotideBlockMesh(ctx: RuntimeContext, unit: Unit, mesh?: Mesh) {
+    if (!Unit.isAtomic(unit)) return Mesh.createEmpty(mesh)
+
+    const builder = MeshBuilder.create(256, 128, mesh)
+
+    const { elements, model } = unit
+    const { chemicalComponentMap, modifiedResidues } = model.properties
+    const { chainAtomSegments, residueAtomSegments, residues } = model.atomicHierarchy
+    const { label_comp_id } = residues
+    const pos = unit.conformation.invariantPosition
+
+    const chainIt = Segmentation.transientSegments(chainAtomSegments, elements)
+    const residueIt = Segmentation.transientSegments(residueAtomSegments, elements)
+
+    let i = 0
+    while (chainIt.hasNext) {
+        residueIt.setSegment(chainIt.move());
+
+        while (residueIt.hasNext) {
+            const { index: residueIndex } = residueIt.move();
+            const cc = chemicalComponentMap.get(label_comp_id.value(residueIndex))
+            const moleculeType = cc ? cc.moleculeType : MoleculeType.unknown
+
+            if (isNucleic(moleculeType)) {
+                let compId = label_comp_id.value(residueIndex)
+                const parentId = modifiedResidues.parentId.get(compId)
+                if (parentId !== undefined) compId = parentId
+                let idx1 = -1, idx2 = -1, idx3 = -1, idx4 = -1, idx5 = -1, idx6 = -1
+                let width = 4.5, height = 4.5, depth = 0.5
+
+                if (isPurinBase(compId)) {
+                    height = 4.5
+                    idx1 = getElementIndexForAtomId(model, residueIndex, 'N1')
+                    idx2 = getElementIndexForAtomId(model, residueIndex, 'C4')
+                    idx3 = getElementIndexForAtomId(model, residueIndex, 'C6')
+                    idx4 = getElementIndexForAtomId(model, residueIndex, 'C2')
+                    idx5 = getElementIndexForAtomId(model, residueIndex, 'N9')
+                    idx6 = getElementIndexForAtomRole(model, residueIndex, 'trace')
+                } else if (isPyrimidineBase(compId)) {
+                    height = 3.0
+                    idx1 = getElementIndexForAtomId(model, residueIndex, 'N3')
+                    idx2 = getElementIndexForAtomId(model, residueIndex, 'C6')
+                    idx3 = getElementIndexForAtomId(model, residueIndex, 'C4')
+                    idx4 = getElementIndexForAtomId(model, residueIndex, 'C2')
+                    idx5 = getElementIndexForAtomId(model, residueIndex, 'N1')
+                    idx6 = getElementIndexForAtomRole(model, residueIndex, 'trace')
+                }
+
+                if (idx1 !== -1 && idx2 !== -1 && idx3 !== -1 && idx4 !== -1 && idx5 !== -1 && idx6 !== -1) {
+                    pos(idx1, p1); pos(idx2, p2); pos(idx3, p3); pos(idx4, p4); pos(idx5, p5); pos(idx6, p6)
+                    Vec3.normalize(v12, Vec3.sub(v12, p2, p1))
+                    Vec3.normalize(v34, Vec3.sub(v34, p4, p3))
+                    Vec3.normalize(vC, Vec3.cross(vC, v12, v34))
+                    Mat4.targetTo(t, p1, p2, vC)
+                    Vec3.scaleAndAdd(center, p1, v12, height / 2 - 0.2)
+                    Mat4.scale(t, t, Vec3.set(sVec, width, depth, height))
+                    Mat4.setTranslation(t, center)
+                    builder.setId(SortedArray.findPredecessorIndex(elements, idx6))
+                    builder.addBox(t)
+                    builder.addCylinder(p5, p6, 1, { radiusTop: 0.2, radiusBottom: 0.2 })
+                }
+            }
+
+            if (i % 10000 === 0 && ctx.shouldUpdate) {
+                await ctx.update({ message: 'Gap mesh', current: i });
+            }
+            ++i
+        }
+    }
+
+    return builder.getMesh()
+}
+
+export const DefaultNucleotideBlockProps = {
+    ...DefaultMeshProps,
+    ...DefaultStructureProps,
+    sizeTheme: { name: 'physical', factor: 1 } as SizeTheme,
+    detail: 0,
+    unitKinds: [ Unit.Kind.Atomic, Unit.Kind.Spheres ] as Unit.Kind[]
+}
+export type NucleotideBlockProps = Partial<typeof DefaultNucleotideBlockProps>
+
+export function NucleotideBlockVisual(): UnitsVisual<NucleotideBlockProps> {
+    let renderObject: MeshRenderObject
+    let currentProps: typeof DefaultNucleotideBlockProps
+    let mesh: Mesh
+    let currentGroup: Unit.SymmetryGroup
+
+    return {
+        get renderObject () { return renderObject },
+        async create(ctx: RuntimeContext, group: Unit.SymmetryGroup, props: NucleotideBlockProps = {}) {
+            currentProps = Object.assign({}, DefaultNucleotideBlockProps, props)
+            currentGroup = group
+
+            const { colorTheme, unitKinds } = { ...DefaultNucleotideBlockProps, ...props }
+            const instanceCount = group.units.length
+            const elementCount = group.elements.length
+            const unit = group.units[0]
+
+            mesh = unitKinds.includes(unit.kind)
+                ? await createNucleotideBlockMesh(ctx, unit, mesh)
+                : Mesh.createEmpty(mesh)
+            // console.log(mesh)
+
+            const transforms = createTransforms(group)
+            const color = createColors(group, elementCount, colorTheme)
+            const marker = createMarkers(instanceCount * elementCount)
+
+            const counts = { drawCount: mesh.triangleCount * 3, elementCount, instanceCount }
+
+            const values: MeshValues = {
+                ...getMeshData(mesh),
+                ...color,
+                ...marker,
+                aTransform: transforms,
+                elements: mesh.indexBuffer,
+                ...createMeshValues(currentProps, counts),
+                aColor: ValueCell.create(new Float32Array(mesh.vertexCount * 3))
+            }
+            const state = createRenderableState(currentProps)
+
+            renderObject = createMeshRenderObject(values, state)
+        },
+        async update(ctx: RuntimeContext, props: NucleotideBlockProps) {
+            const newProps = Object.assign({}, currentProps, props)
+
+            if (!renderObject) return false
+
+            let updateColor = false
+
+            if (newProps.detail !== currentProps.detail) {
+                const unit = currentGroup.units[0]
+                mesh = await createNucleotideBlockMesh(ctx, unit, mesh)
+                ValueCell.update(renderObject.values.drawCount, mesh.triangleCount * 3)
+                updateColor = true
+            }
+
+            if (!deepEqual(newProps.colorTheme, currentProps.colorTheme)) {
+                updateColor = true
+            }
+
+            if (updateColor) {
+                const elementCount = currentGroup.elements.length
+                if (ctx.shouldUpdate) await ctx.update('Computing nucleotide block colors');
+                createColors(currentGroup, elementCount, newProps.colorTheme, renderObject.values)
+            }
+
+            updateMeshValues(renderObject.values, newProps)
+            updateRenderableState(renderObject.state, newProps)
+
+            currentProps = newProps
+            return true
+        },
+        getLoci(pickingId: PickingId) {
+            return getElementLoci(renderObject.id, currentGroup, pickingId)
+        },
+        mark(loci: Loci, action: MarkerAction) {
+            markElement(renderObject.values.tMarker, currentGroup, loci, action)
+        },
+        destroy() {
+            // TODO
+        }
+    }
+}

+ 152 - 0
src/mol-geo/representation/structure/visual/polymer-backbone-cylinder.ts

@@ -0,0 +1,152 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+import { ValueCell } from 'mol-util/value-cell'
+
+import { createMeshRenderObject, MeshRenderObject } from 'mol-gl/render-object'
+import { Unit } from 'mol-model/structure';
+import { DefaultStructureProps, UnitsVisual } from '..';
+import { RuntimeContext } from 'mol-task'
+import { createTransforms, createColors } from './util/common';
+import { deepEqual } from 'mol-util';
+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 } from 'mol-model/loci';
+import { SizeTheme } from '../../../theme';
+import { createMeshValues, updateMeshValues, updateRenderableState, createRenderableState, DefaultMeshProps } from '../../util';
+import { MeshBuilder } from '../../../shape/mesh-builder';
+import { getPolymerElementCount, PolymerBackboneIterator } from './util/polymer';
+import { getElementLoci, markElement } from './util/element';
+import { Vec3 } from 'mol-math/linear-algebra';
+
+async function createPolymerBackboneCylinderMesh(ctx: RuntimeContext, unit: Unit, mesh?: Mesh) {
+    const polymerElementCount = getPolymerElementCount(unit)
+    if (!polymerElementCount) return Mesh.createEmpty(mesh)
+    console.log('polymerElementCount backbone', polymerElementCount)
+
+    // TODO better vertex count estimates
+    const builder = MeshBuilder.create(polymerElementCount * 30, polymerElementCount * 30 / 2, mesh)
+
+    const { elements } = unit
+    const pos = unit.conformation.invariantPosition
+    const pA = Vec3.zero()
+    const pB = Vec3.zero()
+
+    let i = 0
+    const polymerBackboneIt = PolymerBackboneIterator(unit)
+    while (polymerBackboneIt.hasNext) {
+        // TODO size theme
+        const { centerA, centerB } = polymerBackboneIt.move()
+        pos(elements[centerA.element], pA)
+        pos(elements[centerB.element], pB)
+        builder.setId(centerA.element)
+        builder.addCylinder(pA, pB, 0.5, { radiusTop: 0.2, radiusBottom: 0.2 })
+        builder.setId(centerB.element)
+        builder.addCylinder(pB, pA, 0.5, { radiusTop: 0.2, radiusBottom: 0.2 })
+
+        if (i % 10000 === 0 && ctx.shouldUpdate) {
+            await ctx.update({ message: 'Backbone mesh', current: i, max: polymerElementCount });
+        }
+        ++i
+    }
+
+    return builder.getMesh()
+}
+
+export const DefaultPolymerBackboneProps = {
+    ...DefaultMeshProps,
+    ...DefaultStructureProps,
+    sizeTheme: { name: 'physical', factor: 1 } as SizeTheme,
+    detail: 0,
+    unitKinds: [ Unit.Kind.Atomic, Unit.Kind.Spheres ] as Unit.Kind[]
+}
+export type PolymerBackboneProps = Partial<typeof DefaultPolymerBackboneProps>
+
+export function PolymerBackboneVisual(): UnitsVisual<PolymerBackboneProps> {
+    let renderObject: MeshRenderObject
+    let currentProps: typeof DefaultPolymerBackboneProps
+    let mesh: Mesh
+    let currentGroup: Unit.SymmetryGroup
+
+    return {
+        get renderObject () { return renderObject },
+        async create(ctx: RuntimeContext, group: Unit.SymmetryGroup, props: PolymerBackboneProps = {}) {
+            currentProps = Object.assign({}, DefaultPolymerBackboneProps, props)
+            currentGroup = group
+
+            const { colorTheme, unitKinds } = { ...DefaultPolymerBackboneProps, ...props }
+            const instanceCount = group.units.length
+            const elementCount = group.elements.length
+            const unit = group.units[0]
+
+            mesh = unitKinds.includes(unit.kind)
+                ? await createPolymerBackboneCylinderMesh(ctx, unit, mesh)
+                : Mesh.createEmpty(mesh)
+            // console.log(mesh)
+
+            const transforms = createTransforms(group)
+            const color = createColors(group, elementCount, colorTheme)
+            const marker = createMarkers(instanceCount * elementCount)
+
+            const counts = { drawCount: mesh.triangleCount * 3, elementCount, instanceCount }
+
+            const values: MeshValues = {
+                ...getMeshData(mesh),
+                ...color,
+                ...marker,
+                aTransform: transforms,
+                elements: mesh.indexBuffer,
+                ...createMeshValues(currentProps, counts),
+                aColor: ValueCell.create(new Float32Array(mesh.vertexCount * 3))
+            }
+            const state = createRenderableState(currentProps)
+
+            renderObject = createMeshRenderObject(values, state)
+        },
+        async update(ctx: RuntimeContext, props: PolymerBackboneProps) {
+            const newProps = Object.assign({}, currentProps, props)
+
+            if (!renderObject) return false
+
+            let updateColor = false
+
+            if (newProps.detail !== currentProps.detail) {
+                const unit = currentGroup.units[0]
+                mesh = await createPolymerBackboneCylinderMesh(ctx, unit, mesh)
+                ValueCell.update(renderObject.values.drawCount, mesh.triangleCount * 3)
+                updateColor = true
+            }
+
+            if (!deepEqual(newProps.colorTheme, currentProps.colorTheme)) {
+                updateColor = true
+            }
+
+            if (updateColor) {
+                const elementCount = currentGroup.elements.length
+                if (ctx.shouldUpdate) await ctx.update('Computing trace colors');
+                createColors(currentGroup, elementCount, newProps.colorTheme, renderObject.values)
+            }
+
+            updateMeshValues(renderObject.values, newProps)
+            updateRenderableState(renderObject.state, newProps)
+
+            currentProps = newProps
+            return true
+        },
+        getLoci(pickingId: PickingId) {
+            return getElementLoci(renderObject.id, currentGroup, pickingId)
+        },
+        mark(loci: Loci, action: MarkerAction) {
+            markElement(renderObject.values.tMarker, currentGroup, loci, action)
+        },
+        destroy() {
+            // TODO
+        }
+    }
+}

+ 0 - 0
src/mol-geo/representation/structure/visual/polymer-backbone-sphere.ts


+ 184 - 0
src/mol-geo/representation/structure/visual/polymer-direction-wedge.ts

@@ -0,0 +1,184 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+import { ValueCell } from 'mol-util/value-cell'
+
+import { createMeshRenderObject, MeshRenderObject } from 'mol-gl/render-object'
+import { Unit, StructureElement } from 'mol-model/structure';
+import { DefaultStructureProps, UnitsVisual } from '..';
+import { RuntimeContext } from 'mol-task'
+import { createTransforms, createColors } from './util/common';
+import { markElement } from './util/element';
+import { deepEqual } from 'mol-util';
+import { MeshValues } from 'mol-gl/renderable';
+import { getMeshData } from '../../../util/mesh-data';
+import { Mesh } from '../../../shape/mesh';
+import { PickingId } from '../../../util/picking';
+import { OrderedSet } from 'mol-data/int';
+import { createMarkers, MarkerAction } from '../../../util/marker-data';
+import { Loci, EmptyLoci } from 'mol-model/loci';
+import { SizeTheme } from '../../../theme';
+import { createMeshValues, updateMeshValues, updateRenderableState, createRenderableState, DefaultMeshProps } from '../../util';
+import { MeshBuilder } from '../../../shape/mesh-builder';
+import { getPolymerElementCount, PolymerTraceIterator, createCurveSegmentState, interpolateCurveSegment } from './util/polymer';
+import { Vec3, Mat4 } from 'mol-math/linear-algebra';
+import { SecondaryStructureType, MoleculeType } from 'mol-model/structure/model/types';
+
+const t = Mat4.identity()
+const sVec = Vec3.zero()
+const n0 = Vec3.zero()
+const n1 = Vec3.zero()
+const upVec = Vec3.zero()
+
+async function createPolymerDirectionWedgeMesh(ctx: RuntimeContext, unit: Unit, mesh?: Mesh) {
+    const polymerElementCount = getPolymerElementCount(unit)
+    console.log('polymerElementCount direction', polymerElementCount)
+    if (!polymerElementCount) return Mesh.createEmpty(mesh)
+
+    // TODO better vertex count estimates
+    const builder = MeshBuilder.create(polymerElementCount * 30, polymerElementCount * 30 / 2, mesh)
+    const linearSegments = 1
+
+    const state = createCurveSegmentState(linearSegments)
+    const { normalVectors, binormalVectors } = state
+
+    let i = 0
+    const polymerTraceIt = PolymerTraceIterator(unit)
+    while (polymerTraceIt.hasNext) {
+        const v = polymerTraceIt.move()
+        builder.setId(v.center.element)
+
+        const isNucleic = v.moleculeType === MoleculeType.DNA || v.moleculeType === MoleculeType.RNA
+        const isSheet = SecondaryStructureType.is(v.secStrucType, SecondaryStructureType.Flag.Beta)
+        const tension = (isNucleic || isSheet) ? 0.5 : 0.9
+
+        interpolateCurveSegment(state, v, tension)
+
+        if ((isSheet && !v.secStrucChange) || !isSheet) {
+
+            let width = 0.5, height = 1.2, depth = 0.6
+            if (isNucleic) {
+                Vec3.fromArray(n0, binormalVectors, 0)
+                Vec3.fromArray(n1, binormalVectors, 3)
+                Vec3.normalize(upVec, Vec3.add(upVec, n0, n1))
+                depth = 0.9
+            } else {
+                Vec3.fromArray(n0, normalVectors, 0)
+                Vec3.fromArray(n1, normalVectors, 3)
+                Vec3.normalize(upVec, Vec3.add(upVec, n0, n1))
+            }
+
+            Mat4.targetTo(t, v.p3, v.p1, upVec)
+            Mat4.mul(t, t, Mat4.rotY90)
+            Mat4.scale(t, t, Vec3.set(sVec, height, width, depth))
+            Mat4.setTranslation(t, v.p2)
+            builder.addWedge(t)
+        }
+
+        if (i % 10000 === 0 && ctx.shouldUpdate) {
+            await ctx.update({ message: 'Polymer direction mesh', current: i, max: polymerElementCount });
+        }
+        ++i
+    }
+
+    return builder.getMesh()
+}
+
+export const DefaultPolymerDirectionProps = {
+    ...DefaultMeshProps,
+    ...DefaultStructureProps,
+    sizeTheme: { name: 'physical', factor: 1 } as SizeTheme,
+    detail: 0,
+    unitKinds: [ Unit.Kind.Atomic, Unit.Kind.Spheres ] as Unit.Kind[]
+}
+export type PolymerDirectionProps = Partial<typeof DefaultPolymerDirectionProps>
+
+export function PolymerDirectionVisual(): UnitsVisual<PolymerDirectionProps> {
+    let renderObject: MeshRenderObject
+    let currentProps: typeof DefaultPolymerDirectionProps
+    let mesh: Mesh
+    let currentGroup: Unit.SymmetryGroup
+
+    return {
+        get renderObject () { return renderObject },
+        async create(ctx: RuntimeContext, group: Unit.SymmetryGroup, props: PolymerDirectionProps = {}) {
+            currentProps = Object.assign({}, DefaultPolymerDirectionProps, props)
+            currentGroup = group
+
+            const { colorTheme, unitKinds } = { ...DefaultPolymerDirectionProps, ...props }
+            const instanceCount = group.units.length
+            const elementCount = group.elements.length
+            const unit = group.units[0]
+
+            mesh = unitKinds.includes(unit.kind)
+                ? await createPolymerDirectionWedgeMesh(ctx, unit, mesh)
+                : Mesh.createEmpty(mesh)
+
+            const transforms = createTransforms(group)
+            const color = createColors(group, elementCount, colorTheme)
+            const marker = createMarkers(instanceCount * elementCount)
+
+            const counts = { drawCount: mesh.triangleCount * 3, elementCount, instanceCount }
+
+            const values: MeshValues = {
+                ...getMeshData(mesh),
+                ...color,
+                ...marker,
+                aTransform: transforms,
+                elements: mesh.indexBuffer,
+                ...createMeshValues(currentProps, counts),
+            }
+            const state = createRenderableState(currentProps)
+
+            renderObject = createMeshRenderObject(values, state)
+        },
+        async update(ctx: RuntimeContext, props: PolymerDirectionProps) {
+            const newProps = Object.assign({}, currentProps, props)
+
+            if (!renderObject) return false
+
+            let updateColor = false
+
+            if (newProps.detail !== currentProps.detail) {
+                const unit = currentGroup.units[0]
+                mesh = await createPolymerDirectionWedgeMesh(ctx, unit, mesh)
+                ValueCell.update(renderObject.values.drawCount, mesh.triangleCount * 3)
+                updateColor = true
+            }
+
+            if (!deepEqual(newProps.colorTheme, currentProps.colorTheme)) {
+                updateColor = true
+            }
+
+            if (updateColor) {
+                const elementCount = currentGroup.elements.length
+                if (ctx.shouldUpdate) await ctx.update('Computing direction colors');
+                createColors(currentGroup, elementCount, newProps.colorTheme, renderObject.values)
+            }
+
+            updateMeshValues(renderObject.values, newProps)
+            updateRenderableState(renderObject.state, newProps)
+
+            currentProps = newProps
+            return true
+        },
+        getLoci(pickingId: PickingId) {
+            const { objectId, instanceId, elementId } = pickingId
+            if (renderObject.id === objectId) {
+                const unit = currentGroup.units[instanceId]
+                const indices = OrderedSet.ofSingleton(elementId as StructureElement.UnitIndex);
+                return StructureElement.Loci([{ unit, indices }])
+            }
+            return EmptyLoci
+        },
+        mark(loci: Loci, action: MarkerAction) {
+            markElement(renderObject.values.tMarker, currentGroup, loci, action)
+        },
+        destroy() {
+            // TODO
+        }
+    }
+}

+ 158 - 0
src/mol-geo/representation/structure/visual/polymer-gap-cylinder.ts

@@ -0,0 +1,158 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+import { ValueCell } from 'mol-util/value-cell'
+
+import { createMeshRenderObject, MeshRenderObject } from 'mol-gl/render-object'
+import { Unit } from 'mol-model/structure';
+import { DefaultStructureProps, UnitsVisual } from '..';
+import { RuntimeContext } from 'mol-task'
+import { createTransforms, createColors } from './util/common';
+import { deepEqual } from 'mol-util';
+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 } from 'mol-model/loci';
+import { SizeTheme } from '../../../theme';
+import { createMeshValues, updateMeshValues, updateRenderableState, createRenderableState, DefaultMeshProps } from '../../util';
+import { MeshBuilder } from '../../../shape/mesh-builder';
+import { getPolymerGapCount, PolymerGapIterator } from './util/polymer';
+import { getElementLoci, markElement } from './util/element';
+import { Vec3 } from 'mol-math/linear-algebra';
+
+async function createPolymerGapCylinderMesh(ctx: RuntimeContext, unit: Unit, mesh?: Mesh) {
+    const polymerGapCount = getPolymerGapCount(unit)
+    if (!polymerGapCount) return Mesh.createEmpty(mesh)
+    console.log('polymerGapCount', polymerGapCount)
+
+    // TODO better vertex count estimates
+    const builder = MeshBuilder.create(polymerGapCount * 30, polymerGapCount * 30 / 2, mesh)
+
+    const { elements } = unit
+    const pos = unit.conformation.invariantPosition
+    const pA = Vec3.zero()
+    const pB = Vec3.zero()
+
+    let i = 0
+    const polymerGapIt = PolymerGapIterator(unit)
+    while (polymerGapIt.hasNext) {
+        // TODO size theme
+        const { centerA, centerB } = polymerGapIt.move()
+        if (centerA.element === centerB.element) {
+            builder.setId(centerA.element)
+            pos(elements[centerA.element], pA)
+            builder.addSphere(pA, 0.6, 0)
+        } else {
+            pos(elements[centerA.element], pA)
+            pos(elements[centerB.element], pB)
+            builder.setId(centerA.element)
+            builder.addFixedCountDashedCylinder(pA, pB, 0.5, 10, { radiusTop: 0.2, radiusBottom: 0.2 })
+            builder.setId(centerB.element)
+            builder.addFixedCountDashedCylinder(pB, pA, 0.5, 10, { radiusTop: 0.2, radiusBottom: 0.2 })
+        }
+
+        if (i % 10000 === 0 && ctx.shouldUpdate) {
+            await ctx.update({ message: 'Gap mesh', current: i, max: polymerGapCount });
+        }
+        ++i
+    }
+
+    return builder.getMesh()
+}
+
+export const DefaultPolymerGapProps = {
+    ...DefaultMeshProps,
+    ...DefaultStructureProps,
+    sizeTheme: { name: 'physical', factor: 1 } as SizeTheme,
+    detail: 0,
+    unitKinds: [ Unit.Kind.Atomic, Unit.Kind.Spheres ] as Unit.Kind[]
+}
+export type PolymerGapProps = Partial<typeof DefaultPolymerGapProps>
+
+export function PolymerGapVisual(): UnitsVisual<PolymerGapProps> {
+    let renderObject: MeshRenderObject
+    let currentProps: typeof DefaultPolymerGapProps
+    let mesh: Mesh
+    let currentGroup: Unit.SymmetryGroup
+
+    return {
+        get renderObject () { return renderObject },
+        async create(ctx: RuntimeContext, group: Unit.SymmetryGroup, props: PolymerGapProps = {}) {
+            currentProps = Object.assign({}, DefaultPolymerGapProps, props)
+            currentGroup = group
+
+            const { colorTheme, unitKinds } = { ...DefaultPolymerGapProps, ...props }
+            const instanceCount = group.units.length
+            const elementCount = group.elements.length
+            const unit = group.units[0]
+
+            mesh = unitKinds.includes(unit.kind)
+                ? await createPolymerGapCylinderMesh(ctx, unit, mesh)
+                : Mesh.createEmpty(mesh)
+            // console.log(mesh)
+
+            const transforms = createTransforms(group)
+            const color = createColors(group, elementCount, colorTheme)
+            const marker = createMarkers(instanceCount * elementCount)
+
+            const counts = { drawCount: mesh.triangleCount * 3, elementCount, instanceCount }
+
+            const values: MeshValues = {
+                ...getMeshData(mesh),
+                ...color,
+                ...marker,
+                aTransform: transforms,
+                elements: mesh.indexBuffer,
+                ...createMeshValues(currentProps, counts),
+                aColor: ValueCell.create(new Float32Array(mesh.vertexCount * 3))
+            }
+            const state = createRenderableState(currentProps)
+
+            renderObject = createMeshRenderObject(values, state)
+        },
+        async update(ctx: RuntimeContext, props: PolymerGapProps) {
+            const newProps = Object.assign({}, currentProps, props)
+
+            if (!renderObject) return false
+
+            let updateColor = false
+
+            if (newProps.detail !== currentProps.detail) {
+                const unit = currentGroup.units[0]
+                mesh = await createPolymerGapCylinderMesh(ctx, unit, mesh)
+                ValueCell.update(renderObject.values.drawCount, mesh.triangleCount * 3)
+                updateColor = true
+            }
+
+            if (!deepEqual(newProps.colorTheme, currentProps.colorTheme)) {
+                updateColor = true
+            }
+
+            if (updateColor) {
+                const elementCount = currentGroup.elements.length
+                if (ctx.shouldUpdate) await ctx.update('Computing trace colors');
+                createColors(currentGroup, elementCount, newProps.colorTheme, renderObject.values)
+            }
+
+            updateMeshValues(renderObject.values, newProps)
+            updateRenderableState(renderObject.state, newProps)
+
+            currentProps = newProps
+            return true
+        },
+        getLoci(pickingId: PickingId) {
+            return getElementLoci(renderObject.id, currentGroup, pickingId)
+        },
+        mark(loci: Loci, action: MarkerAction) {
+            markElement(renderObject.values.tMarker, currentGroup, loci, action)
+        },
+        destroy() {
+            // TODO
+        }
+    }
+}

+ 177 - 0
src/mol-geo/representation/structure/visual/polymer-trace-mesh.ts

@@ -0,0 +1,177 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+import { ValueCell } from 'mol-util/value-cell'
+
+import { createMeshRenderObject, MeshRenderObject } from 'mol-gl/render-object'
+import { Unit, StructureElement } from 'mol-model/structure';
+import { DefaultStructureProps, UnitsVisual } from '..';
+import { RuntimeContext } from 'mol-task'
+import { createTransforms, createColors } from './util/common';
+import { markElement } from './util/element';
+import { deepEqual } from 'mol-util';
+import { MeshValues } from 'mol-gl/renderable';
+import { getMeshData } from '../../../util/mesh-data';
+import { Mesh } from '../../../shape/mesh';
+import { PickingId } from '../../../util/picking';
+import { OrderedSet } from 'mol-data/int';
+import { createMarkers, MarkerAction } from '../../../util/marker-data';
+import { Loci, EmptyLoci } from 'mol-model/loci';
+import { SizeTheme } from '../../../theme';
+import { createMeshValues, updateMeshValues, updateRenderableState, createRenderableState, DefaultMeshProps } from '../../util';
+import { MeshBuilder } from '../../../shape/mesh-builder';
+import { getPolymerElementCount, PolymerTraceIterator, createCurveSegmentState, interpolateCurveSegment } from './util/polymer';
+import { SecondaryStructureType, MoleculeType } from 'mol-model/structure/model/types';
+
+// TODO handle polymer ends properly
+
+async function createPolymerTraceMesh(ctx: RuntimeContext, unit: Unit, mesh?: Mesh) {
+    const polymerElementCount = getPolymerElementCount(unit)
+    console.log('polymerElementCount trace', polymerElementCount)
+    if (!polymerElementCount) return Mesh.createEmpty(mesh)
+
+    // TODO better vertex count estimates
+    const builder = MeshBuilder.create(polymerElementCount * 30, polymerElementCount * 30 / 2, mesh)
+    const linearSegments = 8
+    const radialSegments = 12
+
+    const state = createCurveSegmentState(linearSegments)
+    const { curvePoints, normalVectors, binormalVectors } = state
+
+    let i = 0
+    const polymerTraceIt = PolymerTraceIterator(unit)
+    while (polymerTraceIt.hasNext) {
+        const v = polymerTraceIt.move()
+        builder.setId(v.center.element)
+
+        const isNucleic = v.moleculeType === MoleculeType.DNA || v.moleculeType === MoleculeType.RNA
+        const isSheet = SecondaryStructureType.is(v.secStrucType, SecondaryStructureType.Flag.Beta)
+        const isHelix = SecondaryStructureType.is(v.secStrucType, SecondaryStructureType.Flag.Helix)
+        const tension = (isNucleic || isSheet) ? 0.5 : 0.9
+
+        // console.log('ELEMENT', i)
+        interpolateCurveSegment(state, v, tension)
+
+        let width = 0.2, height = 0.2
+
+        // TODO size theme
+        if (isSheet) {
+            width = 0.15; height = 1.0
+            const arrowHeight = v.secStrucChange ? 1.7 : 0
+            builder.addSheet(curvePoints, normalVectors, binormalVectors, linearSegments, width, height, arrowHeight, true, true)
+        } else {
+            if (isHelix) {
+                width = 0.2; height = 1.0
+            } else if (isNucleic) {
+                width = 1.5; height = 0.3
+            }
+            builder.addTube(curvePoints, normalVectors, binormalVectors, linearSegments, radialSegments, width, height, 1, true, true)
+        }
+
+        if (i % 10000 === 0 && ctx.shouldUpdate) {
+            await ctx.update({ message: 'Polymer trace mesh', current: i, max: polymerElementCount });
+        }
+        ++i
+    }
+
+    return builder.getMesh()
+}
+
+export const DefaultPolymerTraceProps = {
+    ...DefaultMeshProps,
+    ...DefaultStructureProps,
+    sizeTheme: { name: 'physical', factor: 1 } as SizeTheme,
+    detail: 0,
+    unitKinds: [ Unit.Kind.Atomic, Unit.Kind.Spheres ] as Unit.Kind[]
+}
+export type PolymerTraceProps = Partial<typeof DefaultPolymerTraceProps>
+
+export function PolymerTraceVisual(): UnitsVisual<PolymerTraceProps> {
+    let renderObject: MeshRenderObject
+    let currentProps: typeof DefaultPolymerTraceProps
+    let mesh: Mesh
+    let currentGroup: Unit.SymmetryGroup
+
+    return {
+        get renderObject () { return renderObject },
+        async create(ctx: RuntimeContext, group: Unit.SymmetryGroup, props: PolymerTraceProps = {}) {
+            currentProps = Object.assign({}, DefaultPolymerTraceProps, props)
+            currentGroup = group
+
+            const { colorTheme, unitKinds } = { ...DefaultPolymerTraceProps, ...props }
+            const instanceCount = group.units.length
+            const elementCount = group.elements.length
+            const unit = group.units[0]
+
+            mesh = unitKinds.includes(unit.kind)
+                ? await createPolymerTraceMesh(ctx, unit, mesh)
+                : Mesh.createEmpty(mesh)
+
+            const transforms = createTransforms(group)
+            const color = createColors(group, elementCount, colorTheme)
+            const marker = createMarkers(instanceCount * elementCount)
+
+            const counts = { drawCount: mesh.triangleCount * 3, elementCount, instanceCount }
+
+            const values: MeshValues = {
+                ...getMeshData(mesh),
+                ...color,
+                ...marker,
+                aTransform: transforms,
+                elements: mesh.indexBuffer,
+                ...createMeshValues(currentProps, counts),
+            }
+            const state = createRenderableState(currentProps)
+
+            renderObject = createMeshRenderObject(values, state)
+        },
+        async update(ctx: RuntimeContext, props: PolymerTraceProps) {
+            const newProps = Object.assign({}, currentProps, props)
+
+            if (!renderObject) return false
+
+            let updateColor = false
+
+            if (newProps.detail !== currentProps.detail) {
+                const unit = currentGroup.units[0]
+                mesh = await createPolymerTraceMesh(ctx, unit, mesh)
+                ValueCell.update(renderObject.values.drawCount, mesh.triangleCount * 3)
+                updateColor = true
+            }
+
+            if (!deepEqual(newProps.colorTheme, currentProps.colorTheme)) {
+                updateColor = true
+            }
+
+            if (updateColor) {
+                const elementCount = currentGroup.elements.length
+                if (ctx.shouldUpdate) await ctx.update('Computing trace colors');
+                createColors(currentGroup, elementCount, newProps.colorTheme, renderObject.values)
+            }
+
+            updateMeshValues(renderObject.values, newProps)
+            updateRenderableState(renderObject.state, newProps)
+
+            currentProps = newProps
+            return true
+        },
+        getLoci(pickingId: PickingId) {
+            const { objectId, instanceId, elementId } = pickingId
+            if (renderObject.id === objectId) {
+                const unit = currentGroup.units[instanceId]
+                const indices = OrderedSet.ofSingleton(elementId as StructureElement.UnitIndex);
+                return StructureElement.Loci([{ unit, indices }])
+            }
+            return EmptyLoci
+        },
+        mark(loci: Loci, action: MarkerAction) {
+            markElement(renderObject.values.tMarker, currentGroup, loci, action)
+        },
+        destroy() {
+            // TODO
+        }
+    }
+}

+ 69 - 0
src/mol-geo/representation/structure/visual/util/common.ts

@@ -0,0 +1,69 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ * @author David Sehnal <david.sehnal@gmail.com>
+ */
+
+import { Unit } from 'mol-model/structure';
+import { Mat4 } from 'mol-math/linear-algebra'
+
+import { createUniformColor, ColorData } from '../../../../util/color-data';
+import { createUniformSize, SizeData } from '../../../../util/size-data';
+import { physicalSizeData } from '../../../../theme/structure/size/physical';
+import VertexMap from '../../../../shape/vertex-map';
+import { ColorTheme, SizeTheme } from '../../../../theme';
+import { elementIndexColorData, elementSymbolColorData, instanceIndexColorData, chainIdElementColorData } from '../../../../theme/structure/color';
+import { ValueCell, defaults } from 'mol-util';
+
+export function createTransforms({ units }: Unit.SymmetryGroup, transforms?: ValueCell<Float32Array>) {
+    const unitCount = units.length
+    const n = unitCount * 16
+    const array = transforms && transforms.ref.value.length >= n ? transforms.ref.value : new Float32Array(n)
+    for (let i = 0; i < unitCount; i++) {
+        Mat4.toArray(units[i].conformation.operator.matrix, array, i * 16)
+    }
+    return transforms ? ValueCell.update(transforms, array) : ValueCell.create(array)
+}
+
+const identityTransform = new Float32Array(16)
+Mat4.toArray(Mat4.identity(), identityTransform, 0)
+export function createIdentityTransform(transforms?: ValueCell<Float32Array>) {
+    return transforms ? ValueCell.update(transforms, identityTransform) : ValueCell.create(identityTransform)
+}
+
+export function createColors(group: Unit.SymmetryGroup, elementCount: number, props: ColorTheme, colorData?: ColorData) {
+    switch (props.name) {
+        case 'atom-index':
+            return elementIndexColorData({ group, elementCount }, colorData)
+        case 'chain-id':
+            return chainIdElementColorData({ group, elementCount }, colorData)
+        case 'element-symbol':
+            return elementSymbolColorData({ group, elementCount }, colorData)
+        case 'instance-index':
+            return instanceIndexColorData({ group, elementCount }, colorData)
+        case 'uniform':
+            return createUniformColor(props, colorData)
+    }
+}
+
+// export function createLinkColors(group: Unit.SymmetryGroup, props: ColorTheme, colorData?: ColorData): ColorData {
+//     switch (props.name) {
+//         case 'atom-index':
+//         case 'chain-id':
+//         case 'element-symbol':
+//         case 'instance-index':
+//             return chainIdLinkColorData({ group, vertexMap }, colorData)
+//         case 'uniform':
+//             return createUniformColor(props, colorData)
+//     }
+// }
+
+export function createSizes(group: Unit.SymmetryGroup, vertexMap: VertexMap, props: SizeTheme): SizeData {
+    switch (props.name) {
+        case 'uniform':
+            return createUniformSize(props)
+        case 'physical':
+            return physicalSizeData(defaults(props.factor, 1), { group, vertexMap })
+    }
+}

+ 103 - 0
src/mol-geo/representation/structure/visual/util/element.ts

@@ -0,0 +1,103 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+import { Vec3 } from 'mol-math/linear-algebra';
+import { Unit, StructureElement } from 'mol-model/structure';
+import { SizeTheme } from '../../../../theme';
+import { RuntimeContext } from 'mol-task';
+import { sphereVertexCount } from '../../../../primitive/sphere';
+import { Mesh } from '../../../../shape/mesh';
+import { MeshBuilder } from '../../../../shape/mesh-builder';
+import { ValueCell, defaults } from 'mol-util';
+import { TextureImage } from 'mol-gl/renderable/util';
+import { Loci, isEveryLoci, EmptyLoci } from 'mol-model/loci';
+import { MarkerAction, applyMarkerAction } from '../../../../util/marker-data';
+import { Interval, OrderedSet } from 'mol-data/int';
+import { getPhysicalRadius } from '../../../../theme/structure/size/physical';
+import { PickingId } from '../../../../util/picking';
+
+export function getElementRadius(unit: Unit, props: SizeTheme): StructureElement.Property<number> {
+    switch (props.name) {
+        case 'uniform':
+            return () => props.value
+        case 'physical':
+            const radius = getPhysicalRadius(unit)
+            const factor = defaults(props.factor, 1)
+            return (l) => radius(l) * factor
+    }
+}
+
+export async function createElementSphereMesh(ctx: RuntimeContext, unit: Unit, radius: StructureElement.Property<number>, detail: number, mesh?: Mesh) {
+    const { elements } = unit;
+    const elementCount = elements.length;
+    const vertexCount = elementCount * sphereVertexCount(detail)
+    const meshBuilder = MeshBuilder.create(vertexCount, vertexCount / 2, mesh)
+
+    const v = Vec3.zero()
+    const pos = unit.conformation.invariantPosition
+    const l = StructureElement.create()
+    l.unit = unit
+
+    for (let i = 0; i < elementCount; i++) {
+        l.element = elements[i]
+        pos(elements[i], v)
+
+        meshBuilder.setId(i)
+        meshBuilder.addSphere(v, radius(l), detail)
+
+        if (i % 10000 === 0 && ctx.shouldUpdate) {
+            await ctx.update({ message: 'Sphere mesh', current: i, max: elementCount });
+        }
+    }
+
+    return meshBuilder.getMesh()
+}
+
+export function markElement(tMarker: ValueCell<TextureImage>, group: Unit.SymmetryGroup, loci: Loci, action: MarkerAction) {
+    let changed = false
+    const elementCount = group.elements.length
+    const instanceCount = group.units.length
+    const array = tMarker.ref.value.array
+    if (isEveryLoci(loci)) {
+        applyMarkerAction(array, 0, elementCount * instanceCount, action)
+        changed = true
+    } else if (StructureElement.isLoci(loci)) {
+        for (const e of loci.elements) {
+            const unitIdx = Unit.findUnitById(e.unit.id, group.units)
+            if (unitIdx !== -1) {
+                if (Interval.is(e.indices)) {
+                    const idxStart = unitIdx * elementCount + Interval.start(e.indices);
+                    const idxEnd = unitIdx * elementCount + Interval.end(e.indices);
+                    if (applyMarkerAction(array, idxStart, idxEnd, action) && !changed) {
+                        changed = true
+                    }
+                } else {
+                    for (let i = 0, _i = e.indices.length; i < _i; i++) {
+                        const idx = unitIdx * elementCount + e.indices[i];
+                        if (applyMarkerAction(array, idx, idx + 1, action) && !changed) {
+                            changed = true
+                        }
+                    }
+                }
+            }
+        }
+    } else {
+        return
+    }
+    if (changed) {
+        ValueCell.update(tMarker, tMarker.ref.value)
+    }
+}
+
+export function getElementLoci(id: number, group: Unit.SymmetryGroup, pickingId: PickingId) {
+    const { objectId, instanceId, elementId } = pickingId
+    if (id === objectId) {
+        const unit = group.units[instanceId]
+        const indices = OrderedSet.ofSingleton(elementId as StructureElement.UnitIndex);
+        return StructureElement.Loci([{ unit, indices }])
+    }
+    return EmptyLoci
+}

+ 122 - 0
src/mol-geo/representation/structure/visual/util/link.ts

@@ -0,0 +1,122 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+import { Vec3 } from 'mol-math/linear-algebra';
+import { RuntimeContext } from 'mol-task';
+import { Mesh } from '../../../../shape/mesh';
+import { MeshBuilder } from '../../../../shape/mesh-builder';
+import { LinkType } from 'mol-model/structure/model/types';
+import { DefaultMeshProps } from '../../../util';
+
+export const DefaultLinkCylinderProps = {
+    ...DefaultMeshProps,
+    linkScale: 0.4,
+    linkSpacing: 1,
+    linkRadius: 0.25,
+    radialSegments: 16
+}
+export type LinkCylinderProps = typeof DefaultLinkCylinderProps
+
+const tmpShiftV12 = Vec3.zero()
+const tmpShiftV13 = Vec3.zero()
+
+/** Calculate 'shift' direction that is perpendiculat to v1 - v2 and goes through v3 */
+export function calculateShiftDir (out: Vec3, v1: Vec3, v2: Vec3, v3: Vec3 | null) {
+    Vec3.normalize(tmpShiftV12, Vec3.sub(tmpShiftV12, v1, v2))
+
+    if (v3 !== null) {
+        Vec3.sub(tmpShiftV13, v1, v3)
+    } else {
+        Vec3.copy(tmpShiftV13, v1)  // no reference point, use v1
+    }
+    Vec3.normalize(tmpShiftV13, tmpShiftV13)
+
+    // ensure v13 and v12 are not colinear
+    let dp = Vec3.dot(tmpShiftV12, tmpShiftV13)
+    if (1 - Math.abs(dp) < 1e-5) {
+        Vec3.set(tmpShiftV13, 1, 0, 0)
+        dp = Vec3.dot(tmpShiftV12, tmpShiftV13)
+        if (1 - Math.abs(dp) < 1e-5) {
+            Vec3.set(tmpShiftV13, 0, 1, 0)
+            dp = Vec3.dot(tmpShiftV12, tmpShiftV13)
+        }
+    }
+
+    Vec3.setMagnitude(tmpShiftV12, tmpShiftV12, dp)
+    Vec3.sub(tmpShiftV13, tmpShiftV13, tmpShiftV12)
+    return Vec3.normalize(out, tmpShiftV13)
+}
+
+export interface LinkCylinderMeshBuilderProps {
+    linkCount: number
+    referencePosition(edgeIndex: number): Vec3 | null
+    position(posA: Vec3, posB: Vec3, edgeIndex: number): void
+    order(edgeIndex: number): number
+    flags(edgeIndex: number): LinkType.Flag
+}
+
+/**
+ * Each edge is included twice to allow for coloring/picking
+ * the half closer to the first vertex, i.e. vertex a.
+ */
+export async function createLinkCylinderMesh(ctx: RuntimeContext, linkBuilder: LinkCylinderMeshBuilderProps, props: LinkCylinderProps, mesh?: Mesh) {
+    const { linkCount, referencePosition, position, order, flags } = linkBuilder
+
+    if (!linkCount) return Mesh.createEmpty(mesh)
+
+    // approximate vertextCount (* 2), exact calculation would need to take
+    // multiple cylinders for bond orders and metall coordinations into account
+    const vertexCount = props.radialSegments * 2 * linkCount * 2
+    const meshBuilder = MeshBuilder.create(vertexCount, vertexCount / 2, mesh)
+
+    const va = Vec3.zero()
+    const vb = Vec3.zero()
+    const vShift = Vec3.zero()
+
+    const { linkScale, linkSpacing, linkRadius, radialSegments } = props
+
+    const cylinderParams = {
+        height: 1,
+        radiusTop: linkRadius,
+        radiusBottom: linkRadius,
+        radialSegments
+    }
+
+    for (let edgeIndex = 0, _eI = linkCount; edgeIndex < _eI; ++edgeIndex) {
+        position(va, vb, edgeIndex)
+
+        const o = order(edgeIndex)
+        const f = flags(edgeIndex) as any as LinkType // TODO
+        meshBuilder.setId(edgeIndex)
+
+        if (LinkType.is(f, LinkType.Flag.MetallicCoordination)) {
+            // show metall coordinations with dashed cylinders
+            cylinderParams.radiusTop = cylinderParams.radiusBottom = linkRadius / 3
+            meshBuilder.addFixedCountDashedCylinder(va, vb, 0.5, 7, cylinderParams)
+        } else if (o === 2 || o === 3) {
+            // show bonds with order 2 or 3 using 2 or 3 parallel cylinders
+            const multiRadius = linkRadius * (linkScale / (0.5 * o))
+            const absOffset = (linkRadius - multiRadius) * linkSpacing
+
+            calculateShiftDir(vShift, va, vb, referencePosition(edgeIndex))
+            Vec3.setMagnitude(vShift, vShift, absOffset)
+
+            cylinderParams.radiusTop = cylinderParams.radiusBottom = multiRadius
+
+            if (o === 3) meshBuilder.addCylinder(va, vb, 0.5, cylinderParams)
+            meshBuilder.addDoubleCylinder(va, vb, 0.5, vShift, cylinderParams)
+        } else {
+            cylinderParams.radiusTop = cylinderParams.radiusBottom = linkRadius
+            meshBuilder.addCylinder(va, vb, 0.5, cylinderParams)
+        }
+
+        if (edgeIndex % 10000 === 0 && ctx.shouldUpdate) {
+            await ctx.update({ message: 'Cylinder mesh', current: edgeIndex, max: linkCount });
+        }
+    }
+
+    return meshBuilder.getMesh()
+}

+ 69 - 0
src/mol-geo/representation/structure/visual/util/polymer.ts

@@ -0,0 +1,69 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+import { Unit, ElementIndex } from 'mol-model/structure';
+import { Segmentation, OrderedSet, Interval } from 'mol-data/int';
+import SortedRanges from 'mol-data/int/sorted-ranges';
+
+export * from './polymer/backbone-iterator'
+export * from './polymer/gap-iterator'
+export * from './polymer/trace-iterator'
+export * from './polymer/curve-segment'
+
+export function getPolymerRanges(unit: Unit): SortedRanges<ElementIndex> {
+    switch (unit.kind) {
+        case Unit.Kind.Atomic: return unit.model.atomicHierarchy.polymerRanges
+        case Unit.Kind.Spheres: return unit.model.coarseHierarchy.spheres.polymerRanges
+        case Unit.Kind.Gaussians: return unit.model.coarseHierarchy.gaussians.polymerRanges
+    }
+}
+
+export function getGapRanges(unit: Unit): SortedRanges<ElementIndex> {
+    switch (unit.kind) {
+        case Unit.Kind.Atomic: return unit.model.atomicHierarchy.gapRanges
+        case Unit.Kind.Spheres: return unit.model.coarseHierarchy.spheres.gapRanges
+        case Unit.Kind.Gaussians: return unit.model.coarseHierarchy.gaussians.gapRanges
+    }
+}
+
+export function getPolymerElementCount(unit: Unit) {
+    let count = 0
+    const { elements } = unit
+    const polymerIt = SortedRanges.transientSegments(getPolymerRanges(unit), elements)
+    switch (unit.kind) {
+        case Unit.Kind.Atomic:
+            const residueIt = Segmentation.transientSegments(unit.model.atomicHierarchy.residueAtomSegments, elements)
+            while (polymerIt.hasNext) {
+                const polymerSegment = polymerIt.move()
+                residueIt.setSegment(polymerSegment)
+                while (residueIt.hasNext) {
+                    const residueSegment = residueIt.move()
+                    const { start, end } = residueSegment
+                    if (OrderedSet.areIntersecting(Interval.ofBounds(elements[start], elements[end - 1]), elements)) ++count
+                }
+            }
+            break
+        case Unit.Kind.Spheres:
+        case Unit.Kind.Gaussians:
+            while (polymerIt.hasNext) {
+                const { start, end } = polymerIt.move()
+                count += OrderedSet.intersectionSize(Interval.ofBounds(elements[start], elements[end - 1]), elements)
+            }
+            break
+    }
+    return count
+}
+
+export function getPolymerGapCount(unit: Unit) {
+    let count = 0
+    const { elements } = unit
+    const gapIt = SortedRanges.transientSegments(getGapRanges(unit), elements)
+    while (gapIt.hasNext) {
+        const { start, end } = gapIt.move()
+        if (OrderedSet.areIntersecting(Interval.ofBounds(elements[start], elements[end - 1]), elements)) ++count
+    }
+    return count
+}

+ 144 - 0
src/mol-geo/representation/structure/visual/util/polymer/backbone-iterator.ts

@@ -0,0 +1,144 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+import { Unit, StructureElement, ElementIndex, ResidueIndex } from 'mol-model/structure';
+import { Segmentation, SortedArray } from 'mol-data/int';
+import Iterator from 'mol-data/iterator';
+import SortedRanges from 'mol-data/int/sorted-ranges';
+import { getElementIndexForAtomRole } from 'mol-model/structure/util';
+import { AtomRole } from 'mol-model/structure/model/types';
+import { getPolymerRanges } from '../polymer';
+
+/** Iterates over consecutive pairs of residues/coarse elements in polymers */
+export function PolymerBackboneIterator(unit: Unit): Iterator<PolymerBackbonePair> {
+    switch (unit.kind) {
+        case Unit.Kind.Atomic: return new AtomicPolymerBackboneIterator(unit)
+        case Unit.Kind.Spheres:
+        case Unit.Kind.Gaussians:
+            return new CoarsePolymerBackboneIterator(unit)
+    }
+}
+
+interface PolymerBackbonePair {
+    centerA: StructureElement
+    centerB: StructureElement
+}
+
+function createPolymerBackbonePair (unit: Unit) {
+    return {
+        centerA: StructureElement.create(unit),
+        centerB: StructureElement.create(unit),
+    }
+}
+
+const enum AtomicPolymerBackboneIteratorState { nextPolymer, firstResidue, nextResidue, cycle }
+
+export class AtomicPolymerBackboneIterator implements Iterator<PolymerBackbonePair> {
+    private value: PolymerBackbonePair
+    private polymerIt: SortedRanges.Iterator<ElementIndex, ResidueIndex>
+    private residueIt: Segmentation.SegmentIterator<ResidueIndex>
+    private state: AtomicPolymerBackboneIteratorState = AtomicPolymerBackboneIteratorState.nextPolymer
+    private residueSegment: Segmentation.Segment<ResidueIndex>
+    hasNext: boolean = false;
+
+    private getElementIndex(residueIndex: ResidueIndex, atomRole: AtomRole) {
+        const index = getElementIndexForAtomRole(this.unit.model, residueIndex, atomRole)
+        // TODO handle case when it returns -1
+        const elementIndex = SortedArray.indexOf(this.unit.elements, index) as ElementIndex
+        if (elementIndex === -1) {
+            console.log('-1', residueIndex, atomRole, index)
+        }
+        return elementIndex === -1 ? 0 as ElementIndex : elementIndex
+    }
+
+    move() {
+        if (this.state === AtomicPolymerBackboneIteratorState.nextPolymer) {
+            while (this.polymerIt.hasNext) {
+                this.residueIt.setSegment(this.polymerIt.move());
+                if (this.residueIt.hasNext) {
+                    this.residueSegment = this.residueIt.move()
+                    this.value.centerB.element = this.getElementIndex(this.residueSegment.index, 'trace')
+                    this.state = AtomicPolymerBackboneIteratorState.nextResidue
+                    break
+                }
+            }
+        }
+
+        if (this.state === AtomicPolymerBackboneIteratorState.nextResidue) {
+            this.residueSegment = this.residueIt.move()
+            this.value.centerA.element = this.value.centerB.element
+            this.value.centerB.element = this.getElementIndex(this.residueSegment.index, 'trace')
+            if (!this.residueIt.hasNext) {
+                if (this.unit.model.atomicHierarchy.cyclicPolymerMap.has(this.residueSegment.index)) {
+                    this.state = AtomicPolymerBackboneIteratorState.cycle
+                } else {
+                    // TODO need to advance to a polymer that has two or more residues (can't assume it has)
+                    this.state = AtomicPolymerBackboneIteratorState.nextPolymer
+                }
+            }
+        } else if (this.state === AtomicPolymerBackboneIteratorState.cycle) {
+            const { cyclicPolymerMap } = this.unit.model.atomicHierarchy
+            this.value.centerA.element = this.value.centerB.element
+            this.value.centerB.element = this.getElementIndex(cyclicPolymerMap.get(this.residueSegment.index)!, 'trace')
+            // TODO need to advance to a polymer that has two or more residues (can't assume it has)
+            this.state = AtomicPolymerBackboneIteratorState.nextPolymer
+        }
+
+        this.hasNext = this.residueIt.hasNext || this.polymerIt.hasNext || this.state === AtomicPolymerBackboneIteratorState.cycle
+        return this.value;
+    }
+
+    constructor(private unit: Unit.Atomic) {
+        this.polymerIt = SortedRanges.transientSegments(getPolymerRanges(unit), unit.elements)
+        this.residueIt = Segmentation.transientSegments(unit.model.atomicHierarchy.residueAtomSegments, unit.elements)
+        this.value = createPolymerBackbonePair(unit)
+        this.hasNext = this.residueIt.hasNext && this.polymerIt.hasNext
+    }
+}
+
+const enum CoarsePolymerBackboneIteratorState { nextPolymer, nextElement }
+
+export class CoarsePolymerBackboneIterator implements Iterator<PolymerBackbonePair> {
+    private value: PolymerBackbonePair
+    private polymerIt: SortedRanges.Iterator<ElementIndex, ResidueIndex>
+    private polymerSegment: Segmentation.Segment<ResidueIndex>
+    private state: CoarsePolymerBackboneIteratorState = CoarsePolymerBackboneIteratorState.nextPolymer
+    private elementIndex: number
+    hasNext: boolean = false;
+
+    move() {
+        if (this.state === CoarsePolymerBackboneIteratorState.nextPolymer) {
+            if (this.polymerIt.hasNext) {
+                this.polymerSegment = this.polymerIt.move();
+                this.elementIndex = this.polymerSegment.start
+                if (this.elementIndex + 1 < this.polymerSegment.end) {
+                    this.value.centerB.element = this.unit.elements[this.elementIndex]
+                    this.state = CoarsePolymerBackboneIteratorState.nextElement
+                } else {
+                    this.state = CoarsePolymerBackboneIteratorState.nextPolymer
+                }
+            }
+        }
+
+        if (this.state === CoarsePolymerBackboneIteratorState.nextElement) {
+            this.elementIndex += 1
+            this.value.centerA.element = this.value.centerB.element
+            this.value.centerB.element = this.unit.elements[this.elementIndex]
+            if (this.elementIndex + 1 >= this.polymerSegment.end) {
+                this.state = CoarsePolymerBackboneIteratorState.nextPolymer
+            }
+        }
+
+        this.hasNext = this.elementIndex + 1 < this.polymerSegment.end || this.polymerIt.hasNext
+        return this.value;
+    }
+
+    constructor(private unit: Unit.Spheres | Unit.Gaussians) {
+        this.polymerIt = SortedRanges.transientSegments(getPolymerRanges(unit), unit.elements);
+        this.value = createPolymerBackbonePair(unit)
+        this.hasNext = this.polymerIt.hasNext
+    }
+}

Algúns arquivos non se mostraron porque demasiados arquivos cambiaron neste cambio