Skip to content

Add a periodic images control to the 3D view - #449

Merged
andeplane merged 5 commits into
mainfrom
claude/issue-70-periodic-images
Oct 16, 2026
Merged

andeplane merged 5 commits into
mainfrom
claude/issue-70-periodic-images

Conversation

@andeplane

@andeplane andeplane commented Oct 16, 2026 •

Copy link
Copy Markdown
Owner

Requested by Anders Hafreager · project thread

Before: the 3D view stopped at the simulation box edge, so crystals and other periodic structures looked cut off.

After: an "Images 1×1×1" control next to "Color atoms" sets how many copies to draw along x, y and z. Copies of atoms and bonds are shifted by the box vectors (triclinic boxes included), keep each atom's color, follow the running simulation, and are display-only. 1×1×1 renders exactly as before.

How: SyncParticlesModifier appends the copies after the real atoms using shifts computed from the streamed frame's boxMatrix (src/modifiers/periodicImages.ts); copies reuse their atom's ID, so per-ID colors, radii and selection apply to every copy, and clicking a copy selects the real atom. SyncBondsModifier copies bonds with the same shifts, and ColorModifier and the bond count only look at real atoms. The total is capped at 500,000 drawn atoms and 10 copies per axis, reducing the largest axis first, and the control shows the reduced count. Unit tests cover the shift math for orthogonal and triclinic boxes, the copying, the cap and colors. A new e2e spec (e2e/periodic-images.spec.ts) runs a triclinic fcc crystal at 2×2×1 and checks 256 → 1024 drawn atoms with copies exactly one box vector apart; it passed in Chromium locally, though only with longer waits on an overloaded machine. The same check was done by hand in the browser, including that copies follow the run and 1×1×1 restores 256 atoms.

Known limitations: copies extend only in the positive direction, the box wireframe is drawn around the original box only, the camera doesn't re-frame to fit the copies, and the setting persists for the browser session like the color setting.

Closes #70

🤖 Generated with Claude Code

https://claude.ai/code/session_01HB39E3Y4jpon2ijSXT7emN

Draw nx×ny×nz copies of the particles and bonds shifted by the box
vectors, like OVITO's Replicate modifier. Visual only. SyncParticles
writes the images after the real atoms, and they keep their atom's ID, so
per-atom colors, radii, picking and selection apply to every copy. Works
for triclinic boxes. The total is capped at 500k rendered atoms.

The run view gets an "Images 1×1×1" control next to "Color atoms".

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HB39E3Y4jpon2ijSXT7emN
@andeplane andeplane self-assigned this Oct 16, 2026
@andeplane
andeplane marked this pull request as ready for review October 16, 2026 07:08
Brings the periodic images branch up to date with main (#455-#463),
porting the feature onto main's streamed engine frames.

Conflict resolution:
- syncparticlesmodifier.ts: take main's frame-based copy (frame.count,
  frame.positions/types/ids, native radii loop) and replicate the image
  blocks on top of it. The image shifts now come from frame.boxMatrix
  (row-major, rows = a, b, c) instead of getCellMatrixPointer() + HEAPF32.
  The native-radii loop stays over the real atoms; setRadius reaches every
  image through the per-ID slot chain.
- syncbondsmodifier.ts: take main's frame.bondCount/bondFirst/bondSecond
  and replicate both endpoints by output.imageShifts as before.
- colormodifier.ts: keep main's perAtomValues(frame, ...) and slice it to
  the real particle count; runByType still loops over real atoms only.
- store/processing.ts: keep main's frame-based box/origo/dimension/
  unitStyle and the realParticleCount() bond count.
- RunDetail.tsx: take main's split (RunDetailHeader/RunViewport); the
  "Images" control now sits after "Color atoms" in RunDetailHeader.tsx.

Follow-ups for main's architecture:
- PeriodicImagesControl finds SyncParticlesModifier in the
  EngineRegistry (useEngine().modifiers) instead of
  state.processing.postTimestepModifiers.
- syncparticlesmodifier.test.ts feeds an EngineFrame instead of a fake
  wasm heap.
- e2e/periodic-images.spec.ts reads particles from the exported engine
  registry and waits for the first synced box.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HB39E3Y4jpon2ijSXT7emN
Each per-axis input now holds its text as a local draft and only commits
when it parses to an integer in 1..MAX_IMAGES_PER_AXIS. Clearing the
field no longer snaps it to 1 (so backspacing "2" and typing "3" gives 3,
not 13 clamped to 10); on blur or Enter an empty/invalid draft reverts
to the current count.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HB39E3Y4jpon2ijSXT7emN
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HB39E3Y4jpon2ijSXT7emN
@andeplane
andeplane merged commit e4888dd into main Oct 16, 2026
3 checks passed
@andeplane
andeplane deleted the claude/issue-70-periodic-images branch October 16, 2026 14:10
andeplane added a commit that referenced this pull request Oct 16, 2026
<!-- claude-projects-attribution: {"github_login":"andeplane"} -->
_Requested by **Anders Hafreager** · [project
thread](https://claude.ai/code/project/chan_01G7VSoPZwxUhEt8N3Ft8nF9?thread=cmsg_01G7VSoPZwxUhEt8N3Ft8nF9Pywr46zeLgPABfk6rjQTj9)_

Before: there was no way to see inside a bulk system; interior features
like crack tips and defects were always hidden behind the surface.

After: a "Slice" button on the run screen opens a small panel with a
plane normal (x, y, z or a custom vector), a position, a width and an
invert option. Only whole atoms inside the slab are drawn, and bonds
with an end outside it are hidden too. The slab follows the running
simulation and updates immediately when moved, also while paused. With
it off, rendering is unchanged.

How: a new `SliceModifier`, registered in
`EngineRegistry.createDefaultModifiers()` after Colors and included in
the rendering-only pass, hides atoms by setting their per-atom radius to
zero through `particleAttributes.ts`; the picking shader reads the same
radius, so hidden atoms can't be clicked and visible ones still pick the
right ID. Clipping planes were rejected because they cut spheres in half
and don't hide bonds. Only slots whose hidden state changed are
rewritten on the GPU, and switching off restores radii once. The slice
is turned off in `runHost.beginFreshRun` (not on continuations), since
its position is in absolute box units. The Slice button sits in
`RunDetailHeader` next to "Color atoms", and its popover picks left or
right alignment from the space available so it never pushes the run
column into a horizontal scroll. Unit tests cover slab selection (axes,
custom and tilted normals, edges, invert), hiding and restoring atoms
and bonds, the box-range helper including triclinic boxes, the popover
alignment choice and the fresh-run reset. Checked in Chromium on a
216-atom lattice with 540 bonds: every atom's and bond's radius matched
the slab for z, invert, moving the slab while running and while paused,
and switching off; the popover stayed inside the run column at 900–1440
px.

Merge note: the slice tests drawn positions per draw slot rather than
LAMMPS atom indices, so it also slices the periodic image copies from
#449 (appended inside the Particles step, before Slice). Both PRs change
`src/rendering/particleAttributes.ts` and the modifier list in
`EngineRegistry`, so whichever merges second needs those reconciled:
hidden flags are per slot here, while #449 makes per-ID styles reach
every copy.

Known limitations: the panel closes on any click outside it (including
dragging the view); position/width are in absolute box units with a ±50
slider fallback before the box is known; below roughly 800 px the run
column is narrower than the panel.

Closes #71

🤖 Generated with [Claude Code](https://claude.com/claude-code)

https://claude.ai/code/session_01HB39E3Y4jpon2ijSXT7emN
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add a periodic images modifier

2 participants