@motionscript/molecule 0.0.0-stage → 0.1.0-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/CHANGELOG.md +5 -0
  2. package/LICENSE +201 -0
  3. package/dist/browser/index.js +4 -0
  4. package/dist/browser/index.js.map +7 -0
  5. package/dist/browser/manifest.json +11 -0
  6. package/dist/index.d.ts +3 -0
  7. package/dist/index.d.ts.map +1 -0
  8. package/dist/index.js +3 -0
  9. package/dist/index.js.map +1 -0
  10. package/dist/nodes.d.ts +18 -0
  11. package/dist/nodes.d.ts.map +1 -0
  12. package/dist/nodes.js +18 -0
  13. package/dist/nodes.js.map +1 -0
  14. package/dist/protein/chemistry.d.ts +52 -0
  15. package/dist/protein/chemistry.d.ts.map +1 -0
  16. package/dist/protein/chemistry.js +208 -0
  17. package/dist/protein/chemistry.js.map +1 -0
  18. package/dist/protein/index.d.ts +35 -0
  19. package/dist/protein/index.d.ts.map +1 -0
  20. package/dist/protein/index.js +35 -0
  21. package/dist/protein/index.js.map +1 -0
  22. package/dist/protein/parse.d.ts +32 -0
  23. package/dist/protein/parse.d.ts.map +1 -0
  24. package/dist/protein/parse.js +387 -0
  25. package/dist/protein/parse.js.map +1 -0
  26. package/dist/protein/protein.d.ts +265 -0
  27. package/dist/protein/protein.d.ts.map +1 -0
  28. package/dist/protein/protein.js +645 -0
  29. package/dist/protein/protein.js.map +1 -0
  30. package/dist/protein/ribbon.d.ts +83 -0
  31. package/dist/protein/ribbon.d.ts.map +1 -0
  32. package/dist/protein/ribbon.js +468 -0
  33. package/dist/protein/ribbon.js.map +1 -0
  34. package/dist/protein/shared.d.ts +221 -0
  35. package/dist/protein/shared.d.ts.map +1 -0
  36. package/dist/protein/shared.js +478 -0
  37. package/dist/protein/shared.js.map +1 -0
  38. package/dist/protein/structure.d.ts +184 -0
  39. package/dist/protein/structure.d.ts.map +1 -0
  40. package/dist/protein/structure.js +324 -0
  41. package/dist/protein/structure.js.map +1 -0
  42. package/package.json +64 -3
  43. package/registry.json +22 -0
  44. package/src/index.ts +2 -0
  45. package/src/nodes.ts +18 -0
  46. package/src/protein/chemistry.ts +223 -0
  47. package/src/protein/index.ts +34 -0
  48. package/src/protein/parse.ts +427 -0
  49. package/src/protein/protein.ts +897 -0
  50. package/src/protein/ribbon.ts +658 -0
  51. package/src/protein/shared.ts +622 -0
  52. package/src/protein/structure.ts +491 -0
  53. package/README.md +0 -4
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/protein/chemistry.ts", "../../src/protein/structure.ts", "../../src/protein/parse.ts", "../../src/protein/protein.ts", "../../src/protein/shared.ts", "../../src/protein/ribbon.ts", "../../src/nodes.ts"],
4
+ "sourcesContent": ["/**\n * The chemistry the {@link Protein} node draws from: how big an atom is, what\n * colour it conventionally takes, what counts as a bond, and which residues are\n * part of a polymer chain rather than sitting beside it.\n *\n * Tables rather than a dependency, for the same reason `graph3d/expression.ts`\n * is a parser rather than a maths library: this package has to stay loadable in\n * a bare Node process with no DOM, and what a molecular-graphics library would\n * bring with it — a renderer, a fetch layer, a DOM — is everything the scene\n * builder was split apart to avoid.\n *\n * The numbers are the standard ones. Van der Waals radii are Bondi's, covalent\n * radii are Cordero's, and the colours are the CPK scheme every molecular viewer\n * has used since Corey and Pauling built theirs out of painted wood — which is\n * the point of using them: a biologist reads oxygen as red before they have\n * read the legend.\n */\n\n/** Ångströms. What an atom's electron cloud actually occupies. */\nconst VAN_DER_WAALS: Readonly<Record<string, number>> = {\n H: 1.1,\n C: 1.7,\n N: 1.55,\n O: 1.52,\n F: 1.47,\n NA: 2.27,\n MG: 1.73,\n P: 1.8,\n S: 1.8,\n CL: 1.75,\n K: 2.75,\n CA: 2.31,\n MN: 2.05,\n FE: 2.04,\n CO: 2.0,\n NI: 1.97,\n CU: 1.96,\n ZN: 2.01,\n SE: 1.9,\n BR: 1.85,\n I: 1.98,\n}\n\n/** The fallback radius, carbon's — the element most of a protein is made of. */\nconst DEFAULT_VAN_DER_WAALS = 1.7\n\n/** Ångströms. Half of how far two of these sit apart when bonded. */\nconst COVALENT: Readonly<Record<string, number>> = {\n H: 0.31,\n C: 0.76,\n N: 0.71,\n O: 0.66,\n F: 0.57,\n NA: 1.66,\n MG: 1.41,\n P: 1.07,\n S: 1.05,\n CL: 1.02,\n K: 2.03,\n CA: 1.76,\n MN: 1.39,\n FE: 1.32,\n CO: 1.26,\n NI: 1.24,\n CU: 1.32,\n ZN: 1.22,\n SE: 1.2,\n BR: 1.2,\n I: 1.39,\n}\n\nconst DEFAULT_COVALENT = 0.77\n\n/**\n * The CPK colours, as Jmol renders them.\n *\n * Only the elements a biological structure actually contains, plus the metals\n * that turn up in cofactors. Anything else takes {@link UNKNOWN_ELEMENT_COLOR},\n * which is deliberately garish — an unrecognised element should look like a\n * question rather than blend into the carbons.\n */\nconst CPK: Readonly<Record<string, string>> = {\n H: \"#ffffff\",\n C: \"#909090\",\n N: \"#3050f8\",\n O: \"#ff0d0d\",\n F: \"#90e050\",\n NA: \"#ab5cf2\",\n MG: \"#8aff00\",\n P: \"#ff8000\",\n S: \"#ffff30\",\n CL: \"#1ff01f\",\n K: \"#8f40d4\",\n CA: \"#3dff00\",\n MN: \"#9c7ac7\",\n FE: \"#e06633\",\n CO: \"#f090a0\",\n NI: \"#50d050\",\n CU: \"#c88033\",\n ZN: \"#7d80b0\",\n SE: \"#ffa100\",\n BR: \"#a62929\",\n I: \"#940094\",\n}\n\n/** What an element nobody has a colour for is drawn in. */\nexport const UNKNOWN_ELEMENT_COLOR = \"#ff1493\"\n\n/** An element's van der Waals radius in Ångströms. */\nexport function vanDerWaalsRadius(element: string): number {\n return VAN_DER_WAALS[element] ?? DEFAULT_VAN_DER_WAALS\n}\n\n/** An element's covalent radius in Ångströms — half of a bond it makes. */\nexport function covalentRadius(element: string): number {\n return COVALENT[element] ?? DEFAULT_COVALENT\n}\n\n/** The CPK colour for an element, as a `#rrggbb` string. */\nexport function elementColor(element: string): string {\n return CPK[element] ?? UNKNOWN_ELEMENT_COLOR\n}\n\n/**\n * The standard amino acids, plus the two modified residues common enough that\n * treating them as foreign would put a hole in the middle of a chain:\n * selenomethionine (`MSE`, how a crystallographer phases a new structure) and\n * pyroglutamate (`PCA`).\n *\n * Both arrive as `HETATM` records, which is exactly why the set is consulted\n * rather than the record type — \"is this part of the chain\" is a question about\n * the residue, and the file answers a different one.\n */\nconst AMINO_ACIDS = new Set([\n \"ALA\", \"ARG\", \"ASN\", \"ASP\", \"CYS\", \"GLN\", \"GLU\", \"GLY\", \"HIS\", \"ILE\",\n \"LEU\", \"LYS\", \"MET\", \"PHE\", \"PRO\", \"SER\", \"THR\", \"TRP\", \"TYR\", \"VAL\",\n \"MSE\", \"PCA\", \"SEC\", \"PYL\",\n])\n\n/** The nucleotides, RNA and DNA. */\nconst NUCLEOTIDES = new Set([\n \"A\", \"C\", \"G\", \"U\", \"I\",\n \"DA\", \"DC\", \"DG\", \"DT\", \"DI\",\n])\n\n/**\n * The waters. Named separately from the rest of the hetero atoms because they\n * are the one kind a viewer hides by default: a crystal structure carries\n * hundreds of them, they are chemically uninteresting to the picture being\n * drawn, and left on they bury the molecule in red dots.\n */\nconst WATERS = new Set([\"HOH\", \"DOD\", \"WAT\", \"H2O\", \"TIP\"])\n\n/** What a residue is, which decides whether it joins a backbone trace. */\nexport type ResidueKind = \"amino\" | \"nucleic\" | \"water\" | \"other\"\n\n/** Classifies a residue by its three-letter code. */\nexport function residueKind(name: string): ResidueKind {\n const code = name.trim().toUpperCase()\n if (AMINO_ACIDS.has(code)) return \"amino\"\n if (NUCLEOTIDES.has(code)) return \"nucleic\"\n if (WATERS.has(code)) return \"water\"\n return \"other\"\n}\n\n/**\n * The atom a residue's backbone trace passes through: the α-carbon of an amino\n * acid, the phosphorus of a nucleotide.\n *\n * One atom per residue rather than the whole backbone, because what the trace is\n * *for* is the smooth curve a ribbon is swept along, and the N–CA–C zig-zag\n * would put a kink at every residue. Every molecular viewer traces α-carbons for\n * the same reason.\n */\nexport function traceAtomName(kind: ResidueKind): string | null {\n if (kind === \"amino\") return \"CA\"\n if (kind === \"nucleic\") return \"P\"\n return null\n}\n\n/**\n * The element an atom belongs to, from the file's element column when it has\n * one and from the atom's *name* when it doesn't.\n *\n * The fallback matters more than it looks: the element column is right-justified\n * in columns 77–78 of a PDB record and plenty of older files, and most files\n * written by hand, simply stop before it. The name is then all there is, and\n * reading it is not quite trimming the digits off — an amino acid's `CA` is a\n * carbon and an ion's `CA` is calcium, and the two are told apart only by what\n * residue they sit in. Hence {@link kind}.\n */\nexport function elementOf(\n declared: string,\n atomName: string,\n kind: ResidueKind\n): string {\n const stated = declared.trim().toUpperCase()\n if (stated !== \"\") return stated\n\n // PDB atom names are padded so that the element starts in column 14 for a\n // two-letter element and column 15 for a one-letter one — but only in files\n // that respect the alignment, which is why this reads the characters instead:\n // strip the leading digit an alternate hydrogen carries (`1HB`), then take the\n // letters.\n const name = atomName.trim().toUpperCase().replace(/^\\d+/, \"\")\n const letters = name.replace(/[^A-Z]/g, \"\")\n if (letters === \"\") return \"\"\n\n // Inside a polymer residue the only two-letter elements are the ones that\n // can't be confused: everything else is C, N, O, S or P followed by the\n // position letters that name the atom (`CB`, `NZ`, `OG1`). A `CA` there is the\n // α-carbon, never calcium.\n if (kind === \"amino\" || kind === \"nucleic\") {\n if (letters.startsWith(\"SE\")) return \"SE\"\n return letters.slice(0, 1)\n }\n\n // Outside one — a ligand, an ion, a cofactor — a two-letter element is real,\n // so prefer the longest match the tables know before falling back to one.\n const pair = letters.slice(0, 2)\n if (pair in COVALENT) return pair\n return letters.slice(0, 1)\n}\n", "/**\n * What a parsed molecule *is*, and the three things derived from it that no\n * coordinate file states outright: which atoms are bonded, which curve each\n * chain traces, and how big the whole thing is.\n *\n * The shape here is the seam between the parser (`parse.ts`) and the node\n * (`protein.ts`), and it is deliberately flat and numeric: parallel arrays of\n * plain numbers rather than a graph of objects. A mid-sized structure is tens of\n * thousands of atoms, the node rebuilds its command list every frame, and an\n * object per atom would put a few hundred thousand allocations between the\n * scrubber and the picture.\n *\n * Parsing happens **once, ahead of the build** — the same arrangement a chart's\n * CSV gets (see `SceneAsset.rows`), and for the same reason: `buildScene` is\n * synchronous, so anything that has to be read off a network or a file has to\n * already be here by the time it runs.\n */\n\nimport { covalentRadius, traceAtomName, type ResidueKind } from \"./chemistry\"\n\n/** One atom, as the file gave it. Coordinates are in Ångströms. */\nexport interface ProteinAtom {\n /** The file's own serial number, kept so `CONECT` records can be resolved. */\n serial: number\n /** The atom's name within its residue — `CA`, `OG1`, `FE`. */\n name: string\n /** The element symbol, upper-cased. Derived when the file omits it. */\n element: string\n /** The residue's three-letter code — `ALA`, `HOH`, `HEM`. */\n residue: string\n /** The residue's number within its chain, as the file numbers it. */\n residueSeq: number\n /** The chain the residue belongs to — `A`, `B`, … */\n chain: string\n x: number\n y: number\n z: number\n /** Whether the file filed this under `HETATM` — a ligand, an ion, a water. */\n hetero: boolean\n /** What the residue is, resolved once here so nothing downstream re-derives it. */\n kind: ResidueKind\n}\n\n/**\n * What a stretch of backbone is doing, which is the one property of a protein\n * that a picture of it is usually *about*.\n *\n * Taken from the file's own `HELIX`/`SHEET` annotations rather than computed:\n * assigning secondary structure from coordinates alone is DSSP, which is a\n * hydrogen-bond model and a paper of its own. A file that annotates none draws\n * as an even tube, which is the honest picture of \"nobody said\".\n */\nexport type SecondaryStructure = \"helix\" | \"sheet\" | \"coil\"\n\n/** One residue's place on a chain's backbone curve. */\nexport interface TracePoint {\n x: number\n y: number\n z: number\n /** What this residue is part of, for the ribbon's shape and its colouring. */\n secondary: SecondaryStructure\n /**\n * The residue's position along **this trace**, 0-based. Against the trace's\n * own length this is what a spectrum colouring needs: N terminus to C\n * terminus, per chain, independent of how long the others are.\n */\n index: number\n /**\n * The residue's number as the file gives it, which is what ties a trace point\n * back to the atoms of the same residue.\n *\n * Both are needed and neither substitutes for the other: {@link index} is\n * dense and ordered, so it is what a spectrum is computed against, while this\n * is sparse and arbitrary — files skip numbers, start at 17, and number\n * insertions — so it is what a *lookup* has to be keyed on.\n */\n residueSeq: number\n /**\n * Which way this residue's ribbon faces — the unit vector its **wide**\n * direction is swept along.\n *\n * A tube needs a point and nothing else; a cartoon ribbon needs to know which\n * way round it is, and that is not something a curve through α-carbons can\n * answer. The residue's own carbonyl does: CA→O is roughly perpendicular to\n * the chain and turns with the fold, so sweeping the ribbon's width along it\n * is what makes a helix read as a twisted band and a strand as a flat one.\n * This is the same vector every molecular viewer builds its cartoon frame\n * from.\n *\n * `null` when the file has no carbonyl for the residue — a Cα-only model, a\n * nucleotide, the last residue of a truncated chain — and the ribbon builder\n * falls back to the curve's own binormal there. See `ribbonFrames`.\n *\n * Not orthogonalized here: it is a *direction*, and the frame that has to be\n * square is built against the tangent, which only exists once the neighbours\n * are known.\n */\n normal: { x: number; y: number; z: number } | null\n}\n\n/** One polymer chain: its identity and the curve its backbone follows. */\nexport interface ProteinChain {\n /** The chain identifier the file uses — `A`, `B`, … */\n id: string\n /** The α-carbon (or phosphorus) trace, in residue order. */\n trace: TracePoint[]\n}\n\n/**\n * A parsed molecule.\n *\n * Bonds and chains are *derived* (see {@link deriveStructure}) rather than read,\n * because coordinate files mostly don't state them: `CONECT` records cover the\n * ligands and little else, and the chain break between two residues is implied\n * by their distance.\n */\nexport interface ProteinStructure {\n /** The accession or filename this came from, for labelling. */\n id: string\n /** The file's `TITLE`, when it has one. */\n title: string\n atoms: ProteinAtom[]\n /** Bonded pairs, as indices into {@link atoms}. */\n bonds: ProteinBond[]\n chains: ProteinChain[]\n /** The centroid of every atom — what the node translates to the origin. */\n center: { x: number; y: number; z: number }\n /** Distance from {@link center} to the furthest atom, in Ångströms. */\n radius: number\n}\n\n/** One bond, as a pair of indices into {@link ProteinStructure.atoms}. */\nexport interface ProteinBond {\n a: number\n b: number\n}\n\n/** An empty molecule — what an unresolved or unparseable source draws as. */\nexport const EMPTY_STRUCTURE: ProteinStructure = {\n id: \"\",\n title: \"\",\n atoms: [],\n bonds: [],\n chains: [],\n center: { x: 0, y: 0, z: 0 },\n radius: 1,\n}\n\n/**\n * A secondary-structure span as the file states it: a run of residue numbers on\n * one chain. Collected by the parser, applied to the traces here.\n */\nexport interface SecondarySpan {\n chain: string\n start: number\n end: number\n kind: Exclude<SecondaryStructure, \"coil\">\n}\n\n/**\n * Completes a parsed atom list into a {@link ProteinStructure}: infers the\n * bonds, walks out each chain's backbone, and measures the whole thing.\n *\n * Split from the parsers because both formats reach the same place — an atom\n * list and a set of annotated spans — and everything past that point is\n * geometry rather than syntax.\n */\nexport function deriveStructure(\n id: string,\n title: string,\n atoms: ProteinAtom[],\n spans: SecondarySpan[]\n): ProteinStructure {\n if (atoms.length === 0) return { ...EMPTY_STRUCTURE, id, title }\n\n return {\n id,\n title,\n atoms,\n bonds: inferBonds(atoms),\n chains: traceChains(atoms, spans),\n ...measure(atoms),\n }\n}\n\n// --- Bonds -----------------------------------------------------------------\n\n/**\n * How much further apart than the sum of their covalent radii two atoms may sit\n * and still count as bonded, in Ångströms.\n *\n * The conventional tolerance. It has to be generous enough to survive a\n * moderate-resolution structure's coordinate error and mean enough not to bond\n * a residue to the one packed against it — 0.45 Å is where every viewer has\n * settled, and the gap between a real bond (~1.5 Å) and the closest non-bonded\n * contact (~2.8 Å) is wide enough that the exact number rarely decides anything.\n */\nconst BOND_TOLERANCE = 0.45\n\n/**\n * The shortest separation treated as a bond. Two atoms closer than this are\n * alternate conformations of the same one, or a duplicate record — bonding them\n * would draw a stick of no length and a mesh with no orientation.\n */\nconst MIN_BOND_LENGTH = 0.4\n\n/**\n * The furthest two atoms can be bonded, which sets the neighbour grid's cell\n * size: the largest pair of covalent radii here (potassium's, twice) plus the\n * tolerance.\n */\nconst MAX_BOND_LENGTH = 2.5\n\n/**\n * Infers bonds by distance, over a uniform grid.\n *\n * The grid is what makes this affordable. Bonding is a nearest-neighbour\n * question and the naïve form is every atom against every other — 20,000 atoms\n * is 200 million comparisons, which is a visible freeze on the frame someone\n * picks a structure. Bucketed at the maximum bond length, each atom only looks\n * at the 27 cells around it, and since the cells are that size no bond can span\n * further, so nothing is missed. The work becomes linear in the atom count.\n *\n * Waters are skipped outright: they are a single oxygen with nothing to bond to\n * (their hydrogens are almost never in the file), so every comparison against\n * one is wasted, and there can be more of them than there are protein atoms.\n */\nexport function inferBonds(atoms: ProteinAtom[]): ProteinBond[] {\n const bonds: ProteinBond[] = []\n const cells = new Map<string, number[]>()\n const radii = new Float32Array(atoms.length)\n\n for (let i = 0; i < atoms.length; i++) {\n const atom = atoms[i]!\n if (atom.kind === \"water\") continue\n radii[i] = covalentRadius(atom.element)\n const key = cellKey(atom.x, atom.y, atom.z)\n const bucket = cells.get(key)\n if (bucket) bucket.push(i)\n else cells.set(key, [i])\n }\n\n for (const [key, bucket] of cells) {\n const [cx, cy, cz] = key.split(\",\").map(Number) as [number, number, number]\n\n for (let dx = -1; dx <= 1; dx++) {\n for (let dy = -1; dy <= 1; dy++) {\n for (let dz = -1; dz <= 1; dz++) {\n const neighbours = cells.get(`${cx + dx},${cy + dy},${cz + dz}`)\n if (!neighbours) continue\n\n for (const i of bucket) {\n for (const j of neighbours) {\n // Each unordered pair is reached from both cells, so keep only one\n // ordering. This also excludes an atom against itself.\n if (j <= i) continue\n const a = atoms[i]!\n const b = atoms[j]!\n const limit = radii[i]! + radii[j]! + BOND_TOLERANCE\n const dxx = a.x - b.x\n const dyy = a.y - b.y\n const dzz = a.z - b.z\n const squared = dxx * dxx + dyy * dyy + dzz * dzz\n if (squared > limit * limit) continue\n if (squared < MIN_BOND_LENGTH * MIN_BOND_LENGTH) continue\n bonds.push({ a: i, b: j })\n }\n }\n }\n }\n }\n }\n\n return bonds\n}\n\n/** The grid cell a coordinate falls in, as a map key. */\nfunction cellKey(x: number, y: number, z: number): string {\n const cx = Math.floor(x / MAX_BOND_LENGTH)\n const cy = Math.floor(y / MAX_BOND_LENGTH)\n const cz = Math.floor(z / MAX_BOND_LENGTH)\n return `${cx},${cy},${cz}`\n}\n\n// --- Chains ----------------------------------------------------------------\n\n/**\n * How far apart two consecutive α-carbons may sit and still be the same chain,\n * in Ångströms.\n *\n * Consecutive α-carbons are 3.8 Å apart — the distance is fixed by the peptide\n * bond's geometry, not by what the protein is doing — so a larger gap is a\n * *chain break*: a disordered loop the crystallographer could not resolve, which\n * the file records by simply skipping those residues. Drawing through one would\n * run a ribbon across the middle of the molecule between two ends that are not\n * joined. Nucleotide phosphorus atoms sit further apart (~6 Å), which is why the\n * threshold is well above 3.8 rather than snug against it.\n */\nconst CHAIN_BREAK_DISTANCE = 7.5\n\n/**\n * The backbone curve of every chain, in residue order, with each residue's\n * secondary structure attached.\n *\n * A chain break starts a **new trace** rather than being drawn through — see\n * {@link CHAIN_BREAK_DISTANCE} — so one chain identifier can produce several\n * entries. That is the right unit downstream anyway: each entry is one\n * continuous curve, which is exactly what a tube is swept along.\n */\nexport function traceChains(\n atoms: ProteinAtom[],\n spans: SecondarySpan[]\n): ProteinChain[] {\n const assign = secondaryLookup(spans)\n // Built ahead of the walk rather than during it, because the carbonyl comes\n // *after* the α-carbon in a residue's records and the trace point is written\n // as the α-carbon is reached. One pass over the atoms costs nothing beside\n // the bond inference already running over them.\n const carbonyls = carbonylDirections(atoms)\n const chains: ProteinChain[] = []\n\n let current: ProteinChain | null = null\n let previous: ProteinAtom | null = null\n\n for (const atom of atoms) {\n if (atom.name !== traceAtomName(atom.kind)) continue\n\n const broken =\n previous !== null &&\n (previous.chain !== atom.chain ||\n distance(previous, atom) > CHAIN_BREAK_DISTANCE)\n\n if (current === null || broken) {\n current = { id: atom.chain, trace: [] }\n chains.push(current)\n }\n\n current.trace.push({\n x: atom.x,\n y: atom.y,\n z: atom.z,\n secondary: assign(atom.chain, atom.residueSeq),\n index: current.trace.length,\n residueSeq: atom.residueSeq,\n normal: carbonyls.get(residueKey(atom)) ?? null,\n })\n previous = atom\n }\n\n // A single point is not a curve — nothing can be swept along it and no\n // direction can be taken from it — so a lone resolved residue is dropped\n // rather than left for the tube builder to divide by zero over.\n return chains.filter((chain) => chain.trace.length > 1)\n}\n\n/**\n * Builds the residue → secondary-structure lookup.\n *\n * A map per chain of residue number to kind, rather than a scan of the span list\n * per residue: a large structure has thousands of residues and hundreds of\n * spans, and the product of the two is the sort of quiet quadratic that only\n * shows up on the file somebody actually cares about.\n */\nfunction secondaryLookup(\n spans: SecondarySpan[]\n): (chain: string, residue: number) => SecondaryStructure {\n const byChain = new Map<string, Map<number, SecondaryStructure>>()\n\n for (const span of spans) {\n let residues = byChain.get(span.chain)\n if (!residues) {\n residues = new Map()\n byChain.set(span.chain, residues)\n }\n // A malformed span (end before start, or one covering the whole file) would\n // otherwise fill the map with junk; the loop simply doesn't run backwards.\n for (let seq = span.start; seq <= span.end; seq++) {\n residues.set(seq, span.kind)\n }\n }\n\n return (chain, residue) => byChain.get(chain)?.get(residue) ?? \"coil\"\n}\n\n/** A residue's identity across the whole file — its chain and its number. */\nfunction residueKey(atom: ProteinAtom): string {\n return `${atom.chain}:${atom.residueSeq}`\n}\n\n/**\n * The unit CA→O vector of every amino-acid residue that has both atoms.\n *\n * This is the one piece of chemistry a *cartoon* needs and a tube does not. A\n * ribbon has a width, so it has a side, and nothing about a curve through\n * α-carbons says which way round it should be: sweep a flat band along that\n * curve with an arbitrary frame and a helix comes out as a twisted ribbon in\n * the wrong plane, its face turning at random rather than with the fold.\n *\n * The carbonyl is what every molecular viewer resolves that with. It points\n * roughly across the chain, it is what hydrogen-bonds a helix to itself and a\n * strand to its neighbour, and so it turns *with* the structure — which makes a\n * band swept along it lie the way the fold does.\n *\n * Amino acids only. A nucleotide's ribbon has no equivalent (its trace atom is\n * the phosphorus and its \"width\" is the base pair), and a residue whose file\n * gives no `O` — a Cα-only model, a truncated terminus — simply has no entry;\n * both fall back to the curve's own binormal in the ribbon builder.\n */\nfunction carbonylDirections(\n atoms: ProteinAtom[]\n): Map<string, { x: number; y: number; z: number }> {\n const alpha = new Map<string, ProteinAtom>()\n const oxygen = new Map<string, ProteinAtom>()\n\n for (const atom of atoms) {\n if (atom.kind !== \"amino\") continue\n // The first record of each name wins, which is what picks conformation A\n // out of a residue the file gives two of: alternates are written in order\n // and the ribbon needs one frame, not an average of two.\n if (atom.name === \"CA\") {\n if (!alpha.has(residueKey(atom))) alpha.set(residueKey(atom), atom)\n } else if (atom.name === \"O\") {\n if (!oxygen.has(residueKey(atom))) oxygen.set(residueKey(atom), atom)\n }\n }\n\n const directions = new Map<string, { x: number; y: number; z: number }>()\n for (const [key, ca] of alpha) {\n const o = oxygen.get(key)\n if (!o) continue\n const x = o.x - ca.x\n const y = o.y - ca.y\n const z = o.z - ca.z\n const length = Math.hypot(x, y, z)\n // A zero-length carbonyl is a duplicated record rather than a direction.\n if (length < 1e-6) continue\n directions.set(key, { x: x / length, y: y / length, z: z / length })\n }\n return directions\n}\n\n/** Straight-line distance between two atoms, in Ångströms. */\nfunction distance(a: ProteinAtom, b: ProteinAtom): number {\n return Math.hypot(a.x - b.x, a.y - b.y, a.z - b.z)\n}\n\n// --- Bounds ----------------------------------------------------------------\n\n/**\n * The molecule's centroid and the radius of the sphere about it that holds every\n * atom.\n *\n * The centroid rather than the centre of the bounding box: a structure with one\n * long tail — a coiled-coil, a nucleic acid strand hanging off a complex —\n * has a box whose centre is nowhere near the mass of it, and the camera would\n * frame the empty half.\n *\n * The radius is floored above zero so a single-atom file (or one whose\n * coordinates are all identical) still gives the camera a distance to work from\n * rather than parking it inside the atom.\n */\nfunction measure(atoms: ProteinAtom[]): {\n center: { x: number; y: number; z: number }\n radius: number\n} {\n let sx = 0\n let sy = 0\n let sz = 0\n for (const atom of atoms) {\n sx += atom.x\n sy += atom.y\n sz += atom.z\n }\n const center = {\n x: sx / atoms.length,\n y: sy / atoms.length,\n z: sz / atoms.length,\n }\n\n let radius = 0\n for (const atom of atoms) {\n const d = Math.hypot(\n atom.x - center.x,\n atom.y - center.y,\n atom.z - center.z\n )\n if (d > radius) radius = d\n }\n\n return { center, radius: Math.max(radius, 1) }\n}\n", "/**\n * Reads a coordinate file into a {@link ProteinStructure}.\n *\n * Two formats, because the Protein Data Bank serves two and which one an entry\n * has is not the author's choice: the legacy **PDB** format numbers atoms in\n * five columns and names chains in one, so an entry that outgrew either — a\n * ribosome, a capsid — exists only as **mmCIF**. Refusing the second would mean\n * refusing exactly the structures most worth looking at.\n *\n * Both are read the same way: pull out an atom list and whatever secondary\n * structure the file annotates, then hand both to `deriveStructure`, which does\n * the geometry. {@link parseStructure} sniffs which is which, so a caller\n * holding a downloaded file never has to.\n *\n * **The first model only.** An NMR ensemble is twenty superposed conformations\n * of the same molecule; drawing all of them at once produces a blur, and\n * choosing between them is a question this node does not ask. The first is the\n * conventional representative.\n */\n\nimport { elementOf, residueKind } from \"./chemistry\"\nimport {\n deriveStructure,\n EMPTY_STRUCTURE,\n type ProteinAtom,\n type ProteinStructure,\n type SecondarySpan,\n} from \"./structure\"\n\n/**\n * Reads a coordinate file, detecting its format from the contents.\n *\n * `id` labels the result — an accession, or the asset's name. Anything that\n * can't be read at all comes back as {@link EMPTY_STRUCTURE} rather than\n * throwing: this runs against a file someone just uploaded or an accession\n * someone is halfway through typing, and both of those are ordinary states\n * rather than errors. An empty structure draws nothing, which is the honest\n * picture of a file with no atoms in it.\n */\nexport function parseStructure(text: string, id = \"\"): ProteinStructure {\n if (typeof text !== \"string\" || text.trim() === \"\") {\n return { ...EMPTY_STRUCTURE, id }\n }\n return isMmcif(text) ? parseMmcif(text, id) : parsePdb(text, id)\n}\n\n/**\n * Whether a file is mmCIF.\n *\n * The `data_` block header is mmCIF's first line and appears in no PDB file, so\n * it decides on its own — but only over the first stretch, since `data_` is also\n * an ordinary substring that could turn up in a PDB `REMARK`. The category\n * prefix is the belt-and-braces check for a fragment handed over without its\n * header.\n */\nfunction isMmcif(text: string): boolean {\n const head = text.slice(0, 4096)\n return /^\\s*data_/.test(head) || head.includes(\"_atom_site.\")\n}\n\n// --- PDB -------------------------------------------------------------------\n\n/**\n * Reads the legacy PDB format, which is **fixed-column**: every field is at a\n * known offset and the whitespace between them is padding rather than a\n * separator. Splitting on spaces is the classic way to get this wrong — a\n * residue number that runs into its insertion code, or a `-` sign that eats the\n * gap between two coordinates, and the record silently comes apart.\n *\n * Columns, 1-based as the specification numbers them:\n *\n * 7–11 serial 13–16 name 18–20 resName 22 chainID\n * 23–26 resSeq 31–38 x 39–46 y 47–54 z\n * 77–78 element\n */\nfunction parsePdb(text: string, id: string): ProteinStructure {\n const atoms: ProteinAtom[] = []\n const spans: SecondarySpan[] = []\n let title = \"\"\n\n for (const line of text.split(\"\\n\")) {\n const record = line.slice(0, 6).trim()\n\n // Everything after the first `ENDMDL` is another conformation of what we\n // already have. Stopping at it rather than filtering by model number also\n // ends the read early on a large ensemble.\n if (record === \"ENDMDL\") break\n\n if (record === \"ATOM\" || record === \"HETATM\") {\n const atom = parsePdbAtom(line, record === \"HETATM\")\n if (atom) atoms.push(atom)\n continue\n }\n\n if (record === \"TITLE\") {\n // A long title is continued across several records, with the continuation\n // number in columns 9–10 and the text always from column 11.\n title = `${title} ${line.slice(10).trim()}`.trim()\n continue\n }\n\n // The two annotation records. Their first-residue fields sit at *different*\n // offsets from each other — a genuine wart of the format rather than a\n // mistake here — while their last-residue fields agree.\n if (record === \"HELIX\") {\n const span = pdbSpan(line, 19, 21, \"helix\")\n if (span) spans.push(span)\n continue\n }\n if (record === \"SHEET\") {\n const span = pdbSpan(line, 21, 22, \"sheet\")\n if (span) spans.push(span)\n }\n }\n\n return deriveStructure(id, title, atoms, spans)\n}\n\n/** One `ATOM`/`HETATM` record, or `null` when its coordinates don't read. */\nfunction parsePdbAtom(line: string, hetero: boolean): ProteinAtom | null {\n // Truncated before the coordinates. Worth checking outright: a short slice of\n // a fixed-column record reads as the empty string, and `Number(\"\")` is 0\n // rather than `NaN` — so without this a mangled line becomes an atom sitting\n // at the origin, which is far harder to notice than a missing one.\n if (line.length < 54) return null\n\n // An alternate location: the same atom modelled twice because the side chain\n // is disordered. Keeping both would double the sticks through that residue,\n // so take the first (blank or `A`), which is the convention.\n const altLoc = line.slice(16, 17).trim()\n if (altLoc !== \"\" && altLoc !== \"A\") return null\n\n const x = Number(line.slice(30, 38))\n const y = Number(line.slice(38, 46))\n const z = Number(line.slice(46, 54))\n if (!Number.isFinite(x) || !Number.isFinite(y) || !Number.isFinite(z)) {\n return null\n }\n\n const name = line.slice(12, 16).trim()\n const residue = line.slice(17, 20).trim()\n const kind = residueKind(residue)\n\n return {\n serial: Number(line.slice(6, 11)) || 0,\n name,\n element: elementOf(line.slice(76, 78), name, kind),\n residue,\n residueSeq: Number(line.slice(22, 26)) || 0,\n // A file with a single unnamed chain leaves the column blank; calling that\n // `A` keeps \"group by chain\" from producing one group named nothing.\n chain: line.slice(21, 22).trim() || \"A\",\n x,\n y,\n z,\n hetero,\n kind,\n }\n}\n\n/** Where the last residue's number sits on both `HELIX` and `SHEET` — columns 34–37. */\nconst PDB_SPAN_END_AT = 33\n\n/**\n * One `HELIX`/`SHEET` record as a residue span.\n *\n * The first-residue offsets differ between the two records, so they are passed\n * in rather than hard-coded: a helix names its chain in column 20 and its first\n * residue in 22–25, a sheet names its chain in 22 and its first residue in\n * 23–26. Their *last* residue is in the same place in both, which is the one\n * thing they agree on.\n */\nfunction pdbSpan(\n line: string,\n chainAt: number,\n startAt: number,\n kind: SecondarySpan[\"kind\"]\n): SecondarySpan | null {\n const chain = line.slice(chainAt, chainAt + 1).trim() || \"A\"\n // Read as text first: a blank fixed-column field slices to `\"\"`, and\n // `Number(\"\")` is 0, so a `Number.isFinite` check alone would accept a record\n // that states nothing as a span over residue 0.\n const startText = line.slice(startAt, startAt + 4).trim()\n const endText = line.slice(PDB_SPAN_END_AT, PDB_SPAN_END_AT + 4).trim()\n if (startText === \"\" || endText === \"\") return null\n\n const start = Number(startText)\n const end = Number(endText)\n if (!Number.isFinite(start) || !Number.isFinite(end)) return null\n return { chain, start, end, kind }\n}\n\n// --- mmCIF -----------------------------------------------------------------\n\n/**\n * Reads mmCIF, which is a tagged format rather than a positional one: values are\n * whitespace-separated and their meaning comes from the column headers declared\n * above them, so the reader has to learn the layout before it can read a row.\n *\n * Only three categories are consulted — the atoms, and the two that annotate\n * secondary structure. mmCIF carries dozens more, and every one this doesn't\n * read is one this can't be broken by.\n */\nfunction parseMmcif(text: string, id: string): ProteinStructure {\n const lines = text.split(\"\\n\")\n const atoms = mmcifAtoms(lines)\n const spans = [\n ...mmcifSpans(lines, \"_struct_conf\", \"helix\"),\n ...mmcifSpans(lines, \"_struct_sheet_range\", \"sheet\"),\n ]\n return deriveStructure(id, mmcifTitle(lines), atoms, spans)\n}\n\n/** The atoms of the first model in an `_atom_site` loop. */\nfunction mmcifAtoms(lines: string[]): ProteinAtom[] {\n const atoms: ProteinAtom[] = []\n let model: string | null = null\n\n for (const row of mmcifRows(lines, \"_atom_site\")) {\n // `auth_*` is the numbering the literature uses and the one a PDB file would\n // have carried; `label_*` is the internal, re-derived scheme. Preferring\n // auth means a residue number quoted in a paper matches what is drawn, and\n // falling back keeps a file that omits it readable.\n const chain = row(\"auth_asym_id\") || row(\"label_asym_id\") || \"A\"\n const residue = row(\"auth_comp_id\") || row(\"label_comp_id\")\n const name = row(\"auth_atom_id\") || row(\"label_atom_id\")\n\n const thisModel = row(\"pdbx_PDB_model_num\")\n if (model === null) model = thisModel\n else if (thisModel !== model) break\n\n const altLoc = row(\"label_alt_id\")\n if (altLoc !== \"\" && altLoc !== \".\" && altLoc !== \"?\" && altLoc !== \"A\") {\n continue\n }\n\n const x = Number(row(\"Cartn_x\"))\n const y = Number(row(\"Cartn_y\"))\n const z = Number(row(\"Cartn_z\"))\n if (!Number.isFinite(x) || !Number.isFinite(y) || !Number.isFinite(z)) {\n continue\n }\n\n const kind = residueKind(residue)\n atoms.push({\n serial: Number(row(\"id\")) || 0,\n name,\n element: elementOf(row(\"type_symbol\"), name, kind),\n residue,\n residueSeq: Number(row(\"auth_seq_id\") || row(\"label_seq_id\")) || 0,\n chain,\n x,\n y,\n z,\n hetero: row(\"group_PDB\") === \"HETATM\",\n kind,\n })\n }\n\n return atoms\n}\n\n/** The residue spans of one annotation category. */\nfunction mmcifSpans(\n lines: string[],\n category: string,\n kind: SecondarySpan[\"kind\"]\n): SecondarySpan[] {\n const spans: SecondarySpan[] = []\n\n for (const row of mmcifRows(lines, category)) {\n // `_struct_conf` covers turns and bends as well as helices, all under one\n // category and told apart by this tag. A sheet range has no such tag, so an\n // absent one passes.\n const type = row(\"conf_type_id\")\n if (type !== \"\" && !type.startsWith(\"HELX\")) continue\n\n const chain = row(\"beg_auth_asym_id\") || row(\"beg_label_asym_id\") || \"A\"\n const start = Number(row(\"beg_auth_seq_id\") || row(\"beg_label_seq_id\"))\n const end = Number(row(\"end_auth_seq_id\") || row(\"end_label_seq_id\"))\n if (!Number.isFinite(start) || !Number.isFinite(end)) continue\n spans.push({ chain, start, end, kind })\n }\n\n return spans\n}\n\n/** The entry's title, from the `_struct.title` item. */\nfunction mmcifTitle(lines: string[]): string {\n for (let i = 0; i < lines.length; i++) {\n const line = lines[i]!\n if (!line.startsWith(\"_struct.title\")) continue\n const inline = line.slice(\"_struct.title\".length).trim()\n // A value too long for the line is carried on the next one, or in a\n // semicolon-delimited block below it; the inline form covers the rest.\n if (inline !== \"\") return unquote(inline)\n const next = lines[i + 1]?.trim() ?? \"\"\n return unquote(next.startsWith(\";\") ? next.slice(1) : next)\n }\n return \"\"\n}\n\n/**\n * Walks the rows of one mmCIF category, yielding a field reader for each.\n *\n * The reader is a closure over the current row rather than an object, so\n * nothing allocates a record per atom — this runs over hundreds of thousands of\n * rows on a large entry, and the caller only ever wants a handful of the fields.\n *\n * Handles both forms a category takes: a `loop_` with a header block and many\n * rows, and the flat `_category.field value` form mmCIF uses when there is\n * exactly one row.\n */\nfunction* mmcifRows(\n lines: string[],\n category: string\n): Generator<(field: string) => string> {\n const prefix = `${category}.`\n\n for (let i = 0; i < lines.length; i++) {\n const line = lines[i]!.trim()\n\n // The flat, single-row form: consecutive `_category.field value` lines.\n if (line.startsWith(prefix)) {\n const single = new Map<string, string>()\n while (i < lines.length) {\n const item = lines[i]!.trim()\n if (!item.startsWith(prefix)) break\n const gap = item.search(/\\s/)\n if (gap === -1) {\n // A field whose value is on the following line.\n single.set(item.slice(prefix.length), unquote(lines[++i]?.trim() ?? \"\"))\n } else {\n single.set(\n item.slice(prefix.length, gap),\n unquote(item.slice(gap).trim())\n )\n }\n i++\n }\n yield (field) => single.get(field) ?? \"\"\n return\n }\n\n if (line !== \"loop_\") continue\n\n // The header block: every `_category.field` line, in the order the values\n // will arrive in.\n const columns = new Map<string, number>()\n let cursor = i + 1\n while (cursor < lines.length) {\n const header = lines[cursor]!.trim()\n if (!header.startsWith(\"_\")) break\n if (header.startsWith(prefix)) {\n columns.set(header.slice(prefix.length), columns.size)\n }\n cursor++\n }\n // A `loop_` for some other category. Its rows carry no leading underscore\n // and aren't `loop_`, so the outer scan walks past them harmlessly.\n if (columns.size === 0) continue\n\n for (; cursor < lines.length; cursor++) {\n const row = lines[cursor]!\n const trimmed = row.trim()\n // A loop ends at the next block, the next loop, or a blank line.\n if (trimmed === \"\" || trimmed === \"#\" || trimmed.startsWith(\"_\")) break\n if (trimmed === \"loop_\" || trimmed.startsWith(\"data_\")) break\n\n const values = splitCifRow(trimmed)\n yield (field) => {\n const index = columns.get(field)\n if (index === undefined) return \"\"\n const value = values[index] ?? \"\"\n return value === \".\" || value === \"?\" ? \"\" : value\n }\n }\n return\n }\n}\n\n/**\n * Splits one mmCIF row into values, respecting quotes.\n *\n * Quoting is not decoration here: a chain identifier can be `'A'`, and a residue\n * name can contain a space that a plain `split` would turn into two columns and\n * shift every field after it by one.\n */\nfunction splitCifRow(row: string): string[] {\n const values: string[] = []\n let i = 0\n\n while (i < row.length) {\n const char = row[i]!\n if (char === \" \" || char === \"\\t\") {\n i++\n continue\n }\n if (char === \"'\" || char === '\"') {\n const end = row.indexOf(char, i + 1)\n if (end === -1) {\n values.push(row.slice(i + 1))\n break\n }\n values.push(row.slice(i + 1, end))\n i = end + 1\n continue\n }\n let end = i\n while (end < row.length && row[end] !== \" \" && row[end] !== \"\\t\") end++\n values.push(row.slice(i, end))\n i = end\n }\n\n return values\n}\n\n/** Strips the quotes mmCIF wraps a value in, and the null placeholders. */\nfunction unquote(value: string): string {\n const text = value.trim()\n if (text === \".\" || text === \"?\") return \"\"\n const first = text[0]\n if ((first === \"'\" || first === '\"') && text.endsWith(first) && text.length > 1) {\n return text.slice(1, -1)\n }\n return text\n}\n", "import {\n command,\n driveCommand,\n easeInOut,\n node,\n type Command,\n type CommandArgs,\n Canvas3D,\n Geo,\n Graphics3D,\n Mat,\n Scene3D,\n property,\n type BufferGeometry3D,\n type Canvas3DProps,\n type Color,\n type NodeConfig,\n type Transform3D,\n type Vector3,\n} from \"@motionscript/core\"\n\nimport {\n lerpColor3D,\n cameraOrbit,\n resolveColor3D,\n snapValue,\n orbitProps,\n type OrbitTarget,\n} from \"@motionscript/core/component\"\nimport {\n atomColors,\n atomRadius,\n backboneRuns,\n DEFAULT_PROTEIN_PALETTE,\n hexOf,\n lerpHexColor,\n stickPlacement,\n traceColors,\n type ProteinColorScheme,\n type ProteinPalette,\n type ProteinRepresentation,\n} from \"./shared\"\nimport { cartoonGeometry, cartoonSpans } from \"./ribbon\"\nimport {\n EMPTY_STRUCTURE,\n type ProteinAtom,\n type ProteinStructure,\n} from \"./structure\"\n\nexport interface ProteinProps extends Canvas3DProps {\n /**\n * The molecule to draw, already parsed.\n *\n * A value rather than a source, and that is the whole arrangement: reading a\n * coordinate file means a network round-trip or a file read, and this node is\n * constructed inside a synchronous render pass. So the fetching and the\n * parsing happen *before* the build — see `parseStructure` and the app's\n * structure store — and what arrives here is finished data. It is the same\n * bargain a chart makes with its rows.\n */\n structure: ProteinStructure\n /** How the molecule is drawn. See {@link ProteinRepresentation}. */\n representation: ProteinRepresentation\n /** What decides an atom's colour. See {@link ProteinColorScheme}. */\n colorScheme: ProteinColorScheme\n /** The colour the `uniform` scheme paints, and every scheme's fallback. */\n color: Color\n /**\n * Every colour the *other* schemes paint from — see {@link ProteinPalette},\n * which is the shape these are read as rather than a prop of its own.\n *\n * Thirteen props rather than one palette because a prop is a whole value: a\n * single one would make a `to` that reddens the helices a statement about the\n * eight chain colours too, and the ones the author never touched would snap.\n * Each of these carries over and tweens on its own, like every other colour in\n * the app.\n */\n helixColor: string\n sheetColor: string\n coilColor: string\n /** The two ends of the N→C ramp, swept through HSL between them. */\n residueStartColor: string\n residueEndColor: string\n /** One per chain, cycling past the eighth. */\n chainColor1: string\n chainColor2: string\n chainColor3: string\n chainColor4: string\n chainColor5: string\n chainColor6: string\n chainColor7: string\n chainColor8: string\n /**\n * Atom size as a fraction of the true van der Waals radius.\n *\n * 1 is the space-filling model — atoms at the size they actually are, touching\n * their neighbours. Ball-and-stick shrinks them further on its own, so this\n * stays \"how big are the atoms\" in both rather than meaning something\n * different in each.\n */\n atomScale: number\n /** Bond stick radius, in Ångströms. */\n bondRadius: number\n /** Backbone tube radius, in Ångströms. A ribbon swells past this on a helix. */\n ribbonRadius: number\n /** Whether to draw ligands, ions and cofactors — everything that isn't polymer. */\n ligands: boolean\n /** Whether to draw crystallographic waters. Off, because there are hundreds. */\n waters: boolean\n /** Mesh detail: segments around a sphere or a stick. */\n quality: number\n /** Camera orbit about the vertical axis, in **degrees**. Animate this to spin. */\n orbit: number\n /** Camera elevation above the horizon, in **degrees**. */\n elevation: number\n /**\n * How far back the camera sits, as a multiple of the molecule's own radius.\n *\n * A multiple rather than a distance, because the things this node draws differ\n * in size by two orders of magnitude — crambin is 12 Å across and a ribosome\n * is 300 — and a stored distance that framed one would put the other off\n * screen or inside the camera. As a ratio the same number frames both, and it\n * goes on meaning the same thing when the structure is swapped.\n */\n zoom: number\n /** Strength of the ambient fill. */\n ambientIntensity: number\n /** Colour of the ambient fill. White leaves the molecule's own colours alone. */\n ambientColor: Color\n /** Strength of the key light; the rim light is derived from it. */\n keyIntensity: number\n /** Colour of the key light, and of the rim derived from it. */\n keyColor: Color\n}\n\n/** {@link ProteinProps.zoom}, held inside what can actually be framed. */\nconst MIN_ZOOM = 0.2\nconst MAX_ZOOM = 12\nconst DEFAULT_ZOOM = 2.6\n\n/**\n * How much of its true size an atom is drawn at in ball-and-stick, on top of\n * whatever {@link ProteinProps.atomScale} says.\n *\n * Ball-and-stick is not a smaller space-filling model — it is a different claim\n * about what matters. Atoms at a quarter of their radius stop touching, which is\n * what makes room for the bonds that are the whole point of the representation.\n */\nconst BALL_SCALE = 0.28\n\n/**\n * A molecular structure viewer.\n *\n * Extends {@link Canvas3D} — as `Graph3D` does — so this class only has to say\n * *what* to draw, and the bridge from the 2D scene graph into the 3D renderer is\n * the base class's business. It overrides `buildScene3D`, the single seam both\n * the real render and the asset declaration pass go through.\n *\n * <Protein width=\"fill\" height=\"fill\"\n * structure={parseStructure(text, \"6LU7\")}\n * representation=\"ribbon\" colorScheme=\"secondary\" />\n *\n * **There is no pointer here.** Every other molecular viewer is driven by\n * dragging the molecule around, and this one cannot be: the studio renders a\n * timeline, so the camera has to be something a `to` command can tween and a\n * scrub can reproduce exactly — frame 300 must be identical whether it was\n * reached by playing forward or by dragging the playhead backwards. So the\n * camera is three tweenable numbers (`orbit`, `elevation`, `zoom`), and spinning\n * the molecule is a command rather than a gesture.\n *\n * Atoms and bonds are drawn **instanced**: one sphere geometry and one cylinder\n * geometry, placed thousands of times in a single draw call each. A mesh per\n * atom would be tens of thousands of draw calls a frame, which no renderer\n * survives — and the instance lists themselves are cached (see {@link built}),\n * because they don't change when the camera moves and the camera is what\n * normally moves.\n */\n@node({\n key: \"protein\",\n parentKey: \"node\",\n forkable: true,\n layout: {\n children: \"freeform\",\n acceptsChildren: false,\n defaultWidthMode: \"fixed\",\n defaultHeightMode: \"fixed\",\n },\n seed: {\n width: 720,\n height: 560,\n },\n})\nexport class Protein extends Canvas3D<ProteinProps> {\n /**\n * The molecule.\n *\n * Snaps rather than interpolating, and every other non-scalar prop here\n * interpolates. There is no halfway point between two different molecules —\n * atom 4,000 of one is not the same atom as atom 4,000 of the other, and\n * blending their coordinates would produce a cloud that is neither. Swapping\n * the structure is a change of *subject*, so it lands at the end of a tween\n * the way a change of text does.\n */\n @property({ default: EMPTY_STRUCTURE, tween: snapValue })\n declare structure: ProteinStructure\n\n @property({ default: \"ribbon\", tween: snapValue })\n declare representation: ProteinRepresentation\n\n @property({ default: \"secondary\", tween: snapValue })\n declare colorScheme: ProteinColorScheme\n\n @property({ default: \"#4f9dff\", mapper: resolveColor3D, tween: lerpColor3D })\n declare color: Color\n\n /**\n * The palette, thirteen props of it — see {@link ProteinProps.helixColor} for\n * why it is thirteen and not one.\n *\n * No `mapper`, unlike the three `Color` props around them: these stay hex\n * strings because a hex string is what an instance tint is handed, and\n * {@link lerpHexColor} is the tween that goes with that. The defaults are\n * written out rather than read off {@link DEFAULT_PROTEIN_PALETTE} because a\n * decorator default has to be a literal the class carries; the two are pinned\n * against each other by a test.\n */\n @property({ default: \"#f0605d\", tween: lerpHexColor })\n declare helixColor: string\n\n @property({ default: \"#f5c451\", tween: lerpHexColor })\n declare sheetColor: string\n\n @property({ default: \"#c8ccd4\", tween: lerpHexColor })\n declare coilColor: string\n\n @property({\n default: DEFAULT_PROTEIN_PALETTE.residueStart,\n tween: lerpHexColor,\n })\n declare residueStartColor: string\n\n @property({\n default: DEFAULT_PROTEIN_PALETTE.residueEnd,\n tween: lerpHexColor,\n })\n declare residueEndColor: string\n\n @property({ default: \"#4f9dff\", tween: lerpHexColor })\n declare chainColor1: string\n\n @property({ default: \"#ff8a3d\", tween: lerpHexColor })\n declare chainColor2: string\n\n @property({ default: \"#4ddb9a\", tween: lerpHexColor })\n declare chainColor3: string\n\n @property({ default: \"#e05fd0\", tween: lerpHexColor })\n declare chainColor4: string\n\n @property({ default: \"#ffd166\", tween: lerpHexColor })\n declare chainColor5: string\n\n @property({ default: \"#8b7bff\", tween: lerpHexColor })\n declare chainColor6: string\n\n @property({ default: \"#ff6b6b\", tween: lerpHexColor })\n declare chainColor7: string\n\n @property({ default: \"#3fd0d6\", tween: lerpHexColor })\n declare chainColor8: string\n\n @property({ default: 1 }) declare atomScale: number\n @property({ default: 0.18 }) declare bondRadius: number\n @property({ default: 0.45 }) declare ribbonRadius: number\n\n @property({ default: true, tween: snapValue }) declare ligands: boolean\n @property({ default: false, tween: snapValue }) declare waters: boolean\n\n /**\n * Structural, like `Graph3D.segments`: a geometry is immutable, so every\n * intermediate value of a tween reallocates the sphere and the cylinder. Set\n * once; the appearance schema marks it non-animatable to say the same thing\n * from the other side.\n */\n @property({ default: 12 }) declare quality: number\n\n @property({ default: 35 }) declare orbit: number\n @property({ default: 18 }) declare elevation: number\n @property({ default: DEFAULT_ZOOM }) declare zoom: number\n\n /**\n * The default rig, and only the default rig — the same four controls the 3D\n * viewport carries, in the same order and with the same defaults, because\n * \"how is this lit\" is one question and a molecule is not a special case of\n * it. Each light is a **strength and a colour**, in that order: how much\n * light, and what colour it is.\n *\n * Both colours default to white rather than to a tint, so a rig nobody has\n * touched grades nothing. A coloured default would be a look applied to every\n * structure in every scene by a control the author never opened.\n */\n @property({ default: 0.55 }) declare ambientIntensity: number\n\n @property({ default: \"#ffffff\", mapper: resolveColor3D, tween: lerpColor3D })\n declare ambientColor: Color\n\n @property({ default: 1.6 }) declare keyIntensity: number\n\n @property({ default: \"#ffffff\", mapper: resolveColor3D, tween: lerpColor3D })\n declare keyColor: Color\n\n constructor(props?: NodeConfig<Protein, ProteinProps>) {\n super(props as NodeConfig<Canvas3D<ProteinProps>, ProteinProps>)\n }\n\n /**\n * Frame the scene at a spherical camera placement. Every axis is optional,\n * so \"pull back\" and \"spin round\" stay separate intentions — see\n * {@link OrbitTarget}.\n */\n @command({\n args: [\n {\n key: \"target\", kind: \"orbit\", properties: {\n orbit: { kind: \"number\", step: 1, unit: \"°\" },\n elevation: { kind: \"number\", min: -89, max: 89, step: 1, unit: \"°\" },\n zoom: { kind: \"number\", min: 0.2, max: 24, step: 0.1, unit: \"×\" },\n },\n },\n ],\n })\n orbitTo(args: CommandArgs<{ target: OrbitTarget }> & { duration: number }): Command<ProteinProps> {\n return this.to({\n data: orbitProps(args.data?.target ?? {}) as Partial<ProteinProps>,\n duration: args.duration,\n easing: args.easing ?? easeInOut(),\n })\n }\n\n /**\n * Drift the camera around the molecule for the command's duration — a\n * continuous orbit at `speed` with a lateral `sway` and a `zoom` pulse\n * (`breathe`), both on a shared `cycle` so they don't beat against each\n * other. `settle` eases in from wherever the camera already is, so `float`\n * after an `orbitTo` does not snap.\n */\n @command({\n args: [\n { key: \"speed\", kind: \"number\", default: 36, min: -360, max: 360, step: 1, unit: \"°/s\" },\n { key: \"elevation\", kind: \"number\", default: 18, min: -89, max: 89, step: 1, unit: \"°\" },\n { key: \"zoom\", kind: \"number\", default: 2.6, min: 0.2, max: 12, step: 0.1, unit: \"×\" },\n { key: \"sway\", kind: \"number\", default: 6, min: 0, max: 45, step: 1, unit: \"°\" },\n { key: \"breathe\", kind: \"number\", default: 0.08, min: 0, max: 0.6, step: 0.01, scale: 100, unit: \"%\" },\n { key: \"cycle\", kind: \"number\", default: 8, min: 0.5, max: 60, step: 0.5, unit: \"s\" },\n { key: \"settle\", kind: \"number\", default: 1, min: 0, max: 20, step: 0.1, unit: \"s\" },\n ],\n })\n float(args: CommandArgs<{\n speed?: number\n elevation?: number\n zoom?: number\n sway?: number\n breathe?: number\n cycle?: number\n settle?: number\n }> & { duration?: number }): Command<ProteinProps> {\n const {\n speed = 36,\n elevation = 18,\n zoom = 2.6,\n sway = 6,\n breathe = 0.08,\n cycle = 8,\n settle = 1,\n } = args.data ?? {}\n const duration = args.duration ?? 8\n const easing = args.easing ?? easeInOut()\n\n const fromOrbit = this.orbit\n const fromElevation = this.elevation\n const fromZoom = this.zoom\n const period = Math.max(cycle, 0.0001)\n\n return driveCommand(duration, (t) => {\n const seconds = t * duration\n // Ease from the camera's current placement into the drift over `settle`\n // seconds. A zero `settle` starts the drift immediately.\n const blend = settle <= 0 ? 1 : easing(Math.min(1, seconds / settle))\n const phase = (seconds / period) * Math.PI * 2\n\n const driftOrbit = fromOrbit + speed * seconds + Math.sin(phase) * sway\n const driftZoom = zoom * (1 + Math.sin(phase) * breathe)\n\n this.set({\n orbit: fromOrbit + (driftOrbit - fromOrbit) * blend,\n elevation: fromElevation + (elevation - fromElevation) * blend,\n zoom: fromZoom + (driftZoom - fromZoom) * blend,\n } as Partial<ProteinProps>)\n }) as Command<ProteinProps>\n }\n\n /**\n * The thirteen colour props, as the one value the colourers take.\n *\n * Rebuilt per call rather than cached: it is thirteen property reads behind\n * `built()`'s own cache, which is what stops it happening per frame.\n */\n private paletteOf(): ProteinPalette {\n return {\n helix: this.helixColor,\n sheet: this.sheetColor,\n coil: this.coilColor,\n residueStart: this.residueStartColor,\n residueEnd: this.residueEndColor,\n chains: [\n this.chainColor1,\n this.chainColor2,\n this.chainColor3,\n this.chainColor4,\n this.chainColor5,\n this.chainColor6,\n this.chainColor7,\n this.chainColor8,\n ],\n }\n }\n\n // ---- Drawing ------------------------------------------------------------\n\n protected override buildScene3D(): Scene3D {\n const scene = new Scene3D()\n const structure = this.structure\n // Framed on what is actually **drawn**, not on the whole file. A crystal\n // structure carries hundreds of ordered waters scattered well beyond the\n // molecule, and they are hidden by default — framing the sphere that holds\n // them would park the camera 40% further back than the picture needs, which\n // reads as a molecule adrift in an empty box.\n const radius = this.built().radius\n const distance = radius * clampZoom(this.zoom)\n\n // The clipping planes are cut to the molecule rather than fixed, because\n // \"far enough away to see all of it\" differs a hundredfold between a peptide\n // and a virus capsid — and a depth buffer stretched over a range it doesn't\n // need spends its precision on empty space, which shows up as z-fighting\n // between atoms that are genuinely an Ångström apart.\n // Kept explicit rather than left to the camera's own derivation: that sizes\n // the planes to the scene's bounding box, and a molecule's *atoms* are what\n // need the precision here, not its extent.\n //\n // Polar placement is the camera's vocabulary now, so the three props pass\n // straight through; `cameraOrbit` is the quarter turn between the stored\n // zero and the camera's.\n scene.perspective({\n fov: 45,\n near: Math.max(0.1, radius * 0.02),\n far: distance + radius * 4,\n orbit: cameraOrbit(this.orbit),\n elevation: this.elevation,\n distance,\n })\n\n // The backdrop is the node's own **Fills** section — a `Canvas3D`\n // composites its 3D pass over its fill layers, so a paint stack behind a\n // molecule is not merely possible but strictly better than one colour, and\n // an empty stack is what transparency already means everywhere else in the\n // app. There is no 3D background pass to reach for and no clear colour to\n // set; see `resolveViewport3DFills`, which carries the colour this node\n // used to hold in its appearance bag across to that stack.\n\n // A key light, a weaker rim from the opposite side, and enough ambient that\n // an atom facing away is still an atom rather than a silhouette. Placed\n // relative to the molecule so the lighting holds at any size — a light's\n // parameters and its placement are separate arguments, because placement is\n // a transform rather than a property of the lamp.\n //\n // The rim takes the key's colour as well as a fraction of its strength: it\n // is the key seen from the other side rather than a light of its own, which\n // is why there are four controls and not six. The same rig, and the same\n // argument, as the 3D viewport's — see `Canvas3DView.buildScene3D`.\n scene\n .light({\n type: \"ambient\",\n intensity: this.ambientIntensity,\n color: this.ambientColor,\n })\n .light(\n {\n type: \"directional\",\n intensity: this.keyIntensity,\n color: this.keyColor,\n },\n { position: [radius, radius * 2, radius * 2] }\n )\n .light(\n {\n type: \"directional\",\n intensity: this.keyIntensity * 0.4,\n color: this.keyColor,\n },\n { position: [-radius * 2, -radius, -radius * 1.5] }\n )\n\n // Everything is drawn inside one group that puts the molecule's centroid on\n // the origin, so the camera — which always looks at the origin — frames it\n // wherever the crystallographer's coordinate system happened to put it. PDB\n // coordinates are in the crystal's frame and routinely sit hundreds of\n // Ångströms from zero.\n //\n // `begin`/`end` is what a `Graphics3D.group` was: a transform pushed over\n // everything drawn between them. It lives on the scene now rather than on\n // the graphics, because hierarchy is a property of the scene.\n const g3 = new Graphics3D()\n this.addMolecule(g3)\n\n const { center } = structure\n scene.begin({\n id: \"molecule\",\n transform: { position: [-center.x, -center.y, -center.z] },\n })\n scene.draw(g3)\n scene.end()\n\n return scene\n }\n\n /** The molecule itself, in whichever representation is showing. */\n private addMolecule(g3: Graphics3D): void {\n const built = this.built()\n\n this.addSpheres(g3, built.spheres, built.sphereTints, \"atoms\")\n this.addSticks(g3, built.sticks, built.stickTints, \"bonds\")\n\n for (const run of built.runs) {\n g3.mesh(\n Geo.tube({\n points: run.points,\n radius: run.radius,\n // `[along the curve, around it]`. Segments along the curve are\n // proportional to the run's own length, so the sweep follows it rather\n // than cutting corners across a tight turn — and a short run doesn't\n // pay for a long one's tessellation.\n segments: [Math.max(6, run.points.length * 4), this.stickQuality()],\n }),\n Mat.standard({ color: run.color, roughness: 0.45, metalness: 0.05 })\n )\n }\n\n for (const ribbon of built.ribbons) {\n g3.mesh(\n ribbon,\n // `vertexColors`, where every other material here takes a `color`. A\n // cartoon span is one mesh covering many residues and the colouring is\n // per residue, so the colour varies *within* the primitive — which is\n // the one thing a material's uniform colour cannot express. Safe in a\n // way it would not be on the instanced spheres (see {@link addSpheres}):\n // this geometry genuinely carries a `color` attribute.\n Mat.standard({\n vertexColors: true,\n roughness: 0.45,\n metalness: 0.05,\n })\n )\n }\n }\n\n /** One instanced sphere op, skipped entirely when there is nothing in it. */\n private addSpheres(\n g3: Graphics3D,\n placements: Transform3D[],\n tints: string[],\n key: string\n ): void {\n if (placements.length === 0) return\n const quality = this.sphereQuality()\n g3.instances(\n // A unit sphere, sized per instance. One geometry for the whole molecule:\n // the renderer dedupes by identity and uploads it once, where a radius\n // baked into the geometry would mean one upload per element.\n Geo.sphere({\n radius: 1,\n // `[longitude, latitude]` — half as many bands as meridians, which is\n // what a sphere wants and what three's own defaults do.\n segments: [quality, Math.max(4, Math.round(quality / 2))],\n }),\n // No `vertexColors` here, and that is the whole trick rather than an\n // omission. An instance's tint arrives as its own attribute and the\n // renderer samples it whenever one is present; asking for *vertex* colours\n // as well makes the shader read a per-vertex `color` the geometry doesn't\n // have, which supplies zeroes — and the molecule renders solid black.\n Mat.standard({ roughness: 0.35, metalness: 0.05 }),\n placements,\n { colors: tints, transform: { key } }\n )\n }\n\n /** One instanced cylinder op. */\n private addSticks(\n g3: Graphics3D,\n placements: Transform3D[],\n tints: string[],\n key: string\n ): void {\n if (placements.length === 0) return\n g3.instances(\n // A unit cylinder — one tall, centred on the origin, running along +Y —\n // which `stickPlacement` moves, turns and stretches onto each bond.\n Geo.cylinder({\n radius: 1,\n height: 1,\n segments: this.stickQuality(),\n // The ends are inside the atoms they join, so nothing can see them.\n capped: false,\n }),\n // Bare of `vertexColors` for the reason the spheres are — see above.\n Mat.standard({ roughness: 0.4, metalness: 0.05 }),\n placements,\n { colors: tints, transform: { key } }\n )\n }\n\n // ---- What gets drawn ----------------------------------------------------\n\n /**\n * Everything the builder needs, cached against the inputs that decide it.\n *\n * This is the node's one piece of memoisation and it earns its keep. The\n * builder runs every frame; between two frames the thing that has normally\n * changed is the camera; and none of what is cached here depends on the\n * camera. Without it, orbiting a 20,000-atom structure would rebuild a\n * 20,000-entry placement list and a 40,000-entry stick list sixty times a\n * second — for a picture whose atoms have not moved.\n *\n * The structure is compared by **identity** rather than by contents: it is a\n * parsed value produced once, upstream, so a different object genuinely means\n * a different molecule.\n */\n private cache: {\n key: string\n structure: ProteinStructure\n built: Built\n } | null = null\n\n private built(): Built {\n const structure = this.structure\n const key = [\n this.representation,\n this.colorScheme,\n hexOf(this.color),\n // The palette decides every colour the scheme paints, so a change to it\n // changes the tint arrays this cache is holding — exactly as `color` does.\n // Missing it meant editing a chain colour repainted nothing until\n // something else in this key happened to move.\n paletteKey(this.paletteOf()),\n this.ligands,\n this.waters,\n this.atomScale,\n this.bondRadius,\n this.ribbonRadius,\n // In the key now, where it never used to be: the cartoon's curve is\n // subdivided against the mesh detail (see `subdivisionsFor`), so a change\n // to it changes the geometry this cache is holding. The atoms never\n // needed it — `quality` reaches their spheres as a `segments` count the\n // renderer reads off the descriptor, not as anything built here.\n this.quality,\n ].join(\"|\")\n\n const cached = this.cache\n if (cached && cached.structure === structure && cached.key === key) {\n return cached.built\n }\n\n const built = this.build(structure)\n this.cache = { key, structure, built }\n return built\n }\n\n /** Builds the placement lists for the current representation. */\n private build(structure: ProteinStructure): Built {\n const palette = this.paletteOf()\n const colors = atomColors(\n structure,\n this.colorScheme,\n hexOf(this.color),\n palette\n )\n const showing = structure.atoms.map((atom) => this.isShowing(atom))\n const representation = this.representation\n const spaceFilling = representation === \"spacefill\"\n const atomic = spaceFilling || representation === \"ballAndStick\"\n\n // On a backbone or a ribbon the polymer is the curve, so only what *isn't*\n // polymer keeps its atoms — an inhibitor sitting in an active site is\n // usually the entire subject of the picture, and a bare backbone doesn't\n // show it. On the atomic representations everything showing is drawn.\n const drawAtom = (index: number): boolean => {\n if (!showing[index]) return false\n if (atomic) return true\n const kind = structure.atoms[index]!.kind\n return kind !== \"amino\" && kind !== \"nucleic\"\n }\n\n const built: Built = {\n spheres: [],\n sphereTints: [],\n sticks: [],\n stickTints: [],\n runs: [],\n ribbons: [],\n radius: structure.radius,\n }\n\n const scale = this.atomScale * (spaceFilling ? 1 : BALL_SCALE)\n for (let i = 0; i < structure.atoms.length; i++) {\n if (!drawAtom(i)) continue\n const atom = structure.atoms[i]!\n built.spheres.push({\n position: [atom.x, atom.y, atom.z],\n scale: atomRadius(atom, scale),\n })\n built.sphereTints.push(colors[i]!)\n }\n\n // A space-filling model has no visible bonds — the atoms are at their true\n // radii and already overlapping — so the sticks are simply not built.\n if (!spaceFilling) {\n for (const bond of structure.bonds) {\n if (!drawAtom(bond.a) || !drawAtom(bond.b)) continue\n const a = structure.atoms[bond.a]!\n const b = structure.atoms[bond.b]!\n // Two half-sticks rather than one, each in its own atom's colour: a\n // single stick has to be *some* colour, and whichever atom's it takes,\n // the far end reads as belonging to the wrong one. Splitting at the\n // midpoint is what ball-and-stick models have done since they were brass.\n const mid: Vector3 = {\n x: (a.x + b.x) / 2,\n y: (a.y + b.y) / 2,\n z: (a.z + b.z) / 2,\n }\n built.sticks.push(stickPlacement(a, mid, this.bondRadius))\n built.stickTints.push(colors[bond.a]!)\n built.sticks.push(stickPlacement(mid, b, this.bondRadius))\n built.stickTints.push(colors[bond.b]!)\n }\n }\n\n if (!atomic) this.addRuns(built, structure)\n built.radius = framingRadius(built, structure)\n return built\n }\n\n /**\n * The polymer, as whichever sweep its representation calls for.\n *\n * Two genuinely different builds rather than one with a radius switch, and\n * the split is the same one the representations themselves are: `backbone`\n * asks *where does the chain go*, which an even round tube answers exactly\n * and completely; `ribbon` asks *what is it folded into*, which is the\n * cartoon convention and needs a cross-section with a width, a thickness and\n * a side (see `ribbon.ts`).\n */\n private addRuns(built: Built, structure: ProteinStructure): void {\n const cartoon = this.representation === \"ribbon\"\n const uniform = hexOf(this.color)\n const palette = this.paletteOf()\n\n structure.chains.forEach((chain, index) => {\n const colors = traceColors(\n structure,\n chain,\n this.colorScheme,\n uniform,\n palette\n )\n\n if (cartoon) {\n for (const span of cartoonSpans(chain, colors)) {\n const geometry = cartoonGeometry(span, {\n radius: this.ribbonRadius,\n quality: this.quality,\n })\n if (geometry) built.ribbons.push(geometry)\n }\n return\n }\n\n backboneRuns(chain, colors, false).forEach((run, runIndex) => {\n built.runs.push({\n points: run.points,\n color: run.color,\n radius: this.ribbonRadius,\n // Keyed by its place in the chain rather than by its slot in the op\n // list, so adding a run doesn't renumber every later one and force the\n // tail of the renderer's cache to rebuild.\n key: `chain:${index}:${runIndex}`,\n })\n })\n })\n }\n\n /** Whether an atom passes the ligand and water filters. */\n private isShowing(atom: ProteinAtom): boolean {\n if (atom.kind === \"water\") return this.waters\n if (atom.kind === \"amino\" || atom.kind === \"nucleic\") return true\n return this.ligands\n }\n\n /** Segments around a sphere's equator. */\n private sphereQuality(): number {\n return Math.max(4, Math.round(this.quality))\n }\n\n /** Segments around a stick or a tube — half a sphere's, since it has no poles. */\n private stickQuality(): number {\n return Math.max(3, Math.round(this.quality / 2))\n }\n}\n\n/** A palette flattened for {@link Protein.built}'s cache key. */\nfunction paletteKey(palette: ProteinPalette): string {\n return [\n palette.helix,\n palette.sheet,\n palette.coil,\n palette.residueStart,\n palette.residueEnd,\n ...palette.chains,\n ].join(\",\")\n}\n\n/** One swept stretch of backbone. */\ninterface BuiltRun {\n points: Vector3[]\n color: string\n radius: number\n key: string\n}\n\n/** Everything the per-frame builder emits, built once per change and cached. */\ninterface Built {\n spheres: Transform3D[]\n sphereTints: string[]\n sticks: Transform3D[]\n stickTints: string[]\n /** Even round tubes — the `backbone` representation. */\n runs: BuiltRun[]\n /** Cartoon meshes, one per secondary-structure span — the `ribbon` one. */\n ribbons: BufferGeometry3D[]\n /** How far the drawn parts reach from the molecule's centroid, in Ångströms. */\n radius: number\n}\n\n/**\n * The radius the camera frames: the furthest *drawn* thing from the centroid.\n *\n * Measured over the placements rather than over the atom list, so it covers\n * exactly what will appear — the backbone runs when a ribbon is showing, the\n * atoms when it isn't, and neither the hidden waters nor the ligands somebody\n * switched off.\n *\n * Falls back to the whole structure's radius when nothing is drawn at all, so a\n * node whose filters have hidden everything still has a camera distance rather\n * than a zero.\n */\nfunction framingRadius(built: Built, structure: ProteinStructure): number {\n const { center } = structure\n let furthest = 0\n\n const reach = (x: number, y: number, z: number) => {\n const d = Math.hypot(x - center.x, y - center.y, z - center.z)\n if (d > furthest) furthest = d\n }\n\n for (const sphere of built.spheres) {\n const [x, y, z] = sphere.position as [number, number, number]\n reach(x, y, z)\n }\n for (const run of built.runs) {\n for (const point of run.points) reach(point.x, point.y, point.z)\n }\n // The cartoon's vertices rather than its residues: it is already a finished\n // buffer by the time this runs, and reading it is both exact — a strand's\n // arrowhead reaches wider than the α-carbons it was built from — and cheaper\n // than keeping a parallel copy of the curve alive to measure instead.\n for (const ribbon of built.ribbons) {\n const position = ribbon.position\n for (let i = 0; i + 2 < position.length; i += 3) {\n reach(position[i]!, position[i + 1]!, position[i + 2]!)\n }\n }\n\n return furthest > 0 ? furthest : structure.radius\n}\n\nfunction clampZoom(zoom: number): number {\n if (!Number.isFinite(zoom)) return DEFAULT_ZOOM\n return Math.min(MAX_ZOOM, Math.max(MIN_ZOOM, zoom))\n}\n", "/**\n * What the {@link Protein} node turns a parsed molecule into: which atoms are\n * showing, what colour each one takes, and how a bond becomes a cylinder.\n *\n * Everything here is **derived per structure**, not per frame. The node caches\n * what these return against the inputs that produced them (see\n * `Protein.instances`), because the camera moves every frame and none of this\n * changes when it does — and a mid-sized entry is tens of thousands of atoms, so\n * rebuilding the arrays sixty times a second to spin the view would be the\n * difference between a smooth orbit and a slideshow.\n */\n\nimport type {\n Color,\n Quaternion,\n Transform3D,\n Vector3,\n} from \"@motionscript/core\"\n\nimport { elementColor, vanDerWaalsRadius } from \"./chemistry\"\nimport type {\n ProteinAtom,\n ProteinChain,\n ProteinStructure,\n SecondaryStructure,\n} from \"./structure\"\n\n/**\n * How the molecule is drawn.\n *\n * The four that answer genuinely different questions, which is why there are\n * four rather than a dozen:\n *\n * - `spacefill` — every atom at its van der Waals radius. What the molecule's\n * *surface* is: the shape it presents to everything around it.\n * - `ballAndStick` — atoms shrunk, bonds drawn. What the molecule's *chemistry*\n * is, and the only one where an individual atom can be picked out.\n * - `backbone` — a thin even tube through the α-carbons. Where the chain *goes*,\n * with nothing else in the way.\n * - `ribbon` — the same curve, swelling along helices and strands and coloured\n * by them. What the protein is *folded into*, which is what a figure in a\n * paper is nearly always about.\n */\nexport const PROTEIN_REPRESENTATIONS = [\n \"ribbon\",\n \"backbone\",\n \"ballAndStick\",\n \"spacefill\",\n] as const\n\nexport type ProteinRepresentation = (typeof PROTEIN_REPRESENTATIONS)[number]\n\n/**\n * What decides an atom's colour.\n *\n * Each one answers a question the picture might be asking, and the default\n * differs by representation for exactly that reason: a space-filling model with\n * no element colouring is a featureless blob, while a ribbon coloured by element\n * is a single shade of grey.\n */\nexport const PROTEIN_COLOR_SCHEMES = [\n /** Helix / sheet / coil. Fold. */\n \"secondary\",\n /** CPK — oxygen red, nitrogen blue, carbon grey. Chemistry. */\n \"element\",\n /** One hue per chain. Which subunit is which, in a complex. */\n \"chain\",\n /** A spectrum from each chain's N terminus to its C terminus. Direction. */\n \"residue\",\n /** One colour throughout, whatever the node's own `color` is. */\n \"uniform\",\n] as const\n\nexport type ProteinColorScheme = (typeof PROTEIN_COLOR_SCHEMES)[number]\n\n/**\n * The per-chain palette.\n *\n * Eight, spread around the wheel rather than sampled from a gradient: chains are\n * a *nominal* scale — chain B is not between A and C in any sense — so the\n * colours have to be distinguishable rather than ordered. A ninth chain wraps,\n * which is the honest failure for a complex that large; the alternative is\n * colours nobody can tell apart.\n *\n * Eight is therefore the *palette's* length rather than a constant anything\n * downstream may assume: an author who edits these edits eight entries, and the\n * wrap is modulo whatever the list holds.\n */\nexport const CHAIN_COLORS = [\n \"#4f9dff\",\n \"#ff8a3d\",\n \"#4ddb9a\",\n \"#e05fd0\",\n \"#ffd166\",\n \"#8b7bff\",\n \"#ff6b6b\",\n \"#3fd0d6\",\n] as const\n\n/**\n * Helix, sheet and coil.\n *\n * The conventional assignment — warm for helices, cool for strands, pale for\n * everything else — which is old enough to be read without a legend.\n */\nexport const SECONDARY_COLORS: Readonly<Record<SecondaryStructure, string>> = {\n helix: \"#f0605d\",\n sheet: \"#f5c451\",\n coil: \"#c8ccd4\",\n}\n\n/** Where the residue spectrum starts and ends, as hues in degrees. */\nconst SPECTRUM_START = 250\nconst SPECTRUM_END = 0\n\n/** The saturation and lightness the residue spectrum is swept at. */\nconst SPECTRUM_SATURATION = 0.72\nconst SPECTRUM_LIGHTNESS = 0.58\n\n/**\n * Every colour a scheme reaches for that is **not** the node's own `color`.\n *\n * Three schemes paint from more than one colour — the fold's three states, the\n * eight chain hues, the two ends of the N→C ramp — and until now all three were\n * frozen in this file. They are conventional rather than arbitrary, which is why\n * the defaults below are exactly what they were, but \"conventional\" is a good\n * reason for a *default* and a poor one for a rule: a figure has a palette, and\n * a molecule in it that refuses to join in is the one element on the slide\n * somebody has to work around.\n *\n * A **read** shape rather than a prop. The node holds these as thirteen separate\n * props and gathers them into one of these to hand to the colourers below, and\n * that split is deliberate rather than clumsy: a prop is a whole value, so a\n * single `palette` prop would make every `to` that reddens the helices a\n * statement about all thirteen colours, snapping the twelve the author never\n * touched back to whatever the command happened to carry. Thirteen props carry\n * over one at a time and tween one at a time, which is what every other colour\n * in the app does.\n *\n * Nothing here reaches the `element` scheme, deliberately. CPK is not a palette\n * anybody chose — oxygen is red because oxygen is red — so it stays in\n * `chemistry.ts` with the van der Waals radii, which are facts about the same\n * atoms and equally not a matter of taste.\n */\nexport interface ProteinPalette {\n /** Secondary structure, when the scheme is `secondary`. */\n helix: string\n sheet: string\n coil: string\n /**\n * One hue per chain, when the scheme is `chain`, wrapping past the end.\n *\n * A list rather than a fixed eight so the wrap follows what is actually here:\n * an author who deletes down to three colours gets a three-colour cycle, which\n * is a legible answer, where a wrap modulo a constant would index past it.\n */\n chains: readonly string[]\n /** The two ends of the N→C ramp, when the scheme is `residue`. */\n residueStart: string\n residueEnd: string\n}\n\n/**\n * The palette every scheme has always drawn — the conventional assignment, now\n * stated as a value the author can take over rather than as a constant.\n *\n * The two spectrum ends are **computed** from the hues the ramp used to be\n * written as, rather than typed out as hex, so the default sweep is the same\n * blue-through-green-to-red it has always been by construction instead of by\n * somebody having done the arithmetic correctly once.\n */\nexport const DEFAULT_PROTEIN_PALETTE: ProteinPalette = {\n helix: SECONDARY_COLORS.helix,\n sheet: SECONDARY_COLORS.sheet,\n coil: SECONDARY_COLORS.coil,\n chains: CHAIN_COLORS,\n residueStart: hslHex(SPECTRUM_START, SPECTRUM_SATURATION, SPECTRUM_LIGHTNESS),\n residueEnd: hslHex(SPECTRUM_END, SPECTRUM_SATURATION, SPECTRUM_LIGHTNESS),\n}\n\n/**\n * How many chain colours the palette offers, and therefore what the chain cycle\n * wraps at.\n *\n * Eight, because that is how many hues can be told apart at a glance — the\n * reason {@link CHAIN_COLORS} has eight — and it is named here because three\n * places have to agree on it: the schema declares this many rows, the props\n * mapper reads this many, and the node holds this many.\n */\nexport const CHAIN_COLOR_COUNT = CHAIN_COLORS.length\n\n/**\n * The appearance-bag field names the palette is edited as, in panel order.\n *\n * Three places have to agree on this list and none of them can derive it from\n * the others: the schema declares a row per name, `proteinMotionProps` copies a\n * row per name onto the props, and the node declares a `@property` per name.\n * The third is the one that cannot be written as a loop — a decorator is a\n * declaration — so what this buys is the first two staying in step with it, and\n * a test pins the third against it.\n *\n * `color` is deliberately not here. It is the node's own colour rather than part\n * of a scheme's palette: it is what `uniform` paints and what every scheme falls\n * back to, so it lives beside `representation` as a field of the molecule.\n */\nexport const PROTEIN_PALETTE_FIELDS = [\n \"helixColor\",\n \"sheetColor\",\n \"coilColor\",\n \"residueStartColor\",\n \"residueEndColor\",\n ...CHAIN_COLORS.map((_, index) => `chainColor${index + 1}` as const),\n] as const\n\n/** The default each {@link PROTEIN_PALETTE_FIELDS} entry carries, by name. */\nexport const PROTEIN_PALETTE_DEFAULTS: Readonly<Record<string, string>> = {\n helixColor: DEFAULT_PROTEIN_PALETTE.helix,\n sheetColor: DEFAULT_PROTEIN_PALETTE.sheet,\n coilColor: DEFAULT_PROTEIN_PALETTE.coil,\n residueStartColor: DEFAULT_PROTEIN_PALETTE.residueStart,\n residueEndColor: DEFAULT_PROTEIN_PALETTE.residueEnd,\n ...Object.fromEntries(\n DEFAULT_PROTEIN_PALETTE.chains.map((colour, index) => [\n `chainColor${index + 1}`,\n colour,\n ])\n ),\n}\n\n/**\n * Tween: two `#rrggbb` strings, blended per channel.\n *\n * The palette entries are the one family of colours here that stay **strings**\n * rather than going through `resolveColor3D` — an instance tint is handed a hex\n * string — so they need a tween of their own rather than {@link lerpColor3D}.\n *\n * Blended in RGB, unlike the residue ramp {@link spectrum} sweeps: that one is a\n * *spectrum*, where covering the wheel is the whole point, while this is one\n * colour becoming another, where the short straight path is what \"fading to red\"\n * means. Anything unparseable snaps, which is the only honest answer for a value\n * with no channels to interpolate.\n */\nexport function lerpHexColor(from: string, to: string, t: number): string {\n const a = hexRgb(from)\n const b = hexRgb(to)\n if (!a || !b) return t < 1 ? from : to\n const byte = (x: number, y: number): string =>\n Math.round(x + (y - x) * t)\n .toString(16)\n .padStart(2, \"0\")\n return `#${byte(a[0], b[0])}${byte(a[1], b[1])}${byte(a[2], b[2])}`\n}\n\n/** A `#rrggbb` string as three 0–255 channels, or `null` if it isn't one. */\nfunction hexRgb(value: string): [number, number, number] | null {\n const hex = /^#?([0-9a-f]{6})$/i.exec(value.trim())\n if (!hex) return null\n const int = parseInt(hex[1]!, 16)\n return [(int >> 16) & 255, (int >> 8) & 255, int & 255]\n}\n\n/**\n * The colour of every atom, in the order the structure holds them.\n *\n * One flat array rather than a function called per atom, because the instance\n * builder walks the whole list and an array is what `Graphics3D.instances` takes\n * anyway.\n */\nexport function atomColors(\n structure: ProteinStructure,\n scheme: ProteinColorScheme,\n uniform: string,\n palette: ProteinPalette = DEFAULT_PROTEIN_PALETTE\n): string[] {\n if (scheme === \"uniform\") return structure.atoms.map(() => uniform)\n if (scheme === \"element\") {\n return structure.atoms.map((atom) => elementColor(atom.element))\n }\n\n const chains = chainOrder(structure)\n if (scheme === \"chain\") {\n return structure.atoms.map((atom) =>\n chainColor(chains, atom.chain, palette)\n )\n }\n\n if (scheme === \"secondary\") {\n const lookup = secondaryByResidue(structure)\n return structure.atoms.map(\n (atom) => palette[lookup.get(residueKey(atom)) ?? \"coil\"]\n )\n }\n\n // `residue` — a spectrum along each chain, so position is read against the\n // chain the residue is in rather than against the whole file. Without that, a\n // short chain beside a long one would be drawn in a single colour.\n const positions = residuePositions(structure)\n return structure.atoms.map((atom) => {\n const position = positions.get(residueKey(atom))\n return position === undefined ? uniform : spectrum(position, palette)\n })\n}\n\n/**\n * The colour of each point along one chain's backbone.\n *\n * Separate from {@link atomColors} because a trace point is a *residue* and an\n * atom is not: the element scheme has nothing to say about a residue (every\n * trace atom is a carbon, so the whole ribbon would be grey), so it falls back\n * to the chain colouring — which is what somebody who picked \"element\" and then\n * switched to a ribbon actually wants to see.\n */\nexport function traceColors(\n structure: ProteinStructure,\n chain: ProteinChain,\n scheme: ProteinColorScheme,\n uniform: string,\n palette: ProteinPalette = DEFAULT_PROTEIN_PALETTE\n): string[] {\n if (scheme === \"uniform\") return chain.trace.map(() => uniform)\n if (scheme === \"secondary\") {\n return chain.trace.map((point) => palette[point.secondary])\n }\n if (scheme === \"residue\") {\n const last = Math.max(1, chain.trace.length - 1)\n return chain.trace.map((point) => spectrum(point.index / last, palette))\n }\n\n const chains = chainOrder(structure)\n const colour = chainColor(chains, chain.id, palette)\n return chain.trace.map(() => colour)\n}\n\n/**\n * A colour on the N→C spectrum, `0` at the start of a chain and `1` at its end.\n *\n * Interpolated in **HSL**, which is what makes the default a spectrum at all: a\n * straight RGB blend between the two ends passes through grey, where a hue sweep\n * runs the ramp everybody recognises. And along the *direct numeric path*\n * between the two hues rather than the shorter way round the wheel — 250° to 0°\n * is the familiar blue-through-green-to-red run, and taking the short arc would\n * hop through magenta instead and cover a quarter of the colours.\n */\nfunction spectrum(position: number, palette: ProteinPalette): string {\n const from = hexHsl(palette.residueStart)\n const to = hexHsl(palette.residueEnd)\n const t = clamp01(position)\n return hslHex(\n from.h + (to.h - from.h) * t,\n from.s + (to.s - from.s) * t,\n from.l + (to.l - from.l) * t\n )\n}\n\nfunction clamp01(value: number): number {\n return Math.min(1, Math.max(0, value))\n}\n\n/**\n * HSL to a `#rrggbb` string.\n *\n * Written out rather than reached for, because the spectrum is the one place\n * this package generates a colour instead of being handed one, and every other\n * colour path here takes hex.\n */\nfunction hslHex(hue: number, saturation: number, lightness: number): string {\n const h = ((hue % 360) + 360) % 360\n const c = (1 - Math.abs(2 * lightness - 1)) * saturation\n const x = c * (1 - Math.abs(((h / 60) % 2) - 1))\n const m = lightness - c / 2\n\n const [r, g, b] =\n h < 60\n ? [c, x, 0]\n : h < 120\n ? [x, c, 0]\n : h < 180\n ? [0, c, x]\n : h < 240\n ? [0, x, c]\n : h < 300\n ? [x, 0, c]\n : [c, 0, x]\n\n const byte = (value: number): string =>\n Math.round((value + m) * 255)\n .toString(16)\n .padStart(2, \"0\")\n return `#${byte(r!)}${byte(g!)}${byte(b!)}`\n}\n\n/**\n * A `#rrggbb` string back to HSL — the inverse of {@link hslHex}, and here for\n * the one caller that needs it: the residue ramp is authored as two colours and\n * swept as a hue.\n *\n * Anything unparseable comes back as the default ramp's own start, so a half\n * typed hex in the inspector fades toward a colour rather than painting `NaN`\n * into every residue of the chain.\n */\nfunction hexHsl(value: string): { h: number; s: number; l: number } {\n const hex = /^#?([0-9a-f]{6})$/i.exec(value.trim())\n if (!hex) return { h: SPECTRUM_START, s: SPECTRUM_SATURATION, l: SPECTRUM_LIGHTNESS }\n\n const int = parseInt(hex[1]!, 16)\n const r = ((int >> 16) & 255) / 255\n const g = ((int >> 8) & 255) / 255\n const b = (int & 255) / 255\n\n const max = Math.max(r, g, b)\n const min = Math.min(r, g, b)\n const l = (max + min) / 2\n const d = max - min\n if (d === 0) return { h: 0, s: 0, l }\n\n const s = d / (1 - Math.abs(2 * l - 1))\n const h =\n max === r\n ? 60 * (((g - b) / d) % 6)\n : max === g\n ? 60 * ((b - r) / d + 2)\n : 60 * ((r - g) / d + 4)\n return { h: (h + 360) % 360, s, l }\n}\n\n/** The chain identifiers in the order they first appear, for palette indexing. */\nfunction chainOrder(structure: ProteinStructure): Map<string, number> {\n const order = new Map<string, number>()\n for (const atom of structure.atoms) {\n if (!order.has(atom.chain)) order.set(atom.chain, order.size)\n }\n return order\n}\n\n/** The palette entry for a chain, wrapping past the end. */\nfunction chainColor(\n order: Map<string, number>,\n chain: string,\n palette: ProteinPalette\n): string {\n const colours =\n palette.chains.length > 0 ? palette.chains : DEFAULT_PROTEIN_PALETTE.chains\n const index = order.get(chain) ?? 0\n return colours[index % colours.length]!\n}\n\n/** A residue's identity across the whole structure — its chain and its number. */\nfunction residueKey(atom: ProteinAtom): string {\n return `${atom.chain}:${atom.residueSeq}`\n}\n\n/**\n * Each residue's position along its own chain, `0`–`1`.\n *\n * Read off the *traces* rather than counted over the atoms, so it means the same\n * thing the ribbon means by it — and so a residue the file resolved no backbone\n * for simply has no position, and takes the fallback colour rather than shifting\n * every residue after it along the spectrum.\n */\nfunction residuePositions(structure: ProteinStructure): Map<string, number> {\n const positions = new Map<string, number>()\n for (const chain of structure.chains) {\n const last = Math.max(1, chain.trace.length - 1)\n for (const point of chain.trace) {\n positions.set(`${chain.id}:${point.residueSeq}`, point.index / last)\n }\n }\n return positions\n}\n\n/** Each residue's secondary structure, keyed as {@link residueKey} does. */\nfunction secondaryByResidue(\n structure: ProteinStructure\n): Map<string, SecondaryStructure> {\n const lookup = new Map<string, SecondaryStructure>()\n for (const chain of structure.chains) {\n for (const point of chain.trace) {\n lookup.set(`${chain.id}:${point.residueSeq}`, point.secondary)\n }\n }\n return lookup\n}\n\n// --- Geometry --------------------------------------------------------------\n\n/** An atom's drawn radius in Ångströms, at a given scale of its true size. */\nexport function atomRadius(atom: ProteinAtom, scale: number): number {\n return vanDerWaalsRadius(atom.element) * scale\n}\n\n/**\n * The placement of a stick running from `from` to `to`.\n *\n * A cylinder geometry is built along **+Y**, centred on the origin and one unit\n * tall, so placing one takes all three parts of a transform: move it to the\n * midpoint, scale it to the bond's length, and turn +Y onto the bond's\n * direction.\n *\n * The turn is a quaternion rather than an Euler triple because there is no\n * ordering of three axis rotations that doesn't gimbal somewhere, and a molecule\n * has bonds pointing every way there is — one of them would land exactly on the\n * degenerate axis and the stick would spin to a wrong orientation.\n */\nexport function stickPlacement(\n from: Vector3,\n to: Vector3,\n radius: number\n): Transform3D {\n const dx = to.x - from.x\n const dy = to.y - from.y\n const dz = to.z - from.z\n const length = Math.hypot(dx, dy, dz) || 1\n\n return {\n position: [\n (from.x + to.x) / 2,\n (from.y + to.y) / 2,\n (from.z + to.z) / 2,\n ],\n scale: [radius, length, radius],\n quaternion: upTo(dx / length, dy / length, dz / length),\n }\n}\n\n/**\n * The shortest rotation taking **+Y** onto a unit direction.\n *\n * The general form is \"rotate about the axis perpendicular to both, by the angle\n * between them\", which for a fixed source axis collapses to the cross product\n * with `(0, 1, 0)` — hence the two components rather than three.\n *\n * The antiparallel case has to be handled outright: a direction pointing\n * straight down has a *zero* cross product with up, so the axis is undefined and\n * the general form produces a quaternion of all zeros, which is not a rotation\n * at all. Any half-turn about a perpendicular axis is correct there, and X is as\n * good as any.\n */\nfunction upTo(x: number, y: number, z: number): Quaternion {\n if (y > 0.999999) return { x: 0, y: 0, z: 0, w: 1 }\n if (y < -0.999999) return { x: 1, y: 0, z: 0, w: 0 }\n\n // axis = up × d, normalized; angle = acos(up · d) = acos(y).\n const axisX = z\n const axisZ = -x\n const axisLength = Math.hypot(axisX, axisZ) || 1\n const angle = Math.acos(Math.min(1, Math.max(-1, y)))\n const sin = Math.sin(angle / 2)\n\n return {\n x: (axisX / axisLength) * sin,\n y: 0,\n z: (axisZ / axisLength) * sin,\n w: Math.cos(angle / 2),\n }\n}\n\n/** One stretch of a chain drawn as a single swept tube. */\nexport interface BackboneRun {\n points: Vector3[]\n color: string\n secondary: SecondaryStructure\n}\n\n/**\n * Splits a chain's trace into the runs a backbone is swept as.\n *\n * A run is a stretch that can be drawn as **one** `Geo.tube`, and what forces a\n * break is anything the tube carries once for its whole length: its colour\n * always, and — for a cartoon — its radius, since a helix that doesn't swell is\n * not a helix anyone will recognise.\n *\n * Consecutive runs **overlap by one point**, which is what keeps the tubes\n * meeting rather than leaving a gap at every transition: the shared point is the\n * end of one sweep and the start of the next, so their ends sit in the same\n * place.\n *\n * The trade this makes is worth stating. A tube is swept along a Catmull-Rom\n * curve through its own points, so a long run comes out smooth and a two-point\n * run comes out straight. Under the colourings a fold is usually drawn in —\n * secondary structure, chain, one colour — runs are long and the curve is\n * smooth. Under the residue spectrum every point is its own colour, so the\n * chain becomes a faceted polyline with a gradient along it. That is the honest\n * cost of colouring per residue without a mesh built vertex by vertex, and at\n * the scale a whole fold is viewed at it reads as a curve anyway.\n */\nexport function backboneRuns(\n chain: ProteinChain,\n colors: string[],\n splitBySecondary: boolean\n): BackboneRun[] {\n const runs: BackboneRun[] = []\n\n for (let index = 0; index < chain.trace.length; index++) {\n const point = chain.trace[index]!\n const color = colors[index] ?? colors[0] ?? \"#ffffff\"\n let current = runs[runs.length - 1]\n\n const breaks =\n !current ||\n current.color !== color ||\n (splitBySecondary && current.secondary !== point.secondary)\n\n if (breaks) {\n // Carry the previous run's last point in as this one's first, so the two\n // sweeps butt up against each other.\n const seed = current ? [current.points[current.points.length - 1]!] : []\n current = { points: seed, color, secondary: point.secondary }\n runs.push(current)\n }\n current.points.push({ x: point.x, y: point.y, z: point.z })\n }\n\n // A run of one point is not a curve — there is no direction to sweep along.\n // It can only be the very first run when the trace starts with a lone point,\n // since every later run is seeded with its predecessor's last point.\n return runs.filter((run) => run.points.length > 1)\n}\n\n/** The colour a `Color` prop resolves to when a scheme wants a plain string. */\nexport function hexOf(value: Color): string {\n return typeof value === \"string\" ? value : \"#ffffff\"\n}\n", "/**\n * The cartoon: a chain's backbone swept as a **ribbon** rather than as a tube.\n *\n * This is the one representation whose whole job is to be recognised. A figure\n * in a paper draws a helix as a twisted band and a strand as a flat arrow, and\n * has since Jane Richardson drew the first one by hand in 1980 — the convention\n * is old enough that a reader decodes it without a legend, which is exactly why\n * a picture that departs from it reads as *wrong* rather than as different.\n *\n * The node drew that with `Geo.tube` before this module existed, and the two\n * things it could not express are the two the convention is made of:\n *\n * **A cross-section has a width and a thickness, not a radius.** A tube's is a\n * circle, so a helix could only be told from a coil by being *fatter* — and a\n * fat circle swept along the α-carbons of a helix is close to self-intersecting,\n * because those carbons corkscrew about the helix axis at ~2.3 Å with a 5.4 Å\n * pitch. What comes out is a lumpy sausage: the tube's own diameter is most of\n * the coil's, so the spiral fills in and the shape stops being legible. Swept\n * flat instead — ~2.2 Å across and ~0.5 Å thick — the same spiral reads as the\n * band every textbook draws, because the eye is now reading the *face* turning\n * rather than a diameter changing.\n *\n * **A band has a side, and a curve through α-carbons does not say which.** So\n * the frame is built from the residue's own carbonyl (see\n * {@link TracePoint.normal}), which is what turns with the fold. A ribbon swept\n * on an arbitrary frame twists at random and looks like a misprint.\n *\n * Everything here is derived **per structure**, cached by the node against the\n * inputs that produced it, and never rebuilt for a camera move — the same\n * bargain the instance lists make in `shared.ts`.\n */\n\nimport type { BufferGeometry3D, Color, Vector3 } from \"@motionscript/core\"\n\nimport { resolveColor3D } from \"@motionscript/core/component\"\nimport type { ProteinChain, SecondaryStructure, TracePoint } from \"./structure\"\n\n/**\n * A run of backbone drawn as one mesh: a whole stretch of one secondary\n * structure, with its per-residue colours carried as a vertex attribute.\n *\n * The unit is the **span**, not the run of constant colour that a tube is swept\n * as (see `backboneRuns`), and both halves of that matter. An arrowhead is a\n * statement about where a strand *ends*, so it can only be placed by something\n * that can see the whole strand — split the strand into four differently\n * coloured pieces first and each piece grows an arrow of its own. And a colour\n * that varies along the span is what a vertex attribute is for, which is also\n * what retires the faceted-polyline compromise the tube path has to make under\n * the residue spectrum.\n */\nexport interface CartoonSpan {\n /** What this stretch is doing, which decides its cross-section. */\n secondary: SecondaryStructure\n /**\n * The residues of the span, N→C, **overlapping its neighbours by one point**\n * at each end so consecutive spans meet rather than leaving a gap.\n */\n points: TracePoint[]\n /** One sRGB colour per entry of {@link points}. */\n colors: string[]\n}\n\n/**\n * How wide and how thick each secondary structure's ribbon is, as multiples of\n * the node's `ribbonRadius`.\n *\n * Stated as a ratio to one control rather than as six numbers in the inspector:\n * \"how heavy is the cartoon\" is one question an author actually asks, and the\n * *proportions* between a helix, a strand and a loop are the convention rather\n * than a preference. The absolute values are the ones the established viewers\n * settled on — a helix band about 2.2 Å across and half an Ångström thick, a\n * strand slightly wider still, and a loop a thin round cord.\n *\n * A coil stays round (width equals thickness), which is the other half of the\n * statement: a loop has no face to turn, so giving it one would claim a\n * structure the file did not annotate.\n */\nconst SECTION: Readonly<\n Record<SecondaryStructure, { width: number; thickness: number }>\n> = {\n helix: { width: 2.4, thickness: 0.5 },\n sheet: { width: 2.6, thickness: 0.45 },\n coil: { width: 1, thickness: 1 },\n}\n\n/** How wide a strand's arrowhead is at its base, in the same multiples. */\nconst ARROW_WIDTH = 4.4\n\n/**\n * How many residues at the C-terminal end of a strand the arrowhead occupies.\n *\n * Residues rather than a fraction of the span, because an arrowhead is a fixed\n * *shape* — the same barb whether the strand is five residues or fifteen. A\n * fraction would draw a stubby wedge on a short strand and a long spike on a\n * β-hairpin's arm. Capped below at a third of the span so a three-residue\n * strand is still a strand with a point on it rather than one long taper.\n */\nconst ARROW_RESIDUES = 2.4\n\n/** What a strand's arrow narrows to at its tip, so the point is a point. */\nconst ARROW_TIP = 0.08\n\n/**\n * How many samples each residue-to-residue step is drawn with.\n *\n * The curve is the picture here. Consecutive α-carbons are 3.8 Å apart and a\n * helix turns fully in 3.6 of them, so a ribbon drawn straight between residues\n * is a polygon with about five sides per turn — which is exactly the faceted,\n * angular look that separates a rendering nobody trusts from one that reads as\n * a molecule. Tied to the node's mesh detail so the same control that coarsens\n * the spheres coarsens this.\n */\nfunction subdivisionsFor(quality: number): number {\n return Math.min(10, Math.max(3, Math.round(quality / 2)))\n}\n\n/**\n * Splits a chain's trace into the spans a cartoon is built from — one per\n * unbroken stretch of the same secondary structure.\n *\n * Consecutive spans overlap by one point, for the reason `backboneRuns`'\n * consecutive runs do: the shared residue is the last sample of one sweep and\n * the first of the next, so the two meshes butt up rather than leaving a hole\n * at every helix-to-loop transition.\n */\nexport function cartoonSpans(\n chain: ProteinChain,\n colors: string[]\n): CartoonSpan[] {\n const spans: CartoonSpan[] = []\n\n chain.trace.forEach((point, index) => {\n const color = colors[index] ?? colors[0] ?? \"#ffffff\"\n let current = spans[spans.length - 1]\n\n if (!current || current.secondary !== point.secondary) {\n const previous = current\n current = { secondary: point.secondary, points: [], colors: [] }\n // Carry the previous span's last residue in as this one's first.\n if (previous) {\n current.points.push(previous.points[previous.points.length - 1]!)\n current.colors.push(previous.colors[previous.colors.length - 1]!)\n }\n spans.push(current)\n }\n\n current.points.push(point)\n current.colors.push(color)\n })\n\n // A lone point is not a curve: there is no direction to sweep along and no\n // tangent to build a frame from. Only ever the first span, since every later\n // one is seeded with its predecessor's last point.\n return spans.filter((span) => span.points.length > 1)\n}\n\n/**\n * One span, as a mesh.\n *\n * `null` when there is nothing to sweep, so the caller can skip the op rather\n * than hand the renderer an empty buffer.\n *\n * The arrays are freshly allocated and never mutated afterwards, which is what\n * makes `staticData` honest: the renderer then compares them by identity and\n * uploads each exactly once, where re-deriving them per frame would re-upload\n * the whole fold sixty times a second to spin the camera.\n */\nexport function cartoonGeometry(\n span: CartoonSpan,\n options: { radius: number; quality: number }\n): BufferGeometry3D | null {\n const samples = sampleSpan(span, options)\n if (samples.length < 2) return null\n return sweep(samples, profileFor(span.secondary, options.quality))\n}\n\n// --- Sampling --------------------------------------------------------------\n\n/** One cross-section of the sweep: where it is, how it is turned, how big. */\ninterface Sample {\n position: Vector3\n /** Unit tangent — the direction the sweep is travelling. */\n tangent: Vector3\n /** Unit **wide** direction: the ribbon's face turns with this. */\n normal: Vector3\n /** Unit binormal, completing the frame. */\n binormal: Vector3\n halfWidth: number\n halfThickness: number\n /** Linear RGB, as a vertex colour attribute holds it. */\n color: [number, number, number]\n}\n\n/**\n * The span, resampled onto a smooth curve with a frame and a size at every\n * sample.\n *\n * Three things happen here that each fix something visible:\n *\n * **Catmull-Rom through the residues**, so the ribbon is a curve rather than a\n * five-sided polygon per helical turn — see {@link subdivisionsFor}.\n *\n * **The frames are made continuous before anything is interpolated.** A\n * β-strand's carbonyls point alternately to one side and the other, which is\n * chemistry rather than noise: hydrogen bonds go to the neighbouring strand on\n * both sides. Swept as given, the ribbon would flip 180° at every residue and\n * come out as a chain of triangles. Flipping each frame to agree with its\n * predecessor is what every viewer does and what makes a strand flat.\n *\n * **The arrow is measured in residues from the C-terminal end**, before\n * subdivision, so it is the same barb on a long strand and a short one.\n */\nfunction sampleSpan(\n span: CartoonSpan,\n options: { radius: number; quality: number }\n): Sample[] {\n const points = span.points\n const count = points.length\n const section = SECTION[span.secondary]\n const radius = Math.max(0.01, options.radius)\n\n const frames = continuousFrames(points)\n const tints = span.colors.map((color) => linearRgb(color))\n\n // Where the arrowhead starts, as a position along the span in *residues*.\n // A strand shorter than the barb gives up a third of itself to it rather than\n // becoming one long taper.\n const arrow =\n span.secondary === \"sheet\"\n ? Math.max(count - 1 - ARROW_RESIDUES, (count - 1) * (2 / 3))\n : Infinity\n\n const steps = subdivisionsFor(options.quality)\n const samples: Sample[] = []\n\n // Positions first, frames second: a tangent is a finite difference over the\n // *sampled* curve, so every position has to exist before any of them can be\n // taken. Doing it in one pass would take each tangent from the residues\n // instead, which is the polyline this subdivision exists to get away from.\n const curve: Vector3[] = []\n const carried: Vector3[] = []\n const colors: [number, number, number][] = []\n const positions: number[] = []\n\n for (let i = 0; i < count - 1; i++) {\n // The last segment emits its endpoint too, so the sweep reaches the final\n // residue rather than stopping one sample short of it.\n const last = i === count - 2\n for (let s = 0; s < steps + (last ? 1 : 0); s++) {\n const t = s / steps\n curve.push(\n catmullRom(\n points[i - 1] ?? points[i]!,\n points[i]!,\n points[i + 1]!,\n points[i + 2] ?? points[i + 1]!,\n t\n )\n )\n carried.push(lerpVector(frames[i]!, frames[i + 1]!, t))\n colors.push(lerpRgb(tints[i]!, tints[i + 1]!, t))\n positions.push(i + t)\n }\n }\n\n for (let i = 0; i < curve.length; i++) {\n const position = curve[i]!\n const tangent = normalize(\n subtract(\n curve[Math.min(curve.length - 1, i + 1)]!,\n curve[Math.max(0, i - 1)]!\n )\n )\n // Squared against the tangent, so the frame is orthonormal even though the\n // carbonyl it came from is not perpendicular to the chain.\n const normal = perpendicular(carried[i]!, tangent)\n const binormal = normalize(cross(tangent, normal))\n\n const along = positions[i]!\n const width =\n along <= arrow\n ? section.width\n : // Linear from the barb's base to its point. The base is *wider* than\n // the strand, which is what makes an arrowhead read as one rather\n // than as the strand simply stopping.\n ARROW_WIDTH +\n (ARROW_TIP - ARROW_WIDTH) *\n clamp01((along - arrow) / Math.max(1e-6, count - 1 - arrow))\n\n samples.push({\n position,\n tangent,\n normal,\n binormal,\n halfWidth: radius * width,\n halfThickness: radius * section.thickness,\n color: colors[i]!,\n })\n }\n\n return samples\n}\n\n/**\n * Each residue's ribbon direction, made to agree with its predecessor.\n *\n * Falls back to the curve's own binormal — the axis its turn is about — where\n * the file gives no carbonyl. That is the right stand-in rather than an\n * arbitrary perpendicular: in a helix the carbonyl points very nearly along the\n * helix axis, and so does the binormal of the curve the α-carbons trace, so a\n * Cα-only model comes out with its bands lying the same way a complete file's\n * do.\n */\nfunction continuousFrames(points: TracePoint[]): Vector3[] {\n const frames: Vector3[] = []\n\n for (let i = 0; i < points.length; i++) {\n const point = points[i]!\n let direction = point.normal ?? binormalAt(points, i)\n // Degenerate — three collinear residues with no carbonyl. Any perpendicular\n // will do, and the frame is squared against the tangent anyway.\n if (lengthOf(direction) < 1e-6) direction = { x: 0, y: 0, z: 1 }\n\n const previous = frames[i - 1]\n // The 180° flip a β-strand's alternating carbonyls would otherwise put into\n // the sweep. See {@link sampleSpan}.\n if (previous && dot(previous, direction) < 0) direction = negate(direction)\n frames.push(normalize(direction))\n }\n\n return frames\n}\n\n/** The curve's binormal at a residue — the axis its local turn is about. */\nfunction binormalAt(points: TracePoint[], index: number): Vector3 {\n const before = points[Math.max(0, index - 1)]!\n const here = points[index]!\n const after = points[Math.min(points.length - 1, index + 1)]!\n return cross(subtract(here, before), subtract(after, here))\n}\n\n// --- Sweeping --------------------------------------------------------------\n\n/**\n * One vertex of a cross-section: where it sits in the frame's `(wide, thick)`\n * plane, and which way the surface faces there.\n *\n * A hard edge is **two entries at the same place with different normals**,\n * which is what gives a ribbon the crisp corner a cartoon has rather than the\n * chamfered one a shared-vertex mesh with averaged normals would. It is also\n * why the normals are stated rather than derived: `computeNormals` averages\n * across whatever shares a vertex, and averaging is exactly what a corner must\n * not do.\n */\ninterface ProfileVertex {\n /** Position along the ribbon's width, in `[-1, 1]`. */\n u: number\n /** Position along its thickness, in `[-1, 1]`. */\n v: number\n /** Surface direction at that vertex, in the same plane. */\n nu: number\n nv: number\n}\n\n/**\n * The cross-section a secondary structure is swept with.\n *\n * A loop is a round cord and a helix or a strand is a flat band — the whole\n * distinction the convention rests on, and the one a radius could not express.\n * The band is a rectangle rather than an ellipse because its *edge* is what the\n * eye reads a twist by: an ellipse turning through 90° goes smoothly from wide\n * to narrow and reads as a tube pinching, where a rectangle's corner catches the\n * light and reads as a face turning over.\n */\nfunction profileFor(\n secondary: SecondaryStructure,\n quality: number\n): ProfileVertex[] {\n if (secondary === \"coil\") {\n const sides = Math.min(16, Math.max(5, Math.round(quality / 2)))\n const profile: ProfileVertex[] = []\n for (let i = 0; i < sides; i++) {\n const angle = (i / sides) * Math.PI * 2\n const u = Math.cos(angle)\n const v = Math.sin(angle)\n // Smooth: one vertex per side, its normal radial, so a cord reads as\n // round rather than as a faceted prism.\n profile.push({ u, v, nu: u, nv: v })\n }\n return profile\n }\n\n // Four faces, each with its own pair of corners, walked so consecutive\n // entries are consecutive around the section.\n return [\n { u: 1, v: 1, nu: 0, nv: 1 },\n { u: -1, v: 1, nu: 0, nv: 1 },\n { u: -1, v: 1, nu: -1, nv: 0 },\n { u: -1, v: -1, nu: -1, nv: 0 },\n { u: -1, v: -1, nu: 0, nv: -1 },\n { u: 1, v: -1, nu: 0, nv: -1 },\n { u: 1, v: -1, nu: 1, nv: 0 },\n { u: 1, v: 1, nu: 1, nv: 0 },\n ]\n}\n\n/**\n * The samples and the profile, as one indexed mesh.\n *\n * Ends are **capped**: a ribbon is a solid band and an open end shows the\n * inside of the far face, which at a helix-to-loop transition would be a\n * visible hole rather than a join. Cheap — one fan per end — and it is what\n * lets a span be drawn as its own mesh at all.\n */\nfunction sweep(samples: Sample[], profile: ProfileVertex[]): BufferGeometry3D {\n const ring = profile.length\n const position: number[] = []\n const normal: number[] = []\n const color: number[] = []\n const index: number[] = []\n\n for (const sample of samples) {\n for (const vertex of profile) {\n const u = vertex.u * sample.halfWidth\n const v = vertex.v * sample.halfThickness\n position.push(\n sample.position.x + sample.normal.x * u + sample.binormal.x * v,\n sample.position.y + sample.normal.y * u + sample.binormal.y * v,\n sample.position.z + sample.normal.z * u + sample.binormal.z * v\n )\n // A normal does not survive a non-uniform scale the way a position does:\n // squashing a circle to a 5:1 ellipse tips every one of its normals\n // toward the wide axis. Dividing by the half-extents is the inverse\n // transpose of that scale, written out for two dimensions.\n const n = normalize2(\n vertex.nu / sample.halfWidth,\n vertex.nv / sample.halfThickness\n )\n normal.push(\n sample.normal.x * n[0] + sample.binormal.x * n[1],\n sample.normal.y * n[0] + sample.binormal.y * n[1],\n sample.normal.z * n[0] + sample.binormal.z * n[1]\n )\n color.push(sample.color[0], sample.color[1], sample.color[2])\n }\n }\n\n for (let i = 0; i < samples.length - 1; i++) {\n for (let j = 0; j < ring; j++) {\n const next = (j + 1) % ring\n const a = profile[j]!\n const b = profile[next]!\n // The two halves of a hard corner sit in the same place, so the quad\n // between them has no area. Skipping it keeps the index buffer honest\n // rather than filling it with degenerate triangles.\n if (a.u === b.u && a.v === b.v) continue\n const i0 = i * ring + j\n const i1 = i * ring + next\n const i2 = (i + 1) * ring + next\n const i3 = (i + 1) * ring + j\n index.push(i0, i1, i2, i0, i2, i3)\n }\n }\n\n capEnd(samples[0]!, profile, position, normal, color, index, false)\n capEnd(\n samples[samples.length - 1]!,\n profile,\n position,\n normal,\n color,\n index,\n true\n )\n\n return {\n type: \"buffer\",\n position: new Float32Array(position),\n normal: new Float32Array(normal),\n color: new Float32Array(color),\n index: new Uint32Array(index),\n // Built once per structure and cached by the node; nothing mutates these\n // arrays afterwards, so the renderer may compare them by identity alone.\n staticData: true,\n }\n}\n\n/** A triangle fan closing one end of the sweep, facing along the tangent. */\nfunction capEnd(\n sample: Sample,\n profile: ProfileVertex[],\n position: number[],\n normal: number[],\n color: number[],\n index: number[],\n forward: boolean\n): void {\n const sign = forward ? 1 : -1\n const nx = sample.tangent.x * sign\n const ny = sample.tangent.y * sign\n const nz = sample.tangent.z * sign\n\n const center = position.length / 3\n position.push(sample.position.x, sample.position.y, sample.position.z)\n normal.push(nx, ny, nz)\n color.push(sample.color[0], sample.color[1], sample.color[2])\n\n const first = position.length / 3\n for (const vertex of profile) {\n const u = vertex.u * sample.halfWidth\n const v = vertex.v * sample.halfThickness\n position.push(\n sample.position.x + sample.normal.x * u + sample.binormal.x * v,\n sample.position.y + sample.normal.y * u + sample.binormal.y * v,\n sample.position.z + sample.normal.z * u + sample.binormal.z * v\n )\n normal.push(nx, ny, nz)\n color.push(sample.color[0], sample.color[1], sample.color[2])\n }\n\n for (let j = 0; j < profile.length; j++) {\n const next = (j + 1) % profile.length\n // Wound the opposite way at the two ends, so both caps face outward.\n if (forward) index.push(center, first + j, first + next)\n else index.push(center, first + next, first + j)\n }\n}\n\n// --- Small vector maths ----------------------------------------------------\n//\n// Written out rather than reached for, on the same terms as `chemistry.ts`:\n// this package has to stay loadable in a bare Node process, and none of it is\n// more than a few lines.\n\nfunction catmullRom(\n p0: { x: number; y: number; z: number },\n p1: { x: number; y: number; z: number },\n p2: { x: number; y: number; z: number },\n p3: { x: number; y: number; z: number },\n t: number\n): Vector3 {\n const t2 = t * t\n const t3 = t2 * t\n const a = -0.5 * t3 + t2 - 0.5 * t\n const b = 1.5 * t3 - 2.5 * t2 + 1\n const c = -1.5 * t3 + 2 * t2 + 0.5 * t\n const d = 0.5 * t3 - 0.5 * t2\n return {\n x: p0.x * a + p1.x * b + p2.x * c + p3.x * d,\n y: p0.y * a + p1.y * b + p2.y * c + p3.y * d,\n z: p0.z * a + p1.z * b + p2.z * c + p3.z * d,\n }\n}\n\nfunction subtract(\n a: { x: number; y: number; z: number },\n b: { x: number; y: number; z: number }\n): Vector3 {\n return { x: a.x - b.x, y: a.y - b.y, z: a.z - b.z }\n}\n\nfunction cross(a: Vector3, b: Vector3): Vector3 {\n return {\n x: a.y * b.z - a.z * b.y,\n y: a.z * b.x - a.x * b.z,\n z: a.x * b.y - a.y * b.x,\n }\n}\n\nfunction dot(a: Vector3, b: Vector3): number {\n return a.x * b.x + a.y * b.y + a.z * b.z\n}\n\nfunction negate(v: Vector3): Vector3 {\n return { x: -v.x, y: -v.y, z: -v.z }\n}\n\nfunction lengthOf(v: Vector3): number {\n return Math.hypot(v.x, v.y, v.z)\n}\n\nfunction normalize(v: Vector3): Vector3 {\n const length = lengthOf(v)\n if (length < 1e-9) return { x: 0, y: 0, z: 1 }\n return { x: v.x / length, y: v.y / length, z: v.z / length }\n}\n\nfunction normalize2(u: number, v: number): [number, number] {\n const length = Math.hypot(u, v)\n if (length < 1e-9) return [1, 0]\n return [u / length, v / length]\n}\n\n/** `v` with everything along `axis` taken out of it, normalized. */\nfunction perpendicular(v: Vector3, axis: Vector3): Vector3 {\n const along = dot(v, axis)\n const out = {\n x: v.x - axis.x * along,\n y: v.y - axis.y * along,\n z: v.z - axis.z * along,\n }\n // The carbonyl parallel to the chain — vanishingly rare, but it would leave\n // no frame at all. Any perpendicular is correct; take one from the axis.\n if (lengthOf(out) < 1e-6) {\n const fallback =\n Math.abs(axis.x) < 0.9 ? { x: 1, y: 0, z: 0 } : { x: 0, y: 1, z: 0 }\n return normalize(cross(axis, fallback))\n }\n return normalize(out)\n}\n\nfunction lerpVector(\n a: { x: number; y: number; z: number },\n b: { x: number; y: number; z: number },\n t: number\n): Vector3 {\n return {\n x: a.x + (b.x - a.x) * t,\n y: a.y + (b.y - a.y) * t,\n z: a.z + (b.z - a.z) * t,\n }\n}\n\nfunction lerpRgb(\n a: [number, number, number],\n b: [number, number, number],\n t: number\n): [number, number, number] {\n return [\n a[0] + (b[0] - a[0]) * t,\n a[1] + (b[1] - a[1]) * t,\n a[2] + (b[2] - a[2]) * t,\n ]\n}\n\nfunction clamp01(value: number): number {\n return Math.min(1, Math.max(0, value))\n}\n\n/**\n * A colour as the **linear** RGB triple a vertex attribute holds.\n *\n * The same conversion `grid3d.ts` documents and for the same reason: three\n * reads vertex colours as linear, so an sRGB channel uploaded raw paints far\n * too bright. Skipping it here would wash every ribbon out against the atoms\n * beside it, which take their colour through the material instead.\n */\nfunction linearRgb(value: Color): [number, number, number] {\n const [r, g, b] = resolveColor3D(value)\n return [srgbToLinear(r), srgbToLinear(g), srgbToLinear(b)]\n}\n\n/** The sRGB transfer function, inverted. three's own `SRGBToLinear`. */\nfunction srgbToLinear(channel: number): number {\n return channel < 0.04045\n ? channel * 0.0773993808\n : Math.pow(channel * 0.9478672986 + 0.0521327014, 2.4)\n}\n", "import { Protein } from \"./protein\";\n\n/**\n * Every node type this package publishes.\n *\n * A host registers these by handing the array to an engine —\n * `new Engine(platform, { nodes: NODES })` — which reads each class's `@node()`\n * key. The decorator only *declares* that key: nothing is registered by the mere\n * act of importing a module, so a registry holds exactly what its owner asked\n * for and two engines in one process can differ about it.\n *\n * Listing class **values** is also what survives bundling. This package declares\n * `sideEffects: false`, which licenses a bundler to drop a module nothing\n * imports a value from, and a document names a node type by string — so\n * `import \"./protein\"` for its side effect is exactly what gets shaken out, where an\n * array of bindings is a data dependency that cannot be.\n */\nexport const NODES = [Protein];\n"],
5
+ "mappings": "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",
6
+ "names": ["VAN_DER_WAALS", "DEFAULT_VAN_DER_WAALS", "COVALENT", "DEFAULT_COVALENT", "CPK", "UNKNOWN_ELEMENT_COLOR", "vanDerWaalsRadius", "element", "covalentRadius", "elementColor", "AMINO_ACIDS", "NUCLEOTIDES", "WATERS", "residueKind", "name", "code", "traceAtomName", "kind", "elementOf", "declared", "atomName", "stated", "letters", "pair", "EMPTY_STRUCTURE", "deriveStructure", "id", "title", "atoms", "spans", "inferBonds", "traceChains", "measure", "BOND_TOLERANCE", "MIN_BOND_LENGTH", "MAX_BOND_LENGTH", "bonds", "cells", "radii", "atom", "covalentRadius", "key", "cellKey", "bucket", "cx", "cy", "cz", "dx", "dy", "dz", "neighbours", "i", "j", "a", "b", "limit", "dxx", "dyy", "dzz", "squared", "x", "y", "z", "CHAIN_BREAK_DISTANCE", "assign", "secondaryLookup", "carbonyls", "carbonylDirections", "chains", "current", "previous", "traceAtomName", "broken", "distance", "residueKey", "chain", "byChain", "span", "residues", "seq", "residue", "alpha", "oxygen", "directions", "ca", "o", "length", "sx", "sy", "sz", "center", "radius", "d", "parseStructure", "text", "id", "EMPTY_STRUCTURE", "isMmcif", "parseMmcif", "parsePdb", "head", "atoms", "spans", "title", "line", "record", "atom", "parsePdbAtom", "span", "pdbSpan", "deriveStructure", "hetero", "altLoc", "x", "y", "z", "name", "residue", "kind", "residueKind", "elementOf", "PDB_SPAN_END_AT", "chainAt", "startAt", "chain", "startText", "endText", "start", "end", "lines", "mmcifAtoms", "mmcifSpans", "mmcifTitle", "model", "row", "mmcifRows", "thisModel", "category", "type", "i", "inline", "unquote", "next", "prefix", "single", "item", "gap", "field", "columns", "cursor", "header", "trimmed", "values", "splitCifRow", "index", "value", "char", "first", "command", "driveCommand", "easeInOut", "node", "Canvas3D", "Geo", "Graphics3D", "Mat", "Scene3D", "property", "lerpColor3D", "cameraOrbit", "resolveColor3D", "snapValue", "orbitProps", "PROTEIN_REPRESENTATIONS", "PROTEIN_COLOR_SCHEMES", "CHAIN_COLORS", "SECONDARY_COLORS", "SPECTRUM_START", "SPECTRUM_END", "SPECTRUM_SATURATION", "SPECTRUM_LIGHTNESS", "DEFAULT_PROTEIN_PALETTE", "hslHex", "CHAIN_COLOR_COUNT", "PROTEIN_PALETTE_FIELDS", "_", "index", "PROTEIN_PALETTE_DEFAULTS", "colour", "lerpHexColor", "from", "to", "t", "a", "hexRgb", "b", "byte", "x", "y", "value", "hex", "int", "atomColors", "structure", "scheme", "uniform", "palette", "atom", "elementColor", "chains", "chainOrder", "chainColor", "lookup", "secondaryByResidue", "residueKey", "positions", "residuePositions", "position", "spectrum", "traceColors", "chain", "point", "last", "hexHsl", "clamp01", "hue", "saturation", "lightness", "h", "c", "m", "r", "g", "max", "min", "l", "d", "s", "order", "colours", "atomRadius", "scale", "vanDerWaalsRadius", "stickPlacement", "radius", "dx", "dy", "dz", "length", "upTo", "z", "axisX", "axisZ", "axisLength", "angle", "sin", "backboneRuns", "colors", "splitBySecondary", "runs", "color", "current", "run", "hexOf", "resolveColor3D", "SECTION", "ARROW_WIDTH", "ARROW_RESIDUES", "ARROW_TIP", "subdivisionsFor", "quality", "cartoonSpans", "chain", "colors", "spans", "point", "index", "color", "current", "previous", "span", "cartoonGeometry", "options", "samples", "sampleSpan", "sweep", "profileFor", "points", "count", "section", "radius", "frames", "continuousFrames", "tints", "linearRgb", "arrow", "steps", "curve", "carried", "positions", "i", "last", "s", "t", "catmullRom", "lerpVector", "lerpRgb", "position", "tangent", "normalize", "subtract", "normal", "perpendicular", "binormal", "cross", "along", "width", "clamp01", "direction", "binormalAt", "lengthOf", "dot", "negate", "before", "here", "after", "secondary", "sides", "profile", "angle", "u", "v", "ring", "sample", "vertex", "n", "normalize2", "j", "next", "a", "b", "i0", "i1", "i2", "i3", "capEnd", "forward", "sign", "nx", "ny", "nz", "center", "first", "p0", "p1", "p2", "p3", "t2", "t3", "c", "d", "length", "axis", "out", "fallback", "value", "r", "g", "srgbToLinear", "channel", "MIN_ZOOM", "MAX_ZOOM", "DEFAULT_ZOOM", "BALL_SCALE", "Protein", "Canvas3D", "props", "args", "orbitProps", "easeInOut", "speed", "elevation", "zoom", "sway", "breathe", "cycle", "settle", "duration", "easing", "fromOrbit", "fromElevation", "fromZoom", "period", "driveCommand", "t", "seconds", "blend", "phase", "driftOrbit", "driftZoom", "scene", "Scene3D", "structure", "radius", "distance", "clampZoom", "cameraOrbit", "g3", "Graphics3D", "center", "built", "run", "Geo", "Mat", "ribbon", "placements", "tints", "key", "quality", "hexOf", "paletteKey", "cached", "palette", "colors", "atomColors", "showing", "atom", "representation", "spaceFilling", "atomic", "drawAtom", "index", "kind", "scale", "i", "atomRadius", "bond", "a", "b", "mid", "stickPlacement", "framingRadius", "cartoon", "uniform", "chain", "traceColors", "span", "cartoonSpans", "geometry", "cartoonGeometry", "backboneRuns", "runIndex", "__decorate", "property", "EMPTY_STRUCTURE", "snapValue", "resolveColor3D", "lerpColor3D", "lerpHexColor", "DEFAULT_PROTEIN_PALETTE", "command", "node", "paletteKey", "palette", "framingRadius", "built", "structure", "center", "furthest", "reach", "x", "y", "z", "d", "sphere", "run", "point", "ribbon", "position", "i", "clampZoom", "zoom", "MAX_ZOOM", "MIN_ZOOM", "DEFAULT_ZOOM", "NODES", "Protein"]
7
+ }
@@ -0,0 +1,11 @@
1
+ {
2
+ "name": "@motionscript/molecule",
3
+ "version": "0.1.0-alpha.0",
4
+ "exports": {
5
+ ".": "dist/browser/index.js"
6
+ },
7
+ "peers": [
8
+ "@motionscript/core"
9
+ ],
10
+ "assets": {}
11
+ }
@@ -0,0 +1,3 @@
1
+ export * from "./protein/index.js";
2
+ export { NODES } from "./nodes.js";
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,WAAW,CAAC;AAC1B,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,3 @@
1
+ export * from "./protein/index.js";
2
+ export { NODES } from "./nodes.js";
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,WAAW,CAAC;AAC1B,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC","sourcesContent":["export * from \"./protein\";\nexport { NODES } from \"./nodes\";\n"]}
@@ -0,0 +1,18 @@
1
+ import { Protein } from "./protein/index.js";
2
+ /**
3
+ * Every node type this package publishes.
4
+ *
5
+ * A host registers these by handing the array to an engine —
6
+ * `new Engine(platform, { nodes: NODES })` — which reads each class's `@node()`
7
+ * key. The decorator only *declares* that key: nothing is registered by the mere
8
+ * act of importing a module, so a registry holds exactly what its owner asked
9
+ * for and two engines in one process can differ about it.
10
+ *
11
+ * Listing class **values** is also what survives bundling. This package declares
12
+ * `sideEffects: false`, which licenses a bundler to drop a module nothing
13
+ * imports a value from, and a document names a node type by string — so
14
+ * `import "./protein/index.js"` for its side effect is exactly what gets shaken out, where an
15
+ * array of bindings is a data dependency that cannot be.
16
+ */
17
+ export declare const NODES: (typeof Protein)[];
18
+ //# sourceMappingURL=nodes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nodes.d.ts","sourceRoot":"","sources":["../src/nodes.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,KAAK,oBAAY,CAAC"}
package/dist/nodes.js ADDED
@@ -0,0 +1,18 @@
1
+ import { Protein } from "./protein/index.js";
2
+ /**
3
+ * Every node type this package publishes.
4
+ *
5
+ * A host registers these by handing the array to an engine —
6
+ * `new Engine(platform, { nodes: NODES })` — which reads each class's `@node()`
7
+ * key. The decorator only *declares* that key: nothing is registered by the mere
8
+ * act of importing a module, so a registry holds exactly what its owner asked
9
+ * for and two engines in one process can differ about it.
10
+ *
11
+ * Listing class **values** is also what survives bundling. This package declares
12
+ * `sideEffects: false`, which licenses a bundler to drop a module nothing
13
+ * imports a value from, and a document names a node type by string — so
14
+ * `import "./protein/index.js"` for its side effect is exactly what gets shaken out, where an
15
+ * array of bindings is a data dependency that cannot be.
16
+ */
17
+ export const NODES = [Protein];
18
+ //# sourceMappingURL=nodes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nodes.js","sourceRoot":"","sources":["../src/nodes.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC;;;;;;;;;;;;;;GAcG;AACH,MAAM,CAAC,MAAM,KAAK,GAAG,CAAC,OAAO,CAAC,CAAC","sourcesContent":["import { Protein } from \"./protein\";\n\n/**\n * Every node type this package publishes.\n *\n * A host registers these by handing the array to an engine —\n * `new Engine(platform, { nodes: NODES })` — which reads each class's `@node()`\n * key. The decorator only *declares* that key: nothing is registered by the mere\n * act of importing a module, so a registry holds exactly what its owner asked\n * for and two engines in one process can differ about it.\n *\n * Listing class **values** is also what survives bundling. This package declares\n * `sideEffects: false`, which licenses a bundler to drop a module nothing\n * imports a value from, and a document names a node type by string — so\n * `import \"./protein\"` for its side effect is exactly what gets shaken out, where an\n * array of bindings is a data dependency that cannot be.\n */\nexport const NODES = [Protein];\n"]}
@@ -0,0 +1,52 @@
1
+ /**
2
+ * The chemistry the {@link Protein} node draws from: how big an atom is, what
3
+ * colour it conventionally takes, what counts as a bond, and which residues are
4
+ * part of a polymer chain rather than sitting beside it.
5
+ *
6
+ * Tables rather than a dependency, for the same reason `graph3d/expression.ts`
7
+ * is a parser rather than a maths library: this package has to stay loadable in
8
+ * a bare Node process with no DOM, and what a molecular-graphics library would
9
+ * bring with it — a renderer, a fetch layer, a DOM — is everything the scene
10
+ * builder was split apart to avoid.
11
+ *
12
+ * The numbers are the standard ones. Van der Waals radii are Bondi's, covalent
13
+ * radii are Cordero's, and the colours are the CPK scheme every molecular viewer
14
+ * has used since Corey and Pauling built theirs out of painted wood — which is
15
+ * the point of using them: a biologist reads oxygen as red before they have
16
+ * read the legend.
17
+ */
18
+ /** What an element nobody has a colour for is drawn in. */
19
+ export declare const UNKNOWN_ELEMENT_COLOR = "#ff1493";
20
+ /** An element's van der Waals radius in Ångströms. */
21
+ export declare function vanDerWaalsRadius(element: string): number;
22
+ /** An element's covalent radius in Ångströms — half of a bond it makes. */
23
+ export declare function covalentRadius(element: string): number;
24
+ /** The CPK colour for an element, as a `#rrggbb` string. */
25
+ export declare function elementColor(element: string): string;
26
+ /** What a residue is, which decides whether it joins a backbone trace. */
27
+ export type ResidueKind = "amino" | "nucleic" | "water" | "other";
28
+ /** Classifies a residue by its three-letter code. */
29
+ export declare function residueKind(name: string): ResidueKind;
30
+ /**
31
+ * The atom a residue's backbone trace passes through: the α-carbon of an amino
32
+ * acid, the phosphorus of a nucleotide.
33
+ *
34
+ * One atom per residue rather than the whole backbone, because what the trace is
35
+ * *for* is the smooth curve a ribbon is swept along, and the N–CA–C zig-zag
36
+ * would put a kink at every residue. Every molecular viewer traces α-carbons for
37
+ * the same reason.
38
+ */
39
+ export declare function traceAtomName(kind: ResidueKind): string | null;
40
+ /**
41
+ * The element an atom belongs to, from the file's element column when it has
42
+ * one and from the atom's *name* when it doesn't.
43
+ *
44
+ * The fallback matters more than it looks: the element column is right-justified
45
+ * in columns 77–78 of a PDB record and plenty of older files, and most files
46
+ * written by hand, simply stop before it. The name is then all there is, and
47
+ * reading it is not quite trimming the digits off — an amino acid's `CA` is a
48
+ * carbon and an ion's `CA` is calcium, and the two are told apart only by what
49
+ * residue they sit in. Hence {@link kind}.
50
+ */
51
+ export declare function elementOf(declared: string, atomName: string, kind: ResidueKind): string;
52
+ //# sourceMappingURL=chemistry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chemistry.d.ts","sourceRoot":"","sources":["../../src/protein/chemistry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAyFH,2DAA2D;AAC3D,eAAO,MAAM,qBAAqB,YAAY,CAAA;AAE9C,sDAAsD;AACtD,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAEzD;AAED,2EAA2E;AAC3E,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAEtD;AAED,4DAA4D;AAC5D,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAEpD;AAgCD,0EAA0E;AAC1E,MAAM,MAAM,WAAW,GAAG,OAAO,GAAG,SAAS,GAAG,OAAO,GAAG,OAAO,CAAA;AAEjE,qDAAqD;AACrD,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,WAAW,CAMrD;AAED;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,WAAW,GAAG,MAAM,GAAG,IAAI,CAI9D;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,SAAS,CACvB,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,WAAW,GAChB,MAAM,CA2BR"}