diff --git a/apps/examples/src/examples/fog-of-war/FogOfWarExample.tsx b/apps/examples/src/examples/fog-of-war/FogOfWarExample.tsx new file mode 100644 index 000000000..ee7f1e7ad --- /dev/null +++ b/apps/examples/src/examples/fog-of-war/FogOfWarExample.tsx @@ -0,0 +1,106 @@ +import { useEffect, useRef } from 'react' +import { Box, TLComponents, Tldraw, Vec, useEditor, useReactor } from 'tldraw' +import 'tldraw/tldraw.css' + +const CELL_SIZE = 32 +const COUNT = 100 + +const boxes: Box[][] = [] +const cells: boolean[][] = [] +for (let i = 0; i < COUNT; i++) { + cells[i] = [] + boxes[i] = [] + for (let j = 0; j < COUNT; j++) { + cells[i].push(false) + boxes[i].push( + new Box((i - COUNT / 2) * CELL_SIZE, (j - COUNT / 2) * CELL_SIZE, CELL_SIZE, CELL_SIZE) + ) + } +} + +export function Fog() { + const rCanvas = useRef(null) + const rVisibility = useRef(cells) + const editor = useEditor() + + useEffect(() => { + const cvs = rCanvas.current! + const rect = cvs.getBoundingClientRect() + cvs.width = rect.width + cvs.height = rect.height + }, [editor]) + + useReactor( + 'update fog', + () => { + const cells = rVisibility.current + const shapes = editor.getCurrentPageShapes() + for (const shape of shapes) { + const point = editor.getShapePageBounds(shape)!.point + const geometry = editor.getShapeGeometry(shape) + for (let i = 0; i < boxes.length; i++) { + for (let j = 0; j < boxes[i].length; j++) { + const box = boxes[i][j] + box.translate(Vec.Neg(point)) + if (geometry.bounds.collides(box)) { + cells[i][j] = true + } + box.translate(point) + } + } + } + const cvs = rCanvas.current! + const ctx = cvs.getContext('2d')! + + ctx.resetTransform() + const camera = editor.getCamera() + + ctx.clearRect(0, 0, cvs.width, cvs.height) + ctx.fillStyle = 'rgba(0,0,0,0.9)' + ctx.fillRect(0, 0, cvs.width, cvs.height) + + ctx.translate(100, 100) + ctx.scale(camera.z, camera.z) + ctx.translate(camera.x, camera.y) + + for (let i = 0; i < boxes.length; i++) { + for (let j = 0; j < boxes[i].length; j++) { + if (!cells[i][j]) continue + const box = boxes[i][j] + ctx.filter = 'drop-shadow(100px)' + ctx.clearRect(box.x, box.y, box.width, box.height) + } + } + }, + [editor] + ) + + return ( + + ) +} + +const components: TLComponents = { + InFrontOfTheCanvas: Fog, +} + +export default function BasicExample() { + return ( +
+ +
+ ) +} diff --git a/apps/examples/src/examples/fog-of-war/README.md b/apps/examples/src/examples/fog-of-war/README.md new file mode 100644 index 000000000..977c170d8 --- /dev/null +++ b/apps/examples/src/examples/fog-of-war/README.md @@ -0,0 +1,10 @@ +--- +title: Fog of war +component: ./FogOfWarExample.tsx +category: basic +keywords: [ui, fog, overlay] +--- + +--- + +This example shows how you might keep an HTML canvas in sync with canvas content. It implements a simple "fog of war" effect, where the canvas is covered by a black overlay that can be cleared by drawing shapes on the canvas.