如何在 Paper.js 中像在 Illustrator 中使用 Pathfinder 一样划分多个形状

How to divide multiple shapes in Paper.js like in Illustrator with Pathfinder

我在 Paper.js 中有多个重叠的正方形,我想将所有重叠的形状分开。您可以在 Illustrator 中使用探路者分界线完全做到这一点。在我尝试遍历所有重叠的形状并将它们彼此分开之前,我认为可能必须是一些嵌套循环,我想知道是否有更好的方法。

Illustrator 中的示例

我想转动所有这些方块: https://i.imgur.com/PPRi9M9.png

像这样拼成碎片 https://i.imgur.com/xTFS8jP.png (将碎片彼此分开,以便您可以看到它们是如何分开的)

因为你想创建以前不存在的形状(根据你的例子,你想要创建内部矩形的操作),我认为你将不得不遍历所有重叠的形状,用 Path.getIntersections(path[, include]),并从现有路径重新创建新路径。

计算完所有交点后,您将必须遍历所有顶点,始终沿同一方向旋转,并创建新路径。

取一个(随机)顶点,找到连接的顶点"with the smallest angle"(它应该与currentVertex.getDirectedAngle(connectedVertex)一起工作);将当前顶点设置为已访问并继续,直到再次找到第一个顶点。创建一个形状,然后重复此算法,直到您访问了所有顶点。

您也可以使用 Path.intersect(path[, options]),但我认为这对您没有帮助。

我最终采用了自己的解决方案,它听起来比 更实用、更简单。虽然不确定哪个性能更好。总而言之,我使用嵌套循环和 Path.intersects(path) 映射哪些块相互重叠,然后执行另一个嵌套循环以使用 Path.divide(path) 将每个块与其重叠块分开,这将用你分割的任何路径切割原始路径。

这是我在项目中使用的带有注释的实际代码。

    setupGrid() {
        // Setup block row and column positions
        for (let i = 0;i < this.total;i++) {
            let x
            let y

            if (!odd(i)) {
                x = firstColumnStartX + (this.size/2)
                y = firstColumnStartY + ((i/2) * (this.size + this.gap)) + (this.size/2)
            } else {
                x = secondColumnStartX + (this.size/2)
                y = secondColumnStartY + (Math.floor(i/2) * (this.size + this.gap)) + (this.size/2)
            }

            this.blocks.push(new paper.Path.Rectangle({
                position: [x, y],
                size: this.size,
                strokeColor: '#ff000050'
            }))
        }

        // Setup array to check what blocks are intersecting
        const intersects = []

        // Setup empty array with a nested array mapped to other blocks [5 x [5 x undefined]]
        for (let i = 0;i < this.total;i++) {
            intersects[i] = new Array(this.total).fill(undefined)
        }

        // Intersect checking
        for (let i = 0;i < this.total;i++) {
            const block = this.blocks[i]

            for (let _i = 0;_i < this.total;_i++) {
                const otherBlock = this.blocks[_i]

                if (block !== otherBlock && intersects[i][_i] === undefined) {
                    intersects[_i][i] = intersects[i][_i] = block.intersects(otherBlock)
                }
            }
        }

        // First loop through all blocks
        for (let i = 0;i < this.total;i++) {
            let block = this.blocks[i]

            // Then loop through other blocks only if they were intersected with the original block
            for (let _i = 0;_i < this.total;_i++) {
                const otherBlock = this.blocks[_i]

                if (intersects[i][_i]) {
                    /* divide returns {
                        pieces: array of separated pieces that would be inside the original block's boundaries
                        leftoverBlock: what's leftover of the other block if the original block was subtracted from it
                    } */
                    const divide = this.divide(block, otherBlock)
                    block.remove()
                    otherBlock.remove()

                    // Override current block with the array of pieces
                    block = this.blocks[i] = divide.pieces

                    // Override other block with leftover
                    this.blocks[_i] = divide.leftoverBlock

                    // Don't let other block divide with original block since we already did it here
                    intersects[_i][i] = undefined
                }
            }
        }

        // Set random color for each piece to check if successful
        for (let i = 0;i < this.blocks.length;i++) {
            let block = this.blocks[i]

            if (block instanceof Array) {
                for (let _i = 0;_i < block.length;_i++) {
                    block[_i].fillColor = new paper.Color(Math.random(), Math.random(), Math.random(), 0.1)
                }
            } else {
                block.fillColor = new paper.Color(Math.random(), Math.random(), Math.random(), 0.1)
            }
        }
    }

    // Divide blockA with blockB and expand
    divideBlocks(blockA, blockB, pieces = []) {
        const divideA = blockA.divide(blockB)

        if (divideA instanceof paper.CompoundPath) {
            for (let i = divideA.children.length;i--;) {
                const child = divideA.children[i]
                child.insertAbove(divideA)
                pieces.push(child)
            }
            divideA.remove()
        } else {
            pieces.push(divideA)
        }

        return pieces
    }

    // Divide group (array of paths) with divider
    divideGroup(children, divider, pieces = [], parent) {
        for (let i = children.length;i--;) {
            const child = children[i]

            if (parent) {
                child.insertAbove(parent)
            }

            if (child.intersects(divider)) {
                this.divideBlocks(child, divider, pieces)
            } else {
                pieces.push(child)
            }
        }
    }

    // Subtract group (array of paths) from block
    subtractGroupFromBlock(block, group) {
        let oldBlock
        let newBlock = block

        for (let i = group.length;i--;) {
            const child = group[i]

            if (child.intersects(block)) {
                newBlock = newBlock.subtract(child)

                if (oldBlock) {
                    oldBlock.remove()
                }

                oldBlock = newBlock
            }
        }

        return newBlock
    }

    // Check what kind of divide method to use
    divide(blockA, blockB) {
        const pieces = []
        let leftoverBlock

        if (blockA instanceof paper.Path) {
            this.divideBlocks(blockA, blockB, pieces)
            leftoverBlock = blockB.subtract(blockA)
        } else if (blockA instanceof Array) {
            this.divideGroup(blockA, blockB, pieces)
            leftoverBlock = this.subtractGroupFromBlock(blockB, blockA)
        }

        return {
            pieces,
            leftoverBlock
        }
    }

我的积木设置了随机颜色以区分每个形状:

之前的重叠块: https://i.imgur.com/j9ZSUC5.png

重叠块分成几块: https://i.imgur.com/mc83IH6.png