Context
The reference page at https://github.com/pbakaus/radiant/blob/main/static/sequin-wave.html is a fullscreen WebGL demo. Its core effect comes from a fragment shader that does the following:
- builds a hex/sequin tiling
- samples a layered wave field
- tilts each virtual reflective disc according to that wave
- computes a tight specular response for flashes and sheen
That logic maps well to a plane material in three.js, but several parts of the demo do not belong in a reusable component:
- fullscreen canvas handling
- page label and page background styling
- pointer and touch event listeners
- extra cursor-driven specular lighting
Within this repository, the effect must be expressed as a canvas-side tresjs/three component. Shader files must live under src/shader/<component-name>/, and the component must be integrated through src/dcsConfig.ts without changing the generic preview pages.
Goals / Non-Goals
Goals:
- Provide a plane component with independently configurable length and width that sits flat on the ground by default.
- Keep the visual core of the reference effect: dense reflective cells, layered wave motion, and strong view-dependent flashes.
- Let developers tune the effect through a concise set of safe parameters.
- Keep all core rendering inside the three.js canvas pipeline.
- Keep the implementation self-contained and component-scoped.
Non-Goals:
- Do not reproduce fullscreen page presentation from the reference.
- Do not implement pointer, touch, click, or hover-driven light interaction.
- Do not turn the panel into a volumetric liquid body or a container-based component.
- Do not add generic preview infrastructure changes.
- Do not introduce per-cell real geometry; the panel should stay shader-based for stability and performance.
Decisions
Decision: Use a single PlaneGeometry with independently configurable length and width plus a custom shader material instead of instanced reflective discs.
- Rationale: The reference effect is already fundamentally fragment-driven. A single plane keeps the component lightweight and appropriate for a ground panel.
- Alternative considered: Instanced disc geometry for each sequin. This would increase geometry and update cost without meaningfully improving the intended medium-distance usage.
Decision: Correct the shader field by panel aspect ratio so reflective cells keep a stable shape when length and width diverge.
- Rationale: Once the panel can become rectangular, UV-space sampling alone would visibly stretch the sequins and wave field. Passing panel length and width into the shader keeps the effect visually consistent.
- Alternative considered: Leaving the pattern in normalized UV space. This would make the reflective cells look squashed or elongated on non-square panels.
Decision: Preserve the reference demo's hex tiling, layered wave sampling, tilt direction estimation, and specular layering in the fragment shader.
- Rationale: Those are the parts that define the look and directly answer the user's request.
- Alternative considered: Replacing the pattern with generic noise or texture scrolling. That would lose the recognizable reflective-cell character.
Decision: Remove the cursor-driven light source entirely.
- Rationale: The user explicitly asked to remove the click/interaction effect, and page-level interaction does not belong in a reusable service component.
- Alternative considered: Keeping an optional pointer highlight toggle. This would add complexity and contradict the requested first version.
Decision: Keep color tuning concise with three color controls: base tone, sheen tone, and highlight tone.
- Rationale: This provides enough freedom to shift between cold and warm looks without exposing the entire internal lighting composition.
- Alternative considered: Exposing every intermediate ramp color. That would overcomplicate the control panel.
Decision: Make the component ground-ready by rotating the internal plane into the XZ orientation and setting a near-ground default placement in the registry.
- Rationale: This matches the intended use on floor-like surfaces while preserving a simple external object transform contract.
- Alternative considered: Leaving the plane vertical and requiring external rotation every time. That would create avoidable setup friction.
Risks / Trade-offs
Risk: The panel depends on view-dependent specular math, so it will feel more alive from angled cameras than from a perfectly top-down view.
- Mitigation: Keep ambient sheen and wave modulation strong enough that the panel still reads well when viewed from higher angles.
Risk: Very high density plus large sequin size can overfill cells and reduce readability.
- Mitigation: Clamp
tileDensity and sequinSize to safe ranges in both the component and registry config.
Risk: A fully opaque backing plate may look too heavy in some scenes, while a fully transparent panel can lose the patterned floor identity.
- Mitigation: Blend between dark backing gaps and bright sequins, and expose panel
opacity as a top-level control.
Migration Plan
- Create the new change artifacts for the component.
- Add
dreamyFluidPanelComponent.vue with a single plane mesh and custom shader material.
- Add component-scoped vertex and fragment shader files under
src/shader/dreamyFluidPanelComponent/.
- Register the component in
src/dcsConfig.ts with defaults, controls, description, preview path, and default ground-oriented object transform.
- Add a preview asset under
public/preview/.
- Run
npm run build to validate that the component integrates cleanly without touching preview infrastructure.
Open Questions
- None for the current scope.