如何在 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
我在 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])
,但我认为这对您没有帮助。
我最终采用了自己的解决方案,它听起来比
这是我在项目中使用的带有注释的实际代码。
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