I am trying to create a "fog of war" system in a Konva React canvas similar to what you might find on Roll20 or another virtual tabletop. Rectangles on the canvas should either hide (show a black area) or reveal (turn any black areas transparent). The easy solution here is global composite areas with an XOR operation, but that won't account for "hide" areas drawn on top of "reveal" areas. The most recent (or elements later on in the list of shapes if you prefer) should take precedence. For example, if there is a "reveal" area over the entire canvas and another "hide" shape is on top of it, that shape should still be black. The XOR operation would still have the entire area revealed. How can I create this sort of stacked global composite operation that retains the ordering of the hidden and revealed zones.

I mentioned that I already tried global composites, but the code below best represents the ideal approach.

const applyShapes = () => {
        // Initialize visibility to true
        const newPixelVisibility = Array(stageWidth * stageHeight).fill(true);
        // Apply hide shapes
        fowShapes.forEach((shape) => {
            if (shape.type === TOOL_ENUM.REVEAL) {
                for (let x = shape.x; x < shape.x + shape.width; x++) {
                    for (let y = shape.y; y < shape.y + shape.height; y++) {
                        if (x >= 0 && x < stageWidth && y >= 0 && y < stageHeight) {
                            const index = y * stageWidth + x;
                            newPixelVisibility[index] = shape.layer === FOW_ENUM.HIDE;
                        }
                    }
                }
            }
        });

        setPixelVisibility(newPixelVisibility);
    };
}

return (
            <>
                {pixelVisibility.map((visible, index) => (
                    <Rect
                        key={index}
                        x={index % stageWidth}
                        y={Math.floor(index / stageWidth)}
                        width={1}
                        height={1}
                        opacity={visible ? 1 : 0}
                        fill={'black'}
                    />
                ))}
            </>
        );

This code however is way too slow, in even a 100x100 canvas I'd be rendering 1000 rectangles ever time and my canvases might be much bigger. Are there any methods to achieve the desired outcome above in a computationally effective manner?

1

There are 1 answers

0
Ryan Malley On BEST ANSWER

Whelp I fixed it, gonna leave this up for posterity. The "xor" operation wouldn't work, but the 'destination-out' operation does the exact trick. Frankly after all of the pen and eraser tutorials for HTML canvases, I should have realized it earlier.