```javascript // @run function final() { const [width, height] = [300, 300]; // Create PaintableCanvas instances const lightCanvas = new PaintableCanvas({width, height}); const occlusionCanvas = new PaintableCanvas({width, height}); const baseRayCount = 8; const rayCount = baseRayCount; const {plane, canvas, render} = threeJSInit(width, height, { uniforms: { lightTexture: {value: lightCanvas.texture}, occlusionTexture: {value: occlusionCanvas.texture}, dir_count: {value: rayCount}, size: {value: new THREE.Vector2(width, height)}, }, vertexShader: document.getElementById('vertexShader').textContent, fragmentShader: document.getElementById('fragmentShader').textContent }); lightCanvas.onUpdateTextures = () => render(); occlusionCanvas.onUpdateTextures = () => render(); const isOccluding = () => lightCanvas.currentColor.r === 0 && lightCanvas.currentColor.g === 0 && lightCanvas.currentColor.b === 0; const startDrawing = (e) => isOccluding() ? occlusionCanvas.startDrawing(e) : lightCanvas.startDrawing(e); const onMouseMove = (e) => { isOccluding() ? occlusionCanvas.onMouseMove(e) : lightCanvas.onMouseMove(e) }; const stopDrawing = (e) => { isOccluding() ? occlusionCanvas.stopDrawing(e) : lightCanvas.stopDrawing(e) }; const resetContainer = { reset: () => { }, clear: () => { lightCanvas.clear(); occlusionCanvas.clear(); } }; const {container, setHex} = intializeCanvas({ id: "final", canvas, onSetColor: ({r, g, b}) => { lightCanvas.currentColor = {r, g, b}; }, startDrawing, onMouseMove, stopDrawing, clear: resetContainer.clear, reset: () => { resetContainer.reset(); } }); window.addEventListener('keydown', onKeyDown); function updateTextures() { lightCanvas.updateTexture() occlusionCanvas.updateTexture() } function onKeyDown(event) { if (event.code === 'Enter') { if (rayCount === baseRayCount) { rayCount = 256; } else { rayCount = baseRayCount; } plane.material.uniforms.dir_count.value = rayCount; } render(); } setHex("#eb6b6f"); updateTextures(); let last = undefined; function draw(t, isShadow, resolve) { if (t >= 10.0) { resolve(); return; } const angle = (t * 0.05) * Math.PI * 2; const {x, y} = isShadow ? { x: 90 + 12 * t, y: 200 + 1 * t, } : { x: 100 + 100 * Math.sin(angle + 0.25) * Math.cos(angle * 0.15), y: 50 + 100 * Math.sin(angle * 0.7) }; last ??= {x, y}; (isShadow ? occlusionCanvas : lightCanvas).drawSmoothLine(last, {x, y}); last = {x, y}; getFrame(() => draw(t + (isShadow ? 0.3 : 0.18), isShadow, resolve)); } const executeFinalDraw = () => { lightCanvas.onStartDrawing(); occlusionCanvas.onStartDrawing(); last = undefined; new Promise((resolve) => { setHex("#f9a875"); getFrame(() => draw(0, false, resolve)); }).then(() => new Promise((resolve) => { last = undefined; setHex("#000000") getFrame(() => draw(0, true, resolve)); })) .then(() => { setHex("#eb6b6f"); lightCanvas.onStopDrawing(); occlusionCanvas.onStopDrawing(); }); } resetContainer.reset = () => { resetContainer.clear(); executeFinalDraw(); } const observer = new IntersectionObserver((entries) => { if (entries[0].isIntersecting === true) { executeFinalDraw(); observer.disconnect(container); } }); observer.observe(container); } // final(); ``` ```