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

时间:2019-03-28 06:12:22

标签: paperjs

在Paper.js中,我有多个重叠的正方形,我想将所有重叠的形状分离为自己的形状。您可以在Illustrator中使用路径查找器划分来完全做到这一点。在我尝试遍历所有重叠的形状并将它们彼此分开(我认为可能是一些嵌套循环)之前,我想知道是否有更好的方法。

Illustrator中的示例

我想把所有这些正方形变成: https://i.imgur.com/PPRi9M9.png

分成这样的碎片 https://i.imgur.com/xTFS8jP.png (将各部分移开,以便您看到它们是如何分离的)

2 个答案:

答案 0 :(得分:2)

我最终选择了自己的解决方案,该解决方案比@arthur's answer听起来更实用,更简单。虽然不确定哪个会更有表现。总而言之,我使用嵌套循环和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

答案 1 :(得分:1)

由于您要创建以前不存在的形状(根据您的示例,您希望该操作创建内部矩形),我认为您将必须遍历所有重叠的形状,并使用{{3} },然后从现有路径重新创建新路径。

一旦计算了所有交点,就必须遍历所有顶点,并始终沿相同方向旋转,并创建新路径。

取一个(随机)顶点,找到“具有最小角度”的连接顶点(它应与currentVertex.getDirectedAngle(connectedVertex)一起使用);将当前顶点设置为已访问,然后继续直到找到第一个顶点。创建一个形状,然后重做该算法,直到访问了所有顶点为止。

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