Ripple around an obstacle
Use a bounded water surface with a normalized rectangular obstacle.
APIs demonstrated
Section titled “APIs demonstrated”- waterSurface from
@pibbl/core/textures - WaterSurfaceObstacle from
@pibbl/core/textures - WaterSurfaceOptions from
@pibbl/core/textures - WaterSurfaceMessage from
@pibbl/core/textures
Complete example
Section titled “Complete example”Plain source. Run as a browser module after the document body exists. TSX uses jsx: "react-jsx" and jsxImportSource: "@pibbl/core".
import { pibbl, Group, Rectangle, Circle, Text, signal } from "@pibbl/core";import { displacement } from "@pibbl/core/filters";import { waterSurface, useTexture, type WaterSurfaceOptions, type WaterSurfaceMessage, type WaterSurfaceObstacle,} from "@pibbl/core/textures";
const width = 360, height = 240;const canvas = document.createElement("canvas");canvas.width = width;canvas.height = height;canvas.style.touchAction = "none";document.body.append(canvas);const enabled = signal(true);const toggle = document.createElement("input");toggle.type = "checkbox";toggle.checked = true;toggle.onchange = () => enabled.set(toggle.checked);const label = document.createElement("label");label.append(toggle, "Refraction");document.body.append(label);function Basin() { const island: WaterSurfaceObstacle = { x: 0.42, y: 0.35, width: 0.16, height: 0.3, }; const options: WaterSurfaceOptions = { seed: 5, speed: 0.5, viscosity: 0.2, obstacles: [island], }; const water = useTexture(waterSurface(options), { resolution: { width: 240, height: 160 }, }); const map = water.paint({ mode: "stretch" }); const filter = displacement({ map, strength: 18, region: { x: 0, y: 0, width, height }, }); const send = (x: number, y: number) => { const message: WaterSurfaceMessage = { type: "ripple", x: Math.max(0, Math.min(1, x / width)), y: Math.max(0, Math.min(1, y / height)), }; water.send(message); }; return ( <> <Group style={{ filter: enabled.get() ? filter : undefined }}> <Rectangle style={{ width, height, fill: "#c3e7e8" }} /> {Array.from({ length: 54 }, (_, i) => ( <Rectangle key={i} style={{ left: (i % 9) * 40 + 2, top: Math.floor(i / 9) * 40 + 2, width: 36, height: 36, fill: (i + Math.floor(i / 9)) % 2 ? "#1d8597" : "#80c4ce", }} /> ))} <Circle style={{ cx: 180, cy: 120, radius: 65, fill: "#1d3948", stroke: "#f2d069", strokeWidth: 8, }} /> <Text style={{ left: 180, top: 133, font: "bold 38px sans-serif", textAlign: "center", fill: "#ffffff", }} > Pibbl </Text> </Group> <Rectangle pointerEvents="none" style={{ left: island.x * width, top: island.y * height, width: island.width * width, height: island.height * height, fill: "#596e70", stroke: "#fff", strokeWidth: 2, }} /> <Rectangle style={{ width, height, fill: "#00000000", cursor: "crosshair" }} onPointerDown={(event) => { event.currentTarget.setPointerCapture(event.pointerId); send(event.x, event.y); }} onPointerUp={(event) => event.currentTarget.releasePointerCapture(event.pointerId) } /> </> );}const controller = pibbl(canvas, <Basin />);window.addEventListener("pagehide", () => controller.dispose(), { once: true });Usage notes
Section titled “Usage notes”- Press to ripple the tiled image. The outlined island marks the normalized obstacle, which affects the height field rather than Canvas hit testing.
Documentation version
Section titled “Documentation version”Documentation built with @pibbl/core 0.0.2, revision 272a94a. ALPHA — NOT FOR PRODUCTION USE.