Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Dynamic Structure Updates

Viso supports live manipulation: structures can be updated mid-session by computational backends (Rosetta energy minimization, ML structure prediction) or user actions (mutations, drag operations).

All structural mutations happen on your molex::Assembly. After each batch of changes, push the new snapshot to the engine via engine.set_assembly(Arc::new(assembly.clone())). The engine itself is read-only with respect to structural state.

Per-Entity Coordinate Updates

To stream new atom positions for an existing entity, mutate the relevant entity’s coordinates on your Assembly (using molex’s update_protein_entities codec helper, or assembly.update_positions(eid, &coords) for direct position updates), then re-publish:

#![allow(unused)]
fn main() {
use std::sync::Arc;
use molex::ops::codec::update_protein_entities;

// Apply caller-provided Coords through molex's shared codec so the
// path matches the byte-format pipeline.
let mut entities = vec![assembly.entity(eid).unwrap().clone()];
update_protein_entities(&mut entities, &new_coords);
if let Some(updated) = entities.into_iter().next() {
    assembly.remove_entity(eid);
    assembly.add_entity(updated);
}

engine.set_assembly(Arc::new(assembly.clone()));
}

To make the next sync animate (instead of snapping), queue a per-entity behavior override before re-publishing:

#![allow(unused)]
fn main() {
engine.set_entity_behavior(entity_id, Transition::smooth());
engine.set_assembly(Arc::new(assembly.clone()));
}

The engine queues the transition for the affected entity on its next sync, regardless of whether the override was set before or after the set_assembly call (transitions are picked up in update).

Per-Entity Behavior Overrides

Override the default transition for a specific entity. Once set, every subsequent sync involving that entity uses the override (until cleared):

#![allow(unused)]
fn main() {
let eid = engine.entity_id(raw_id).expect("known entity");

engine.set_entity_behavior(eid, Transition::smooth());

// Subsequent re-publishes that touch this entity will use the
// override instead of the default transition.
engine.set_assembly(Arc::new(assembly.clone()));

// Revert to default:
engine.clear_entity_behavior(eid);
}

Transitions

Every update can specify a Transition controlling the visual animation:

#![allow(unused)]
fn main() {
// Instant snap (no animation; used internally for initial loads
// and trajectory frames).
Transition::snap()

// Standard smooth interpolation (300ms cubic-hermite ease-out).
Transition::smooth()

// Staggered per-residue wave (quadratic-out).
Transition::cascade(
    Duration::from_millis(500),
    Duration::from_millis(5),
)

// Allow backbone size changes (residue mutations).
Transition::smooth().allowing_size_change()
}

See Animation System for details on the data-driven phase model.

Preemption

If a new update arrives while an animation is playing, the current visual position becomes the new animation’s start state and the timer resets. This provides responsive feedback during rapid update cycles (e.g. Rosetta wiggle).

Constraint Visualization (Bands and Pulls)

Bands and pulls are not commands; they are stored constraint specs that the engine resolves to world-space positions every frame so they auto-track animated atoms. Steric clash arcs and exposed-hydrophobic markers work the same way.

Bands

A BandInfo references atoms structurally rather than by world-space position:

#![allow(unused)]
fn main() {
use viso::{AtomRef, BandInfo, BandTarget, BandType};

let band = BandInfo {
    anchor_a: AtomRef { residue: 42, atom_name: "CA".into() },
    anchor_b: BandTarget::Atom(AtomRef {
        residue: 87,
        atom_name: "CA".into(),
    }),
    strength: 1.0,
    target_length: 3.5,
    band_type: Some(BandType::Disulfide),
    is_pull: false,
    is_push: false,
    is_disabled: false,
    from_script: false,
};
}

BandTarget::Position(Vec3) anchors one end to a fixed world-space point (used for “space pulls”). band_type set to None lets the engine auto-detect the type from target_length.

Visual properties:

  • Radius scales with strength (0.1 to 0.4 Å)
  • Color depends on band_type: default (purple), backbone (yellow-orange), disulfide (yellow-green), H-bond (cyan)
  • Disabled bands are gray
  • Script-authored bands (from_script: true) render dimmer

Pulls

A PullInfo is a single active drag constraint. The atom is referenced structurally; the target is given in screen-space (physical pixels) and unprojected at the atom’s depth each frame so the drag stays parallel to the camera plane:

#![allow(unused)]
fn main() {
use viso::{AtomRef, PullInfo};

let pull = PullInfo {
    atom: AtomRef { residue: 42, atom_name: "CA".into() },
    screen_target: (mouse_x, mouse_y),
};
}

Pulls render as a purple cylinder from the atom to the target with a cone arrow head at the target end.

Update band and pull specs through the engine. Both methods replace the previous specs and re-resolve immediately:

#![allow(unused)]
fn main() {
engine.update_bands(vec![band1, band2]);
engine.update_pull(Some(pull));
engine.update_pull(None); // clear when drag ends
}

The engine resolves stored specs to world-space positions every frame, so bands and pulls track animated atoms automatically.

Clash Arcs

Steric clashes render as an electric arc between two atoms. A ClashInfo names each atom per-entity (an entity id plus an entity-local residue and PDB atom name) rather than by flat residue index:

#![allow(unused)]
fn main() {
use viso::{ClashEndpoint, ClashInfo};

let clash = ClashInfo {
    a: ClashEndpoint { entity: eid_a, residue: 12, atom_name: "CB".into() },
    b: ClashEndpoint { entity: eid_b, residue: 40, atom_name: "CG".into() },
    severity: 0.8, // drives emissive intensity and pulse brightness
};

engine.update_clashes(vec![clash]); // replaces the previous clash set
}

Exposed Hydrophobics

Flagged exposed-hydrophobic residues render as a “grease bead” at the sidechain anchor (CB if present, else the sidechain centroid, else CA). An ExposedHydrophobicInfo names the residue per-entity:

#![allow(unused)]
fn main() {
use viso::ExposedHydrophobicInfo;

engine.update_exposed_hydrophobics(vec![ExposedHydrophobicInfo {
    entity: eid,
    residue: 23,
}]);
}

Both update_clashes and update_exposed_hydrophobics replace the previous set and re-resolve immediately; the engine re-resolves their anchors every frame so the markers track animated atoms.

Host-Supplied Void Field

A host can push a precomputed void distance field to be meshed as a smooth blob alongside detected cavities:

#![allow(unused)]
fn main() {
engine.set_external_void_field(dims, origin, spacing, phi, threshold);
}

phi is a flat row-major scalar grid (phi[x*ny*nz + y*nz + z]) in marching-cubes polarity: high at void centers, near zero at atom walls and exterior. It is meshed at the positive threshold into a blob that carries the same cavity tint and breathing as a detected cavity. An empty phi (or any zero dimension) clears the field. Meshing runs on the background worker, so the result appears on the next frame after it completes.