Diagram - Laser Lab
Rotate real Diagram mirrors to guide a beam through three original puzzles.
#Turn a mirror, follow the light
Laser Lab is an interactive reflection demonstration. Guide the beam from the emitter through every numbered checkpoint and into the receiver. Three original optical benches add a second reflection, a blocker, then a four-mirror route with a possible loop.
- Choose Fire beam or Step to activate the beam. It stays inactive until you do.
- Select A in the mirror controls or select its pivot on the bench.
- Use −15°, +15° or enter a value in the angle field and choose Apply angle. You can also drag the corner rotation affordance; hold Shift to snap the gesture by 15°.
- Follow the live result above the bench and checkpoints below it. Mirror changes, Undo and Redo immediately update an activated beam. Fire replays the route; Pause, Step and Resume control that replay. A solved route stays visible during replay.
- When the active route passes every gate and reaches the receiver, Level complete appears immediately. There is no second Fire or run-confirmation step. Choose Next level when ready. Completing all three shows All levels complete and Replay lab; the solved board stays visible.
- Restart restores the current level. Choose another level to start a fresh puzzle.
Mirror positions and sizes stay fixed. Other objects are locked. Select/Pan, zoom and Fit remain available. On a narrow screen, Fit shows the bench and named mirror selection can reveal the active mirror at a readable scale.
#What Diagram supplies
The bench and all optical objects are ordinary Diagram nodes. Custom node content
renders the original emitter, mirror, checkpoint, blocker and receiver artwork.
The root supplies selection, corner rotation, navigation, edit policy and one
Store/Session history. Every accepted rotation uses
session.edit({ kind: 'rotate', degrees }, [mirrorId]); an exact angle subtracts
the mirror's current saved local rotation. A pointer gesture previews its transform
and commits one edit on release.
The host converts the public scene ref from useDiagramContext() local geometry into world points using
its published transform matrix. The solver intersects finite mirror segments,
reflects the ray, checks circular gates and the receiver, and stops at an opaque
blocker or the bench boundary. The beam SVG is projected inside the Diagram
viewport's world, so the same pan/zoom transform keeps artwork and beam aligned.
No internal DOM geometry, connector routing or external physics engine is involved.
The model is deliberately finite and educational: two-sided, zero-thickness mirrors, circular checkpoints, opaque polygon blockers and at most 24 segments. Collinear rays graze rather than reflect; a receiver hit requires every checkpoint. A boundary exit, blocked ray, receiver with missed checkpoints and exhausted segment budget have separate feedback. This does not simulate optical materials, beam splitting or physical light propagation.
#Keep four kinds of state separate
| State | Owner | History |
|---|---|---|
| Object identities, bounds, mirror rotations and metadata | Diagram Store | Ordinary rotation Undo/Redo |
| Selected mirror, focused object, active tool and viewport | Diagram Session | No model edits |
| Chosen level, hints and mirror-control visibility | Host UI | No model edits |
| Beam segments, visible segment count and result | Host solver and finite clock | No model edits |
Firing, pausing, stepping, hints and navigation never write the model. Rotation and history travel cancel any running beam clock and show a newly computed path. Restart and level changes cancel playback, replace the fixture, clear history, clear the active run's success cue and select A. These are fresh puzzle starts rather than undoable business edits. The level choice itself is not recovered by Undo.
There is no autoplay. A hidden model pauses the clock; returning leaves it paused until Resume. Destroy cancels the pending timeout and removes subscriptions, media-query and visibility listeners before disposing the Store. Reduced motion reveals the complete fired path without a traveling marker or scheduled progression. Before a valid route, Step exposes the replay sequence statically. Playback progress stays readable without live segment or animation-frame announcements.
Completion follows the activated live route, independently of the host clock. Before activation, mirror changes leave the beam hidden and the result Ready. After activation, any committed valid route immediately completes the level, including a route restored by Undo or Redo. Moving to an invalid route clears current success and advancement; returning to a valid one gives a new success cue. Once completed, the unchanged level keeps its success and Next level/Replay action throughout subsequent Fire, Pause, Step and Resume. Replay timing is separate from persistent completion. The compact result strip above the canvas reserves space so success does not shift or cover the bench. A short receiver pulse accompanies the static check; reduced motion keeps the check without the pulse. The result strip makes one polite announcement for each new valid-route transition, without moving focus or advancing automatically. Unchanged renders, pan/zoom, selection and replay do not repeat the cue. During pointer rotation the live path is provisional: release commits its route, while Escape restores the previous route without recording or celebrating the preview. Restart and level changes reset beam activation and the active cue. Completed-level records survive navigation within this mounted session; an activated invalid route removes the current level's record. Replay lab clears all.
#Adapt the example
Copy LaserLabDemo.vue, its LaserWorkbench.vue provider child, and levels.ts, solver.ts and playback.ts from the local helper folder. The workbench reads the current model, scene and session with useDiagramContext(). Keep those imports together. PrimeVue Button and InputText provide companion controls; individual PrimeIcons Vue imports supply icons. The authored artwork and local styles are included. Follow First Diagram for setup.
Replace the fixture in levels.ts with your own bounded bench. Give each object a
stable ID and retain the mirror's local horizontal reflective line, or change both
its artwork and sceneOptics endpoints together. Changing the bench dimensions
also requires updating the solver boundary. Edit the node templates to change
symbols without replacing Diagram selection and rotation behavior. Add a rule in
the pure solver, test its geometric edge cases, then adjust the result copy. Change
the surrounding controls independently of the runtime; HTML controls can call the
same public Session commands in a headless or Tailwind application.
Keyboard users can choose named mirrors, adjust the native numeric angle field, activate the rotation buttons and use all playback/history controls. Tab to a Diagram rotation handle and press Enter or Space for a 15° turn. Native field editing retains its normal keyboard behavior. Pointer rotation has no hidden post-gesture rounding; use Shift when a snapped gesture is useful.