Initial
This commit is contained in:
4
resources/app/client-esm/canvas/edges/_module.mjs
Normal file
4
resources/app/client-esm/canvas/edges/_module.mjs
Normal file
@@ -0,0 +1,4 @@
|
||||
export {default as CanvasEdges} from "./edges.mjs";
|
||||
export {default as CollisionResult} from "./collision.mjs";
|
||||
export {default as Edge} from "./edge.mjs";
|
||||
export {default as PolygonVertex} from "./vertex.mjs";
|
||||
94
resources/app/client-esm/canvas/edges/collision.mjs
Normal file
94
resources/app/client-esm/canvas/edges/collision.mjs
Normal file
@@ -0,0 +1,94 @@
|
||||
/**
|
||||
* A specialized object that contains the result of a collision in the context of the ClockwiseSweepPolygon.
|
||||
* This class is not designed or intended for use outside of that context.
|
||||
* @alias CollisionResult
|
||||
*/
|
||||
export default class CollisionResult {
|
||||
constructor({target, collisions=[], cwEdges, ccwEdges, isBehind, isLimited, wasLimited}={}) {
|
||||
this.target = target;
|
||||
this.collisions = collisions;
|
||||
this.cwEdges = cwEdges || new Set();
|
||||
this.ccwEdges = ccwEdges || new Set();
|
||||
this.isBehind = isBehind;
|
||||
this.isLimited = isLimited;
|
||||
this.wasLimited = wasLimited;
|
||||
}
|
||||
|
||||
/**
|
||||
* The vertex that was the target of this result
|
||||
* @type {PolygonVertex}
|
||||
*/
|
||||
target;
|
||||
|
||||
/**
|
||||
* The array of collision points which apply to this result
|
||||
* @type {PolygonVertex[]}
|
||||
*/
|
||||
collisions;
|
||||
|
||||
/**
|
||||
* The set of edges connected to the target vertex that continue clockwise
|
||||
* @type {EdgeSet}
|
||||
*/
|
||||
cwEdges;
|
||||
|
||||
/**
|
||||
* The set of edges connected to the target vertex that continue counter-clockwise
|
||||
* @type {EdgeSet}
|
||||
*/
|
||||
ccwEdges;
|
||||
|
||||
/**
|
||||
* Is the target vertex for this result behind some closer active edge?
|
||||
* @type {boolean}
|
||||
*/
|
||||
isBehind;
|
||||
|
||||
/**
|
||||
* Does the target vertex for this result impose a limited collision?
|
||||
* @type {boolean}
|
||||
*/
|
||||
isLimited;
|
||||
|
||||
/**
|
||||
* Has the set of collisions for this result encountered a limited edge?
|
||||
* @type {boolean}
|
||||
*/
|
||||
wasLimited;
|
||||
|
||||
/**
|
||||
* Is this result limited in the clockwise direction?
|
||||
* @type {boolean}
|
||||
*/
|
||||
limitedCW = false;
|
||||
|
||||
/**
|
||||
* Is this result limited in the counter-clockwise direction?
|
||||
* @type {boolean}
|
||||
*/
|
||||
limitedCCW = false;
|
||||
|
||||
/**
|
||||
* Is this result blocking in the clockwise direction?
|
||||
* @type {boolean}
|
||||
*/
|
||||
blockedCW = false;
|
||||
|
||||
/**
|
||||
* Is this result blocking in the counter-clockwise direction?
|
||||
* @type {boolean}
|
||||
*/
|
||||
blockedCCW = false;
|
||||
|
||||
/**
|
||||
* Previously blocking in the clockwise direction?
|
||||
* @type {boolean}
|
||||
*/
|
||||
blockedCWPrev = false;
|
||||
|
||||
/**
|
||||
* Previously blocking in the counter-clockwise direction?
|
||||
* @type {boolean}
|
||||
*/
|
||||
blockedCCWPrev = false;
|
||||
}
|
||||
285
resources/app/client-esm/canvas/edges/edge.mjs
Normal file
285
resources/app/client-esm/canvas/edges/edge.mjs
Normal file
@@ -0,0 +1,285 @@
|
||||
/**
|
||||
* @typedef {import("../../../common/types.mjs").Point} Point
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {"wall"|"darkness"|"innerBounds"|"outerBounds"} EdgeTypes
|
||||
*/
|
||||
|
||||
/**
|
||||
* A data structure used to represent potential edges used by the ClockwiseSweepPolygon.
|
||||
* Edges are not polygon-specific, meaning they can be reused across many polygon instances.
|
||||
*/
|
||||
export default class Edge {
|
||||
/**
|
||||
* Construct an Edge by providing the following information.
|
||||
* @param {Point} a The first endpoint of the edge
|
||||
* @param {Point} b The second endpoint of the edge
|
||||
* @param {object} [options] Additional options which describe the edge
|
||||
* @param {string} [options.id] A string used to uniquely identify this edge
|
||||
* @param {PlaceableObject} [options.object] A PlaceableObject that is responsible for this edge, if any
|
||||
* @param {EdgeTypes} [options.type] The type of edge
|
||||
* @param {WALL_SENSE_TYPES} [options.light] How this edge restricts light
|
||||
* @param {WALL_SENSE_TYPES} [options.move] How this edge restricts movement
|
||||
* @param {WALL_SENSE_TYPES} [options.sight] How this edge restricts sight
|
||||
* @param {WALL_SENSE_TYPES} [options.sound] How this edge restricts sound
|
||||
* @param {WALL_DIRECTIONS} [options.direction=0] A direction of effect for the edge
|
||||
* @param {WallThresholdData} [options.threshold] Configuration of threshold data for this edge
|
||||
*/
|
||||
constructor(a, b, {id, object, direction, type, light, move, sight, sound, threshold}={}) {
|
||||
this.a = new PIXI.Point(a.x, a.y);
|
||||
this.b = new PIXI.Point(b.x, b.y);
|
||||
this.id = id ?? object?.id ?? undefined;
|
||||
this.object = object;
|
||||
this.type = type || "wall";
|
||||
this.direction = direction ?? CONST.WALL_DIRECTIONS.BOTH;
|
||||
this.light = light ?? CONST.WALL_SENSE_TYPES.NONE;
|
||||
this.move = move ?? CONST.WALL_SENSE_TYPES.NONE;
|
||||
this.sight = sight ?? CONST.WALL_SENSE_TYPES.NONE;
|
||||
this.sound = sound ?? CONST.WALL_SENSE_TYPES.NONE;
|
||||
this.threshold = threshold;
|
||||
|
||||
// Record the edge orientation arranged from top-left to bottom-right
|
||||
const isSE = b.x === a.x ? b.y > a.y : b.x > a.x;
|
||||
if ( isSE ) {
|
||||
this.nw = a;
|
||||
this.se = b;
|
||||
}
|
||||
else {
|
||||
this.nw = b;
|
||||
this.se = a;
|
||||
}
|
||||
this.bounds = new PIXI.Rectangle(this.nw.x, this.nw.y, this.se.x - this.nw.x, this.se.y - this.nw.y);
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* The first endpoint of the edge.
|
||||
* @type {PIXI.Point}
|
||||
*/
|
||||
a;
|
||||
|
||||
/**
|
||||
* The second endpoint of the edge.
|
||||
* @type {PIXI.Point}
|
||||
*/
|
||||
b;
|
||||
|
||||
/**
|
||||
* The endpoint of the edge which is oriented towards the top-left.
|
||||
*/
|
||||
nw;
|
||||
|
||||
/**
|
||||
* The endpoint of the edge which is oriented towards the bottom-right.
|
||||
*/
|
||||
se;
|
||||
|
||||
/**
|
||||
* The rectangular bounds of the edge. Used by the quadtree.
|
||||
* @type {PIXI.Rectangle}
|
||||
*/
|
||||
bounds;
|
||||
|
||||
/**
|
||||
* The direction of effect for the edge.
|
||||
* @type {WALL_DIRECTIONS}
|
||||
*/
|
||||
direction;
|
||||
|
||||
/**
|
||||
* A string used to uniquely identify this edge.
|
||||
* @type {string}
|
||||
*/
|
||||
id;
|
||||
|
||||
/**
|
||||
* How this edge restricts light.
|
||||
* @type {WALL_SENSE_TYPES}
|
||||
*/
|
||||
light;
|
||||
|
||||
/**
|
||||
* How this edge restricts movement.
|
||||
* @type {WALL_SENSE_TYPES}
|
||||
*/
|
||||
move;
|
||||
|
||||
/**
|
||||
* How this edge restricts sight.
|
||||
* @type {WALL_SENSE_TYPES}
|
||||
*/
|
||||
sight;
|
||||
|
||||
/**
|
||||
* How this edge restricts sound.
|
||||
* @type {WALL_SENSE_TYPES}
|
||||
*/
|
||||
sound;
|
||||
|
||||
/**
|
||||
* Specialized threshold data for this edge.
|
||||
* @type {WallThresholdData}
|
||||
*/
|
||||
threshold;
|
||||
|
||||
/**
|
||||
* Record other edges which this one intersects with.
|
||||
* @type {{edge: Edge, intersection: LineIntersection}[]}
|
||||
*/
|
||||
intersections = [];
|
||||
|
||||
/**
|
||||
* A PolygonVertex instance.
|
||||
* Used as part of ClockwiseSweepPolygon computation.
|
||||
* @type {PolygonVertex}
|
||||
*/
|
||||
vertexA;
|
||||
|
||||
/**
|
||||
* A PolygonVertex instance.
|
||||
* Used as part of ClockwiseSweepPolygon computation.
|
||||
* @type {PolygonVertex}
|
||||
*/
|
||||
vertexB;
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Is this edge limited for a particular type?
|
||||
* @returns {boolean}
|
||||
*/
|
||||
isLimited(type) {
|
||||
return this[type] === CONST.WALL_SENSE_TYPES.LIMITED;
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Create a copy of the Edge which can be safely mutated.
|
||||
* @returns {Edge}
|
||||
*/
|
||||
clone() {
|
||||
const clone = new this.constructor(this.a, this.b, this);
|
||||
clone.intersections = [...this.intersections];
|
||||
clone.vertexA = this.vertexA;
|
||||
clone.vertexB = this.vertexB;
|
||||
return clone;
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Get an intersection point between this Edge and another.
|
||||
* @param {Edge} other
|
||||
* @returns {LineIntersection|void}
|
||||
*/
|
||||
getIntersection(other) {
|
||||
if ( this === other ) return;
|
||||
const {a: a0, b: b0} = this;
|
||||
const {a: a1, b: b1} = other;
|
||||
|
||||
// Ignore edges which share an endpoint
|
||||
if ( a0.equals(a1) || a0.equals(b1) || b0.equals(a1) || b0.equals(b1) ) return;
|
||||
|
||||
// Initial fast CCW test for intersection
|
||||
if ( !foundry.utils.lineSegmentIntersects(a0, b0, a1, b1) ) return;
|
||||
|
||||
// Slower computation of intersection point
|
||||
const i = foundry.utils.lineLineIntersection(a0, b0, a1, b1, {t1: true});
|
||||
if ( !i ) return; // Eliminates co-linear lines, theoretically should not be necessary but just in case
|
||||
return i;
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Test whether to apply a proximity threshold to this edge.
|
||||
* If the proximity threshold is met, this edge excluded from perception calculations.
|
||||
* @param {string} sourceType Sense type for the source
|
||||
* @param {Point} sourceOrigin The origin or position of the source on the canvas
|
||||
* @param {number} [externalRadius=0] The external radius of the source
|
||||
* @returns {boolean} True if the edge has a threshold greater than 0 for the source type,
|
||||
* and the source type is within that distance.
|
||||
*/
|
||||
applyThreshold(sourceType, sourceOrigin, externalRadius=0) {
|
||||
const d = this.threshold?.[sourceType];
|
||||
const t = this[sourceType];
|
||||
if ( !d || (t < CONST.WALL_SENSE_TYPES.PROXIMITY) ) return false; // Threshold behavior does not apply
|
||||
const proximity = t === CONST.WALL_SENSE_TYPES.PROXIMITY;
|
||||
const pt = foundry.utils.closestPointToSegment(sourceOrigin, this.a, this.b);
|
||||
const sourceDistance = Math.hypot(pt.x - sourceOrigin.x, pt.y - sourceOrigin.y);
|
||||
return proximity ? Math.max(sourceDistance - externalRadius, 0) < d : (sourceDistance + externalRadius) > d;
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Determine the orientation of this Edge with respect to a reference point.
|
||||
* @param {Point} point Some reference point, relative to which orientation is determined
|
||||
* @returns {number} An orientation in CONST.WALL_DIRECTIONS which indicates whether the Point is left,
|
||||
* right, or collinear (both) with the Edge
|
||||
*/
|
||||
orientPoint(point) {
|
||||
const orientation = foundry.utils.orient2dFast(this.a, this.b, point);
|
||||
if ( orientation === 0 ) return CONST.WALL_DIRECTIONS.BOTH;
|
||||
return orientation < 0 ? CONST.WALL_DIRECTIONS.LEFT : CONST.WALL_DIRECTIONS.RIGHT;
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
/* Intersection Management */
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Identify intersections between a provided iterable of edges.
|
||||
* @param {Iterable<Edge>} edges An iterable of edges
|
||||
*/
|
||||
static identifyEdgeIntersections(edges) {
|
||||
|
||||
// Sort edges by their north-west x value, breaking ties with the south-east x value
|
||||
const sorted = [];
|
||||
for ( const edge of edges ) {
|
||||
edge.intersections.length = 0; // Clear prior intersections
|
||||
sorted.push(edge);
|
||||
}
|
||||
sorted.sort((e1, e2) => (e1.nw.x - e2.nw.x) || (e1.se.x - e2.se.x));
|
||||
|
||||
// Iterate over all known edges, identifying intersections
|
||||
const ln = sorted.length;
|
||||
for ( let i=0; i<ln; i++ ) {
|
||||
const e1 = sorted[i];
|
||||
for ( let j=i+1; j<ln; j++ ) {
|
||||
const e2 = sorted[j];
|
||||
if ( e2.nw.x > e1.se.x ) break; // Segment e2 is entirely right of segment e1
|
||||
e1.recordIntersections(e2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Record the intersections between two edges.
|
||||
* @param {Edge} other Another edge to test and record
|
||||
*/
|
||||
recordIntersections(other) {
|
||||
if ( other === this ) return;
|
||||
const i = this.getIntersection(other);
|
||||
if ( !i ) return;
|
||||
this.intersections.push({edge: other, intersection: i});
|
||||
other.intersections.push({edge: this, intersection: {x: i.x, y: i.y, t0: i.t1, t1: i.t0}});
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Remove intersections of this edge with all other edges.
|
||||
*/
|
||||
removeIntersections() {
|
||||
for ( const {edge: other} of this.intersections ) {
|
||||
other.intersections.findSplice(e => e.edge === this);
|
||||
}
|
||||
this.intersections.length = 0;
|
||||
}
|
||||
}
|
||||
84
resources/app/client-esm/canvas/edges/edges.mjs
Normal file
84
resources/app/client-esm/canvas/edges/edges.mjs
Normal file
@@ -0,0 +1,84 @@
|
||||
import Edge from "./edge.mjs";
|
||||
|
||||
/**
|
||||
* A special class of Map which defines all the edges used to restrict perception in a Scene.
|
||||
* @extends {Map<string, Edge>}
|
||||
*/
|
||||
export default class CanvasEdges extends Map {
|
||||
|
||||
/**
|
||||
* Edge instances which represent the outer boundaries of the game canvas.
|
||||
* @type {Edge[]}
|
||||
*/
|
||||
#outerBounds = [];
|
||||
|
||||
/**
|
||||
* Edge instances which represent the inner boundaries of the scene rectangle.
|
||||
* @type {Edge[]}
|
||||
*/
|
||||
#innerBounds = [];
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Initialize all active edges for the Scene. This workflow occurs once only when the Canvas is first initialized.
|
||||
* Edges are created from the following sources:
|
||||
* 1. Wall documents
|
||||
* 2. Canvas boundaries (inner and outer bounds)
|
||||
* 3. Darkness sources
|
||||
* 4. Programmatically defined in the "initializeEdges" hook
|
||||
*/
|
||||
initialize() {
|
||||
this.clear();
|
||||
|
||||
// Wall Documents
|
||||
for ( /** @type {Wall} */ const wall of canvas.walls.placeables ) wall.initializeEdge();
|
||||
|
||||
// Canvas Boundaries
|
||||
this.#defineBoundaries();
|
||||
|
||||
// Darkness Sources
|
||||
for ( const source of canvas.effects.darknessSources ) {
|
||||
for ( const edge of source.edges ) this.set(edge.id, edge);
|
||||
}
|
||||
|
||||
// Programmatic Edges
|
||||
Hooks.callAll("initializeEdges");
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Incrementally refresh Edges by computing intersections between all registered edges.
|
||||
*/
|
||||
refresh() {
|
||||
Edge.identifyEdgeIntersections(canvas.edges.values());
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Define Edge instances for outer and inner canvas bounds rectangles.
|
||||
*/
|
||||
#defineBoundaries() {
|
||||
const d = canvas.dimensions;
|
||||
const define = (type, r) => {
|
||||
const top = new Edge({x: r.x, y: r.y}, {x: r.right, y: r.y}, {id: `${type}Top`, type});
|
||||
const right = new Edge({x: r.right, y: r.y}, {x: r.right, y: r.bottom}, {id: `${type}Right`, type});
|
||||
const bottom = new Edge({x: r.right, y: r.bottom}, {x: r.x, y: r.bottom}, {id: `${type}Bottom`, type});
|
||||
const left = new Edge({x: r.x, y: r.bottom}, {x: r.x, y: r.y}, {id: `${type}Left`, type});
|
||||
return [top, right, bottom, left];
|
||||
};
|
||||
|
||||
// Outer canvas bounds
|
||||
this.#outerBounds = define("outerBounds", d.rect);
|
||||
for ( const b of this.#outerBounds ) this.set(b.id, b);
|
||||
|
||||
// Inner canvas bounds (if there is padding)
|
||||
if ( d.rect.x === d.sceneRect.x ) this.#innerBounds = this.#outerBounds;
|
||||
else {
|
||||
this.#innerBounds = define("innerBounds", d.sceneRect);
|
||||
for ( const b of this.#innerBounds ) this.set(b.id, b);
|
||||
}
|
||||
}
|
||||
}
|
||||
187
resources/app/client-esm/canvas/edges/vertex.mjs
Normal file
187
resources/app/client-esm/canvas/edges/vertex.mjs
Normal file
@@ -0,0 +1,187 @@
|
||||
/**
|
||||
* A specialized point data structure used to represent vertices in the context of the ClockwiseSweepPolygon.
|
||||
* This class is not designed or intended for use outside of that context.
|
||||
* @alias PolygonVertex
|
||||
*/
|
||||
export default class PolygonVertex {
|
||||
constructor(x, y, {distance, index}={}) {
|
||||
this.x = Math.round(x);
|
||||
this.y = Math.round(y);
|
||||
this.key = PolygonVertex.getKey(this.x, this.y);
|
||||
this._distance = distance;
|
||||
this._d2 = undefined;
|
||||
this._index = index;
|
||||
}
|
||||
|
||||
/**
|
||||
* The effective maximum texture size that Foundry VTT "ever" has to worry about.
|
||||
* @type {number}
|
||||
*/
|
||||
static #MAX_TEXTURE_SIZE = Math.pow(2, 16);
|
||||
|
||||
/**
|
||||
* Determine the sort key to use for this vertex, arranging points from north-west to south-east.
|
||||
* @param {number} x The x-coordinate
|
||||
* @param {number} y The y-coordinate
|
||||
* @returns {number} The key used to identify the vertex
|
||||
*/
|
||||
static getKey(x, y) {
|
||||
return (this.#MAX_TEXTURE_SIZE * x) + y;
|
||||
}
|
||||
|
||||
/**
|
||||
* The set of edges which connect to this vertex.
|
||||
* This set is initially empty and populated later after vertices are de-duplicated.
|
||||
* @type {EdgeSet}
|
||||
*/
|
||||
edges = new Set();
|
||||
|
||||
/**
|
||||
* The subset of edges which continue clockwise from this vertex.
|
||||
* @type {EdgeSet}
|
||||
*/
|
||||
cwEdges = new Set();
|
||||
|
||||
/**
|
||||
* The subset of edges which continue counter-clockwise from this vertex.
|
||||
* @type {EdgeSet}
|
||||
*/
|
||||
ccwEdges = new Set();
|
||||
|
||||
/**
|
||||
* The set of vertices collinear to this vertex
|
||||
* @type {Set<PolygonVertex>}
|
||||
*/
|
||||
collinearVertices = new Set();
|
||||
|
||||
/**
|
||||
* Is this vertex an endpoint of one or more edges?
|
||||
* @type {boolean}
|
||||
*/
|
||||
isEndpoint;
|
||||
|
||||
/**
|
||||
* Does this vertex have a single counterclockwise limiting edge?
|
||||
* @type {boolean}
|
||||
*/
|
||||
isLimitingCCW;
|
||||
|
||||
/**
|
||||
* Does this vertex have a single clockwise limiting edge?
|
||||
* @type {boolean}
|
||||
*/
|
||||
isLimitingCW;
|
||||
|
||||
/**
|
||||
* Does this vertex have non-limited edges or 2+ limited edges counterclockwise?
|
||||
* @type {boolean}
|
||||
*/
|
||||
isBlockingCCW;
|
||||
|
||||
/**
|
||||
* Does this vertex have non-limited edges or 2+ limited edges clockwise?
|
||||
* @type {boolean}
|
||||
*/
|
||||
isBlockingCW;
|
||||
|
||||
/**
|
||||
* Does this vertex result from an internal collision?
|
||||
* @type {boolean}
|
||||
*/
|
||||
isInternal = false;
|
||||
|
||||
/**
|
||||
* The maximum restriction imposed by this vertex.
|
||||
* @type {number}
|
||||
*/
|
||||
restriction = 0;
|
||||
|
||||
/**
|
||||
* Record whether this PolygonVertex has been visited in the sweep
|
||||
* @type {boolean}
|
||||
* @internal
|
||||
*/
|
||||
_visited = false;
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Is this vertex limited in type?
|
||||
* @returns {boolean}
|
||||
*/
|
||||
get isLimited() {
|
||||
return this.restriction === CONST.WALL_SENSE_TYPES.LIMITED;
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Associate an edge with this vertex.
|
||||
* @param {Edge} edge The edge being attached
|
||||
* @param {number} orientation The orientation of the edge with respect to the origin
|
||||
* @param {string} type The restriction type of polygon being created
|
||||
*/
|
||||
attachEdge(edge, orientation, type) {
|
||||
this.edges.add(edge);
|
||||
this.restriction = Math.max(this.restriction ?? 0, edge[type]);
|
||||
if ( orientation <= 0 ) this.cwEdges.add(edge);
|
||||
if ( orientation >= 0 ) this.ccwEdges.add(edge);
|
||||
this.#updateFlags(type);
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Update flags for whether this vertex is limiting or blocking in certain direction.
|
||||
* @param {string} type
|
||||
*/
|
||||
#updateFlags(type) {
|
||||
const classify = edges => {
|
||||
const s = edges.size;
|
||||
if ( s === 0 ) return {isLimiting: false, isBlocking: false};
|
||||
if ( s > 1 ) return {isLimiting: false, isBlocking: true};
|
||||
else {
|
||||
const isLimiting = edges.first().isLimited(type);
|
||||
return {isLimiting, isBlocking: !isLimiting};
|
||||
}
|
||||
};
|
||||
|
||||
// Flag endpoint
|
||||
this.isEndpoint = this.edges.some(edge => {
|
||||
return (edge.vertexA || edge.a).equals(this) || (edge.vertexB || edge.b).equals(this);
|
||||
});
|
||||
|
||||
// Flag CCW edges
|
||||
const ccwFlags = classify(this.ccwEdges);
|
||||
this.isLimitingCCW = ccwFlags.isLimiting;
|
||||
this.isBlockingCCW = ccwFlags.isBlocking;
|
||||
|
||||
// Flag CW edges
|
||||
const cwFlags = classify(this.cwEdges);
|
||||
this.isLimitingCW = cwFlags.isLimiting;
|
||||
this.isBlockingCW = cwFlags.isBlocking;
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Is this vertex the same point as some other vertex?
|
||||
* @param {PolygonVertex} other Some other vertex
|
||||
* @returns {boolean} Are they the same point?
|
||||
*/
|
||||
equals(other) {
|
||||
return this.key === other.key;
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Construct a PolygonVertex instance from some other Point structure.
|
||||
* @param {Point} point The point
|
||||
* @param {object} [options] Additional options that apply to this vertex
|
||||
* @returns {PolygonVertex} The constructed vertex
|
||||
*/
|
||||
static fromPoint(point, options) {
|
||||
return new this(point.x, point.y, options);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user