import { addBoundingSquareTo, getDirectionSorter, Graph, GraphBoundary, GraphDirection, Point, Segment, SegmentWithIntersection, Triangle } from "./pslg"; export function getFirstIntersectingSegmentInDirection(raySegment: Segment, boundary: GraphBoundary, graph: Graph, direction: GraphDirection): [Segment, Point] { const intersectingSegment = boundary.getBoundary(direction) const intersectingPoint = raySegment.getIntersectionWith(intersectingSegment) if ( !intersectingPoint ) { console.log( 'getFirstIntersectingSegmentInDirection', [raySegment.from.coordinate, raySegment.to.coordinate], [intersectingSegment.from.coordinate, intersectingSegment.to.coordinate], intersectingPoint ) throw new RangeError('Ray segment does not extend to boundary in the given direction!') } // Now, collect all non-boundary segments that intersect with the ray segment const intersections = (graph.segments .map(segment => { if ( boundary.isBoundarySegment(segment) ) return undefined return { segment, intersect: segment.getIntersectionWithin(raySegment) } }) .filter(x => x && x.intersect) as SegmentWithIntersection[]) .sort(getDirectionSorter(direction)) const intersection = intersections[0] if ( intersection ) { return [intersection.segment, intersection.intersect] } return [intersectingSegment, intersectingPoint] } export function triangulate(originalGraph: Graph): Graph { const graph = originalGraph.clone() const boundary = addBoundingSquareTo(graph) const leftBound = boundary.getLeftBoundary() const rightBound = boundary.getRightBoundary() const trapezoidSegments: Segment[] = [] // For each vertex in the original graph, create a horizontal line that // extends in both directions until it intersects with either (1) the boundary // or (2) a segment in the graph. for ( const point of graph.points ) { if ( boundary.isBoundaryPoint(point) ) continue // skip boundary points // Create the segment extending out to the left boundary const leftPoint = Point.from(leftBound.from.x, point.y) let leftSegment = new Segment(point, leftPoint) // Get segments that intersect with this const leftIntersectingSegments = (graph.segments .map(segment => { if ( boundary.isBoundarySegment(segment) ) { // Exclude boundary segments return undefined } return { segment, intersect: segment.getIntersectionWithin(leftSegment), } }) .filter(group => group && group.intersect) as Array<{segment: Segment, intersect: Point}>) .sort((a, b) => { return b.intersect.x - a.intersect.x // Sort by right-most x-value }) // Check if there was a nearer intersecting segment const firstLeftIntersectingSegment = leftIntersectingSegments?.[0]?.segment if ( firstLeftIntersectingSegment ) { // Modify the leftSegment to end at the intersection point const leftIntersect = graph.findExistingPointOrAdd(leftIntersectingSegments[0].intersect) leftSegment = new Segment(point, leftIntersect) } // Create the segment extending out to the right boundary const rightPoint = Point.from(rightBound.from.x, point.y) let rightSegment = new Segment(point, rightPoint) // Get segments that intersect with this const rightIntersectingSegments = (graph.segments .map(segment => { if ( boundary.isBoundarySegment(segment) ) { // Exclude boundary segments return undefined } return { segment, intersect: segment.getIntersectionWithin(rightSegment), } }) .filter(group => group && group.intersect) as Array<{segment: Segment, intersect: Point}>) .sort((a, b) => { return a.intersect.x - b.intersect.x // Sort by left-most x-value }) // Check if there was a nearer intersecting segment const firstRightIntersectingSegment = rightIntersectingSegments?.[0]?.segment if ( firstRightIntersectingSegment ) { // Modify the leftSegment to end at the intersection point const rightIntersect = graph.findExistingPointOrAdd(rightIntersectingSegments[0].intersect) rightSegment = new Segment(point, rightIntersect) } const graphLeftSegment = graph.findExistingSegmentOrAdd(leftSegment) const graphRightSegment = graph.findExistingSegmentOrAdd(rightSegment) trapezoidSegments.push(graphLeftSegment, graphRightSegment) } // Now, go through and identify trapezoids for all the horizontal segments we just added for ( const segment of trapezoidSegments ) { // First, find the trapezoid formed with the segment as the bottom // Create a vertical segment from the midpoint of the segment to the top boundary const horizontalMidpoint = segment.getMidpoint() let upperBoundaryPoint = Point.from(horizontalMidpoint.x, boundary.ymax) let upperBoundaryVerticalSegment = new Segment(horizontalMidpoint, upperBoundaryPoint) const [upperIntersectSegment, upperIntersectPoint] = getFirstIntersectingSegmentInDirection( upperBoundaryVerticalSegment, boundary, graph, GraphDirection.UP ) upperBoundaryVerticalSegment = new Segment(horizontalMidpoint, upperIntersectPoint) // Now we have the upper and lower boundaries of the trapezoid. // So, we need to figure out the left and right boundaries next. // Get the midpoint of the vertical segment const verticalMidpoint = upperBoundaryVerticalSegment.getMidpoint() let leftBoundaryPoint = Point.from(boundary.xmin, verticalMidpoint.y) let leftBoundaryHorizontalSegment = new Segment(verticalMidpoint, leftBoundaryPoint) const [leftIntersectSegment, leftIntersectPoint] = getFirstIntersectingSegmentInDirection( leftBoundaryHorizontalSegment, boundary, graph, GraphDirection.LEFT ) leftBoundaryHorizontalSegment = new Segment(verticalMidpoint, leftIntersectPoint) // Repeat to get the right boundary let rightBoundaryPoint = Point.from(boundary.xmax, verticalMidpoint.y) let rightBoundaryHorizontalSegment = new Segment(verticalMidpoint, rightBoundaryPoint) const [rightIntersectSegment, rightIntersectPoint] = getFirstIntersectingSegmentInDirection( rightBoundaryHorizontalSegment, boundary, graph, GraphDirection.RIGHT, ) rightBoundaryHorizontalSegment = new Segment(verticalMidpoint, rightIntersectPoint) // Now, check if we actually have a 4-bound trapezoid, or if we have a triangle const points = Point.distinct([ segment.from, segment.to, upperIntersectSegment.from, upperIntersectSegment.to, ]) if ( points.length === 3 ) { // We found a triangle! Less work. // Create the triangle and push it onto the graph const [p1, p2, p3] = points.map(x => graph.findExistingPointOrAdd(x)) const s12 = graph.findExistingSegmentOrAdd(new Segment(p1, p2)) const s23 = graph.findExistingSegmentOrAdd(new Segment(p2, p3)) const s31 = graph.findExistingSegmentOrAdd(new Segment(p3, p1)) graph.findExistingTriangleOrAdd(new Triangle([s12, s23, s31])) continue // FIXME - remove to handle below-segment case } if ( points.length !== 4 ) { throw new RangeError('Found shape with invalid number of distinct points!') } // Now, we have the 4 bounding segments of the trapezoid. // Let's find the segments that make up the trapezoid // We will do this by re-creating segments for the four sides of the trapezoid // Split the left-side on the intersection point let [leftSegment1, leftSegment2] = leftIntersectSegment.splitAt(leftIntersectPoint) // This is not right. Needs to be `segment`'s intersect point, not the midpoint intersect point graph.removeSegment(leftIntersectSegment) leftSegment1 = graph.findExistingSegmentOrAdd(leftSegment1) leftSegment2 = graph.findExistingSegmentOrAdd(leftSegment2) // We care about the upper-segment from the split, as that is the bound of our trapezoid const trapezoidLeftBoundSegment = leftSegment1.ymin === leftIntersectPoint.y ? leftSegment1 : leftSegment2 // Repeat this process for the right-side segment let [rightSegment1, rightSegment2] = rightIntersectSegment.splitAt(rightIntersectPoint) graph.removeSegment(rightIntersectSegment) rightSegment1 = graph.findExistingSegmentOrAdd(rightSegment1) rightSegment2 = graph.findExistingSegmentOrAdd(rightSegment2) const trapezoidRightBoundSegment = rightSegment1.ymin === rightBoundaryPoint.y ? leftSegment1 : leftSegment2 // Now we have all 4 bounding segments. We find the bisector that creates // triangles with the largest minimum angle. // First, try making triangles from bottom-left to top-right const lowerLeftPoint = graph.findExistingPointOrAdd(Point.from(segment.xmin, segment.ymin)) const upperRightPoint = graph.findExistingPointOrAdd(Point.from(upperIntersectSegment.xmax, upperIntersectSegment.ymax)) const bottomLeftBisectorSegment = new Segment(lowerLeftPoint, upperRightPoint) // const bottomLeftBisectorUpperTriangle = new Triangle([bottomLeftBisectorSegment, upperIntersectSegment, trapezoidLeftBoundSegment]) // const bottomLeftBisectorLowerTriangle = new Triangle([bottomLeftBisectorSegment, segment, trapezoidRightBoundSegment]) // const bottomLeftBisectorMinAngle = Math.min(bottomLeftBisectorUpperTriangle.getMinimumAngle(), bottomLeftBisectorLowerTriangle.getMinimumAngle()) const upperLeftPoint = graph.findExistingPointOrAdd(Point.from(upperIntersectSegment.xmin, upperIntersectSegment.ymax)) const lowerRightPoint = graph.findExistingPointOrAdd(Point.from(segment.xmax, segment.ymin)) // const topRightBisectorSegment = new Segment(upperLeftPoint, lowerRightPoint) // const upperRightBisectorUpperTriangle = new Triangle([topRightBisectorSegment, upperIntersectSegment, trapezoidRightBoundSegment]) // const upperRightBisectorLowerTriangle = new Triangle([topRightBisectorSegment, trapezoidLeftBoundSegment, segment]) // const upperRightBisectorMinAngle = Math.min(upperRightBisectorUpperTriangle.getMinimumAngle(), upperRightBisectorLowerTriangle.getMinimumAngle()) // const optimalBisectorUpperTriangle = upperRightBisectorMinAngle > bottomLeftBisectorMinAngle ? upperRightBisectorUpperTriangle : bottomLeftBisectorUpperTriangle // const optimalBisectorLowerTriangle = upperRightBisectorMinAngle > bottomLeftBisectorMinAngle ? upperRightBisectorLowerTriangle : bottomLeftBisectorLowerTriangle // Add the triangles to the graph // const upperTriangleSegments = optimalBisectorUpperTriangle.sides.map(side => graph.findExistingSegmentOrAdd(side)) // graph.findExistingTriangleOrAdd(new Triangle(upperTriangleSegments as [Segment, Segment, Segment])) // const lowerTriangleSegments = optimalBisectorLowerTriangle.sides.map(side => graph.findExistingSegmentOrAdd(side)) // graph.findExistingTriangleOrAdd(new Triangle(lowerTriangleSegments as [Segment, Segment, Segment])) } // FIXME handle the lower-trapezoid case return graph }