ha-geometry-technical-challenge
Version:
Solution to the Computational Geometry Engineer Challenge
231 lines (219 loc) • 6.8 kB
JavaScript
/**
* Face Search Algorithm
* @desc An implementation of an algorithm to identify poylgon faces given a set of vertices and segments, referencing the DCEL data structure.
* @author Edbert Cheng
**/
/*
Sample Data. Data provided is a planar subdivision.
Edges array pair corresponds to index of vertices list
*/
const sample = {"vertices": [[0,0], [2,0], [2,2], [0,2]],"edges": [[0,1], [1,2], [0,2], [0,3], [2,3]]};
/*
Create a list of all half edges in the data.
Edges are unmarked at initialization.
*/
function allHalfEdges(data){
const totalEdges = [];
data.edges.forEach(elem => {
const elemTwin = [elem[1], elem[0]];
totalEdges.push({edge: elem, marked: false})
totalEdges.push({edge: elemTwin, marked: false})
})
return totalEdges;
}
/*
Finds incident angle of a half edge from data
*/
function angleHalfEdge(edge, data){
let start = edge[0];
let end = edge[1];
let p1 = data.vertices[start];
let p2 = data.vertices[end];
let x = p2[0]-p1[0];
let y = p2[1]-p1[1];
let angle = Math.atan2(y,x)*180/Math.PI;
//if angle is negative
if(angle < 0){
angle = angle + 360
}
return angle;
}
/*
Finds the curve orientation of a face via cross product
*/
function orientPolygon(face, data){
//find vertex with lowest y-coordinate
//get all vertices of the face
/*
const vertsFace = [...new Set(face.flat())];
console.log(vertsFace);
const pointsFace = vertsFace.map(elem => {
return data.vertices[elem]
})
console.log(pointsFace);
const minVert = pointsFace.findIndex
*/
//find edge pair
//[[1,0],[0,2],[2,1]]
//[1,0,2]
//get the first two edges of face
//technically, should be the lowest vertex to test concavity
let edge1 = face[0]
let edge2 = face[1]
//get three points
let p1 = data.vertices[edge1[0]] //x,y
let p2 = data.vertices[edge1[1]] //x,y
let p3 = data.vertices[edge2[1]] //x,y
//cross product
let area = (p2[0] - p1[0]) * (p3[1] - p1[1]) - (p2[1] - p1[1]) * (p3[0] - p1[0])
if (area < 0){
return -1; //clockwise -- remove these
}
if (area > 0){
return 1; //counter-clockwise -- keep
}
else {
return 0 //collinear -- keep
}
}
/*
Create a Map of all vertices in the given data.
Each vertex key contains a object of all half edges which originate from the vertex, sorted in increasing angle
*/
function allVerts(data){ //unsorted
//create a list of total vertices
const tEdges = allHalfEdges(data);
//list of all vertices and attributes
const totalVerts = [];
//loop through each vertex to find all half edge spokes
data.vertices.forEach((vert, index) => {
const vertex = vert;
let edgeList = [];
//loop through each half edge spoke to identify angle
tEdges.forEach(elem => {
if(elem.edge[0] === index){
//find incident angle of each edge
const angle = angleHalfEdge(elem.edge, data)
//create defining half edge and incident angle
edgeList.push({
edge: elem.edge,
angle: angle,
})
}
})
//sort edgeList based on increasing angle from x-axis
edgeList.sort((a,b) =>
(a.angle > b.angle) ? 1 : ((b.angle > a.angle) ? -1: 0)
);
//create an object consisting of vertex and edges
const vertObj = {
vertex: vertex,
edges: edgeList
}
totalVerts.push(vertObj);
})
//create a JS Map of vertices
const vertMap = new Map();
totalVerts.forEach(elem => {
const key = JSON.stringify(elem.vertex)
vertMap.set(key, elem.edges)
})
return vertMap;
//sort list of edges based on degrees from x-axis
}
/*
Finds a list of all faces bounded by edges and vertices in the provided dataset
*/
function allFaces(data){
//list of all faces from data
let totalFaces = [];
//get all half edges from data
let allEdges = allHalfEdges(data);
//get the vertex Map
const vertMap = allVerts(data);
//loop through data to find face
allEdges.forEach(elem => {
const face = []
//construct single face object
//face object should have at least 3 edges
if (elem.marked){
return
}
while (!elem.marked){ //repeat this action until you've found a polygon
elem.marked = true;
//add first edge to face
face.push(elem.edge);
//loop through vertMap to find the next edge //WRITE A SEPARATE FUNCTION FOR THIS
//a single instance
//get the next vertex
const nextVert = JSON.stringify(data.vertices[elem.edge[1]]);
//return all spokes
const spokes = vertMap.get(nextVert);
//get the next spoke
const twinEdge = JSON.stringify([elem.edge[1], elem.edge[0]])
const twinEdgeIndex = spokes.findIndex(elem =>
JSON.stringify(elem.edge) == twinEdge)
//do a ternary operator
//nextEdge is index + 1
//but if spoke is the last item, then nextEdge is index 0
//circular
const nextEdge = spokes[twinEdgeIndex + 1] == undefined ? spokes[0].edge : spokes[twinEdgeIndex + 1].edge;
//find index
const nextEdgeIndex = allEdges.findIndex(elem => JSON.stringify(elem.edge) == JSON.stringify(nextEdge));
//find the next dart
//set as next element
elem = allEdges[nextEdgeIndex];
}
totalFaces.push(face)
})
return totalFaces;
}
/*
Culls all faces list to only include interior face
Identifies the clockwise-oriented face and deletes
*/
function interFaces(data){
let total = allFaces(data);
total.forEach(elem => {
const result = orientPolygon(elem, data)
if (result >= 0){
total.splice(elem, 1)
}
})
return total;
}
/*
Create Text Output to the UI
Generate a text JSON of polygons
Convert face edge list to vertices for canvas drawing
*/
function textOutput(data){
const output = interFaces(data);
const outJSON = output.map((elem, index)=>{
let vertsFace = [...new Set(elem.flat())];
//vertsFace.push(vertsFace[0]) //close the loop, go back to the original
let vertices = vertsFace.map(elem => data.vertices[elem])
return({
name: 'Face '+(index+1),
vertices: vertices,
edges: elem
})
})
return outJSON;
}
/*
Creates Visual Output to UI
List of points will plug into canvas drawing function
*/
function vizOutput(data){
const output = interFaces(data);
const outCanvas = output.map(elem => {
let vertsFace = [...new Set(elem.flat())];
vertsFace.push(vertsFace[0])
let vertices = vertsFace.map(elem => data.vertices[elem])
return vertices;
})
return outCanvas;
}
//console.log(vizOutput(data));
//[{Polygon 0},{Polygon }]