forked from Roll20/roll20-api-scripts
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Yell at the user if they don't have the paths selected for Pathsplitter.
- Loading branch information
Stephen Lindberg
committed
Feb 23, 2018
1 parent
3ace45f
commit 35b3e47
Showing
2 changed files
with
225 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,224 @@ | ||
/** | ||
* This script provides a way for players and GMs to split paths by their | ||
* intersections with another splitting path. | ||
* This could especially be useful for when corrections need to be made to | ||
* paths used for dynamic lighting. | ||
* | ||
* Simply draw a polygonal path intersecting the path you want to split up. | ||
* Select the main path and then the splitting path. | ||
* Then with the main and splitting paths selected, | ||
* enter the command '!pathSplit'. | ||
* The original path will be divided into new paths separated at the points | ||
* where the splitting path intersected the original path. | ||
* | ||
* This script also works with paths that have been scaled and rotated.' | ||
* | ||
* Requires: | ||
* VectorMath | ||
* MatrixMath | ||
* PathMath | ||
*/ | ||
|
||
(() => { | ||
'use strict'; | ||
|
||
const PATHSPLIT_CMD = '!pathSplit'; | ||
const PATHSPLIT_COLOR_CMD = '!pathSplitColor'; | ||
|
||
/** | ||
* A 3-tuple representing a point of intersection between two line segments. | ||
* The first element is a Vector representing the point of intersection in | ||
* 2D homogenous coordinates. | ||
* The second element is the parametric coefficient for the intersection | ||
* along the first segment. | ||
* The third element is the parametric coefficient for the intersection | ||
* along the second segment. | ||
* @typedef {Array} Intersection | ||
*/ | ||
|
||
/** | ||
* A vector used to define a homogeneous point or a direction. | ||
* @typedef {number[]} Vector | ||
*/ | ||
|
||
/** | ||
* A line segment defined by two homogenous 2D points. | ||
* @typedef {Vector[]} Segment | ||
*/ | ||
|
||
|
||
// Initialize the script's state if it hasn't already been initialized. | ||
state.PathSplitter = state.PathSplitter || { | ||
splitPathColor: '#ff00ff' // pink | ||
}; | ||
|
||
|
||
function _getSplitSegmentPaths(mainSegments, splitSegments) { | ||
let resultSegPaths = []; | ||
let curPathSegs = []; | ||
|
||
_.each(mainSegments, seg1 => { | ||
|
||
// Find the points of intersection and their parametric coefficients. | ||
let intersections = []; | ||
_.each(splitSegments, seg2 => { | ||
let i = PathMath.segmentIntersection(seg1, seg2); | ||
if(i) | ||
intersections.push(i); | ||
}); | ||
|
||
if(intersections.length > 0) { | ||
// Sort the intersections in the order that they appear along seg1. | ||
intersections.sort((a, b) => { | ||
return a[1] - b[1]; | ||
}); | ||
|
||
let lastPt = seg1[0]; | ||
_.each(intersections, i => { | ||
// Complete the current segment path. | ||
curPathSegs.push([lastPt, i[0]]); | ||
resultSegPaths.push(curPathSegs); | ||
|
||
// Start a new segment path. | ||
curPathSegs = []; | ||
lastPt = i[0]; | ||
}); | ||
curPathSegs.push([lastPt, seg1[1]]); | ||
} | ||
else { | ||
curPathSegs.push(seg1); | ||
} | ||
}); | ||
resultSegPaths.push(curPathSegs); | ||
|
||
return resultSegPaths; | ||
}; | ||
|
||
/** | ||
* Splits mainPath at its intersections with splitPath. The original path | ||
* is removed, being replaced by the new split up paths. | ||
* @param {Path} mainPath | ||
* @param {Path} splitPath | ||
* @return {Path[]} | ||
*/ | ||
function splitPathAtIntersections(mainPath, splitPath) { | ||
let mainSegments = PathMath.toSegments(mainPath); | ||
let splitSegments = PathMath.toSegments(splitPath); | ||
let segmentPaths = _getSplitSegmentPaths(mainSegments, splitSegments); | ||
|
||
// Convert the list of segment paths into paths. | ||
let _pageid = mainPath.get('_pageid'); | ||
let controlledby = mainPath.get('controlledby'); | ||
let fill = mainPath.get('fill'); | ||
let layer = mainPath.get('layer'); | ||
let stroke = mainPath.get('stroke'); | ||
let stroke_width = mainPath.get('stroke_width'); | ||
|
||
let results = []; | ||
_.each(segmentPaths, segments => { | ||
let pathData = PathMath.segmentsToPath(segments); | ||
_.extend(pathData, { | ||
_pageid, | ||
controlledby, | ||
fill, | ||
layer, | ||
stroke, | ||
stroke_width | ||
}); | ||
let path = createObj('path', pathData); | ||
results.push(path); | ||
}); | ||
|
||
// Remove the original path and the splitPath. | ||
mainPath.remove(); | ||
splitPath.remove(); | ||
|
||
return results; | ||
} | ||
|
||
on('ready', () => { | ||
let macro = findObjs({ | ||
_type: 'macro', | ||
name: 'Pathsplitter' | ||
})[0]; | ||
|
||
if(!macro) { | ||
let players = findObjs({ | ||
_type: 'player' | ||
}); | ||
let gms = _.filter(players, player => { | ||
return playerIsGM(player.get('_id')); | ||
}); | ||
|
||
_.each(gms, gm => { | ||
createObj('macro', { | ||
_playerid: gm.get('_id'), | ||
name: 'Pathsplitter', | ||
action: PATHSPLIT_CMD | ||
}); | ||
}); | ||
} | ||
}); | ||
|
||
on('chat:message', msg => { | ||
if(msg.type === 'api' && msg.content === PATHSPLIT_COLOR_CMD) { | ||
try { | ||
let selected = msg.selected; | ||
let path = findObjs({ | ||
_type: 'path', | ||
_id: selected[0]._id | ||
})[0]; | ||
|
||
let stroke = path.get('stroke'); | ||
state.PathSplitter.splitPathColor = stroke; | ||
} | ||
catch(err) { | ||
log('!pathSplit ERROR: ' + err.message); | ||
log(err.stack); | ||
} | ||
} | ||
else if(msg.type === 'api' && msg.content === PATHSPLIT_CMD) { | ||
try { | ||
let selected = msg.selected; | ||
if(!selected || selected.length !== 2) { | ||
let msg = `Two paths must be selected: the one you want to split, and the splitting path (color: <span style="background: ${state.PathSplitter.splitPathColor}; width: 16px; height: 16px; padding: 0.2em; font-weight: bold;">${state.PathSplitter.splitPathColor}</span>).`; | ||
sendChat('Pathsplitter', msg); | ||
throw new Error('Two paths must be selected.'); | ||
} | ||
|
||
let path1 = findObjs({ | ||
_type: 'path', | ||
_id: selected[0]._id | ||
})[0]; | ||
let path2 = findObjs({ | ||
_type: 'path', | ||
_id: selected[1]._id | ||
})[0]; | ||
|
||
// Determine which path is the main path and which is the | ||
// splitting path. | ||
let mainPath, splitPath; | ||
if(path1.get('stroke') === state.PathSplitter.splitPathColor) { | ||
mainPath = path2; | ||
splitPath = path1; | ||
} | ||
else if(path2.get('stroke') === state.PathSplitter.splitPathColor) { | ||
mainPath = path1; | ||
splitPath = path2; | ||
} | ||
else { | ||
let msg = 'No splitting path selected. '; | ||
msg += `Current split color: <span style="background: ${state.PathSplitter.splitPathColor}; width: 16px; height: 16px; padding: 0.2em; font-weight: bold;">${state.PathSplitter.splitPathColor}</span>` | ||
sendChat('Pathsplitter', msg); | ||
|
||
throw new Error('No splitting path selected.'); | ||
} | ||
splitPathAtIntersections(mainPath, splitPath); | ||
} | ||
catch(err) { | ||
log('!pathSplit ERROR: ' + err.message); | ||
log(err.stack); | ||
} | ||
} | ||
}); | ||
})(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters