Trapezoid drawing working, triangles still broken...

This commit is contained in:
Garrett Mills 2021-09-27 15:40:25 -05:00
parent 08945dd15b
commit 45099df295
2 changed files with 190 additions and 118 deletions

View File

@ -28,7 +28,12 @@ export class Point {
constructor( constructor(
public readonly coordinate: Coordinate, public readonly coordinate: Coordinate,
public readonly name?: string, public readonly name?: string,
) {} ) {
this.coordinate = {
x: parseFloat(coordinate.x.toFixed(12)),
y: parseFloat(coordinate.y.toFixed(12)),
}
}
get x() { get x() {
return this.coordinate.x return this.coordinate.x
@ -49,6 +54,14 @@ export class Point {
) )
} }
isLeftOf(x: Point) {
return this.x < x.x
}
isRightOf(x: Point) {
return this.x > x.x
}
public static midpoint(a: Point, b: Point): Point { public static midpoint(a: Point, b: Point): Point {
const x = (a.x + b.x) / 2 const x = (a.x + b.x) / 2
const y = (a.y + b.y) / 2 const y = (a.y + b.y) / 2
@ -68,6 +81,17 @@ export class Point {
} }
export class Segment { export class Segment {
static fromCombinedHorizontals(a: Segment, b: Segment) {
const leftmostA = a.getLeftmostPoint()
const leftmostB = b.getLeftmostPoint()
const rightmostA = a.getRightmostPoint()
const rightmostB = b.getRightmostPoint()
const leftmost = leftmostA.x < leftmostB.x ? leftmostA : leftmostB
const rightmost = rightmostA.x > rightmostB.x ? rightmostA : rightmostB
return new Segment(leftmost, rightmost)
}
constructor( constructor(
public readonly from: Point, public readonly from: Point,
public readonly to: Point, public readonly to: Point,
@ -112,6 +136,19 @@ export class Segment {
return new Segment(this.from.clone(), this.to.clone()) return new Segment(this.from.clone(), this.to.clone())
} }
getOtherPoint(x: Point) {
if ( this.from.is(x) ) return this.to
return this.from
}
getLeftmostPoint() {
return this.from.x < this.to.x ? this.from : this.to
}
getRightmostPoint() {
return this.getOtherPoint(this.getLeftmostPoint())
}
is(x: Segment) { is(x: Segment) {
return ( return (
( (
@ -148,65 +185,70 @@ export class Segment {
) )
} }
yValueIsWithinRange(y: number, inclusive = true) {
if ( inclusive ) {
return y >= this.ymin && y <= this.ymax
}
return y > this.ymin && y < this.ymax
}
xValueIsWithinRange(x: number, inclusive = true) {
if ( inclusive ) {
return x >= this.xmin && x <= this.xmax
}
return x > this.xmin && x < this.xmax
}
hasPoint(x: Point) { hasPoint(x: Point) {
return !( return (
this.from.is(x) this.from.is(x)
|| this.to.is(x) || this.to.is(x)
) )
} }
getIntersectionWith(x: Segment): Point | undefined {
const slope = this.slope
const xSlope = x.slope
if ( slope === xSlope ) return
const b1 = this.yIntercept
const b2 = x.yIntercept
const m1 = this.slope
const m2 = x.slope
if ( Math.abs(m1) === 0 && Math.abs(m2) === Infinity ) {
if ( x.xmin <= this.xmax && x.xmin >= this.xmin ) {
if ( this.ymin <= x.ymax && this.ymin >= x.ymin ) {
return Point.from(x.xmin, this.ymin)
}
}
}
if ( Math.abs(m1) === Infinity && Math.abs(m2) === 0 ) {
if ( this.xmin <= x.xmax && this.xmin >= x.xmin ) {
if ( x.ymin <= this.ymax && x.ymin >= this.ymin ) {
return Point.from(this.xmin, x.ymin)
}
}
}
const intersectX = (b1 - b2) / (m2 - m1)
const intersectY = this.getYAtX(intersectX)
if ( intersectX <= Math.max(this.from.x, this.to.x) && intersectX >= Math.min(this.from.x, this.to.x) ) {
return Point.from(intersectX, intersectY)
}
}
getIntersectionWithin(x: Segment): Point | undefined { getIntersectionWithin(x: Segment): Point | undefined {
const slope = this.slope const intersection = this.getIntersectionWith(x)
const xSlope = x.slope
// FIXME account for overlapping parallel lines if ( intersection ) {
if ( slope === xSlope ) return if ( !this.hasPoint(intersection) && !x.hasPoint(intersection) ) {
return intersection
const b1 = this.yIntercept
const b2 = x.yIntercept
const m1 = this.slope
const m2 = x.slope
const intersectX = (b1 - b2) / (m2 - m1)
const intersectY = this.getYAtX(intersectX)
if ( intersectX < Math.max(this.from.x, this.to.x) && intersectX > Math.min(this.from.x, this.to.x) ) {
return Point.from(intersectX, intersectY)
} }
} }
}
getIntersectionWith(seg: Segment): Point | undefined {
const [x1, y1] = [this.from.x, this.from.y]
const [x2, y2] = [this.to.x, this.to.y]
const [x3, y3] = [seg.from.x, seg.from.y]
const [x4, y4] = [seg.to.x, seg.to.y]
const x12 = x1 - x2
const x34 = x3 - x4
const y12 = y1 - y2
const y34 = y3 - y4
const c = x12 * y34 - y12 * x34
if ( !Math.abs(c) ) {
return
}
const a = x1 * y2 - y1 * x2
const b = x3 * y4 - y3 * x4
const x = (a * x34 - b * x12) / c
const y = (a * y34 - b * y12) / c
const point = Point.from(x, y)
if (
this.xValueIsWithinRange(point.x)
&& seg.xValueIsWithinRange(point.x)
&& this.yValueIsWithinRange(point.y)
&& seg.yValueIsWithinRange(point.y)
) return point
}
getLength(): number { getLength(): number {
return Math.sqrt( return Math.sqrt(
@ -268,6 +310,10 @@ export class Segment {
get ymax() { get ymax() {
return Math.max(this.from.y, this.to.y) return Math.max(this.from.y, this.to.y)
} }
toQuickDisplay() {
return [this.from.coordinate, this.to.coordinate]
}
} }
export class Circle { export class Circle {
@ -706,6 +752,10 @@ export class Graph {
}) })
} }
getSegmentsEndingAt(point: Point): Segment[] {
return this.segments.filter(segment => segment.hasPoint(point))
}
removeSegment(x: Segment) { removeSegment(x: Segment) {
this.segments = this.segments.filter(segment => !segment.is(x)) this.segments = this.segments.filter(segment => !segment.is(x))
} }

View File

@ -55,72 +55,72 @@ export function triangulate(originalGraph: Graph): Graph {
// For each vertex in the original graph, create a horizontal line that // For each vertex in the original graph, create a horizontal line that
// extends in both directions until it intersects with either (1) the boundary // extends in both directions until it intersects with either (1) the boundary
// or (2) a segment in the graph. // or (2) a segment in the graph.
for ( const point of graph.points ) { const originalPoints = [...graph.points]
for ( const point of originalPoints ) {
if ( boundary.isBoundaryPoint(point) ) continue // skip boundary points if ( boundary.isBoundaryPoint(point) ) continue // skip boundary points
// Create the segment extending out to the left boundary // First, check if there is a horizontal segment ending at this point
const leftPoint = Point.from(leftBound.from.x, point.y) // extending toward the left
let leftSegment = new Segment(point, leftPoint) const hasLeftHorizon = graph.getSegmentsEndingAt(point)
.some(segment => segment.isHorizontal() && segment.getOtherPoint(point).isLeftOf(point))
// Get segments that intersect with this let leftmostPoint: Point = point
const leftIntersectingSegments = (graph.segments let rightmostPoint: Point = point
.map(segment => { let leftIntersectionRay: Segment
if ( boundary.isBoundarySegment(segment) ) { let rightIntersectionRay: Segment
// Exclude boundary segments
return undefined if ( !hasLeftHorizon ) {
const leftRaySegment = new Segment(point, Point.from(boundary.getLeftBoundary().xmin, point.y))
const [leftIntersectingSegment, leftIntersectingPoint] = getFirstIntersectingSegmentInDirection(
leftRaySegment,
boundary,
graph,
GraphDirection.LEFT
)
leftmostPoint = leftIntersectingPoint
leftIntersectionRay = leftIntersectingSegment
} }
return { // First, check if there is a horizontal segment ending at this point
segment, // extending toward the left
intersect: segment.getIntersectionWithin(leftSegment), const hasRightHorizon = graph.getSegmentsEndingAt(point)
.some(segment => segment.isHorizontal() && segment.getOtherPoint(point).isRightOf(point))
if ( !hasRightHorizon ) {
const rightRaySegment = new Segment(point, Point.from(boundary.getRightBoundary().xmin, point.y))
const [rightIntersectingSegment, rightIntersectingPoint] = getFirstIntersectingSegmentInDirection(
rightRaySegment,
boundary,
graph,
GraphDirection.RIGHT
)
rightmostPoint = rightIntersectingPoint
rightIntersectionRay = rightIntersectingSegment
} }
})
.filter(group => group && group.intersect) as Array<{segment: Segment, intersect: Point}>) if ( !leftmostPoint.is(rightmostPoint) ) {
.sort((a, b) => { // Check if this point has a line segment extending from both sides.
return b.intersect.x - a.intersect.x // Sort by right-most x-value // If so, then the line segment will bisect a captive area to create 2 trapezoids,
// so we need to make 2 line segments.
let hasSegmentBelow = false
let hasSegmentAbove = false
graph.getSegmentsEndingAt(point)
.forEach(segment => {
const otherPoint = segment.getOtherPoint(point)
if ( otherPoint.y > point.y ) hasSegmentAbove = true
if ( otherPoint.y < point.y ) hasSegmentBelow = true
}) })
// Check if there was a nearer intersecting segment if ( hasSegmentAbove && hasSegmentBelow ) {
const firstLeftIntersectingSegment = leftIntersectingSegments?.[0]?.segment trapezoidSegments.push(graph.findExistingSegmentOrAdd(new Segment(leftmostPoint, point)))
if ( firstLeftIntersectingSegment ) { trapezoidSegments.push(graph.findExistingSegmentOrAdd(new Segment(rightmostPoint, point)))
// Modify the leftSegment to end at the intersection point } else {
const leftIntersect = graph.findExistingPointOrAdd(leftIntersectingSegments[0].intersect) trapezoidSegments.push(graph.findExistingSegmentOrAdd(new Segment(leftmostPoint, rightmostPoint)))
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 // Now, go through and identify trapezoids for all the horizontal segments we just added
@ -154,6 +154,7 @@ export function triangulate(originalGraph: Graph): Graph {
GraphDirection.LEFT GraphDirection.LEFT
) )
console.log('got leftIntersectSegment', leftBoundaryHorizontalSegment.toQuickDisplay(), leftIntersectSegment.toQuickDisplay(), leftIntersectPoint.coordinate)
leftBoundaryHorizontalSegment = new Segment(verticalMidpoint, leftIntersectPoint) leftBoundaryHorizontalSegment = new Segment(verticalMidpoint, leftIntersectPoint)
// Repeat to get the right boundary // Repeat to get the right boundary
@ -196,21 +197,42 @@ export function triangulate(originalGraph: Graph): Graph {
// Let's find the segments that make up 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 // We will do this by re-creating segments for the four sides of the trapezoid
// Split the left-side on the intersection point // 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 const leftSegmentIntersectPoint = leftIntersectSegment.getIntersectionWith(segment)
if ( !leftSegmentIntersectPoint ) {
console.log('!leftSegmentIntersectPoint', segment.toQuickDisplay(), leftIntersectSegment.toQuickDisplay())
throw new Error('Unable to find trapezoid segment intersection')
}
// TODO Account for the case where we don't need to split the segment.
let trapezoidLeftBoundSegment = leftIntersectSegment
let leftSegment1: Segment | undefined
let leftSegment2: Segment | undefined
if ( !leftIntersectSegment.hasPoint(leftSegmentIntersectPoint) ) {
let [localLeftSegment1, localLeftSegment2] = leftIntersectSegment.splitAt(leftSegmentIntersectPoint)
graph.removeSegment(leftIntersectSegment) graph.removeSegment(leftIntersectSegment)
leftSegment1 = graph.findExistingSegmentOrAdd(leftSegment1) leftSegment1 = graph.findExistingSegmentOrAdd(localLeftSegment1)
leftSegment2 = graph.findExistingSegmentOrAdd(leftSegment2) leftSegment2 = graph.findExistingSegmentOrAdd(localLeftSegment2)
// We care about the upper-segment from the split, as that is the bound of our trapezoid // 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 trapezoidLeftBoundSegment = leftSegment1.ymin === leftSegmentIntersectPoint.y ? leftSegment1 : leftSegment2
}
// Repeat this process for the right-side segment // Repeat this process for the right-side segment
let [rightSegment1, rightSegment2] = rightIntersectSegment.splitAt(rightIntersectPoint) const rightSegmentIntersectPoint = rightIntersectSegment.getIntersectionWith(segment)
graph.removeSegment(rightIntersectSegment) if ( !rightSegmentIntersectPoint ) throw new Error('Unable to find trapezoid segment intersection')
rightSegment1 = graph.findExistingSegmentOrAdd(rightSegment1)
rightSegment2 = graph.findExistingSegmentOrAdd(rightSegment2)
const trapezoidRightBoundSegment = rightSegment1.ymin === rightBoundaryPoint.y ? leftSegment1 : leftSegment2 let trapezoidRightBoundSegment = rightIntersectSegment
let rightSegment1: Segment | undefined
let rightSegment2: Segment | undefined
if ( !rightIntersectSegment.hasPoint(rightSegmentIntersectPoint) ) {
let [localRightSegment1, localRightSegment2] = rightIntersectSegment.splitAt(rightSegmentIntersectPoint)
graph.removeSegment(rightIntersectSegment)
rightSegment1 = graph.findExistingSegmentOrAdd(localRightSegment1)
rightSegment2 = graph.findExistingSegmentOrAdd(localRightSegment2)
// We care about the upper-segment from the split, as that is the bound of our trapezoid
trapezoidRightBoundSegment = rightSegment1.ymin === rightSegmentIntersectPoint.y ? rightSegment1 : rightSegment2
}
// Now we have all 4 bounding segments. We find the bisector that creates // Now we have all 4 bounding segments. We find the bisector that creates
// triangles with the largest minimum angle. // triangles with the largest minimum angle.
@ -220,25 +242,25 @@ export function triangulate(originalGraph: Graph): Graph {
const upperRightPoint = graph.findExistingPointOrAdd(Point.from(upperIntersectSegment.xmax, upperIntersectSegment.ymax)) const upperRightPoint = graph.findExistingPointOrAdd(Point.from(upperIntersectSegment.xmax, upperIntersectSegment.ymax))
const bottomLeftBisectorSegment = new Segment(lowerLeftPoint, upperRightPoint) const bottomLeftBisectorSegment = new Segment(lowerLeftPoint, upperRightPoint)
// const bottomLeftBisectorUpperTriangle = new Triangle([bottomLeftBisectorSegment, upperIntersectSegment, trapezoidLeftBoundSegment]) const bottomLeftBisectorUpperTriangle = new Triangle([bottomLeftBisectorSegment, upperIntersectSegment, trapezoidLeftBoundSegment])
// const bottomLeftBisectorLowerTriangle = new Triangle([bottomLeftBisectorSegment, segment, trapezoidRightBoundSegment]) // const bottomLeftBisectorLowerTriangle = new Triangle([bottomLeftBisectorSegment, segment, trapezoidRightBoundSegment])
// const bottomLeftBisectorMinAngle = Math.min(bottomLeftBisectorUpperTriangle.getMinimumAngle(), bottomLeftBisectorLowerTriangle.getMinimumAngle()) // const bottomLeftBisectorMinAngle = Math.min(bottomLeftBisectorUpperTriangle.getMinimumAngle(), bottomLeftBisectorLowerTriangle.getMinimumAngle())
const upperLeftPoint = graph.findExistingPointOrAdd(Point.from(upperIntersectSegment.xmin, upperIntersectSegment.ymax)) // const upperLeftPoint = graph.findExistingPointOrAdd(Point.from(upperIntersectSegment.xmin, upperIntersectSegment.ymax))
const lowerRightPoint = graph.findExistingPointOrAdd(Point.from(segment.xmax, segment.ymin)) // const lowerRightPoint = graph.findExistingPointOrAdd(Point.from(segment.xmax, segment.ymin))
//
// const topRightBisectorSegment = new Segment(upperLeftPoint, lowerRightPoint) // const topRightBisectorSegment = new Segment(upperLeftPoint, lowerRightPoint)
// const upperRightBisectorUpperTriangle = new Triangle([topRightBisectorSegment, upperIntersectSegment, trapezoidRightBoundSegment]) // const upperRightBisectorUpperTriangle = new Triangle([topRightBisectorSegment, upperIntersectSegment, trapezoidRightBoundSegment])
// const upperRightBisectorLowerTriangle = new Triangle([topRightBisectorSegment, trapezoidLeftBoundSegment, segment]) // const upperRightBisectorLowerTriangle = new Triangle([topRightBisectorSegment, trapezoidLeftBoundSegment, segment])
// const upperRightBisectorMinAngle = Math.min(upperRightBisectorUpperTriangle.getMinimumAngle(), upperRightBisectorLowerTriangle.getMinimumAngle()) // const upperRightBisectorMinAngle = Math.min(upperRightBisectorUpperTriangle.getMinimumAngle(), upperRightBisectorLowerTriangle.getMinimumAngle())
//
// const optimalBisectorUpperTriangle = upperRightBisectorMinAngle > bottomLeftBisectorMinAngle ? upperRightBisectorUpperTriangle : bottomLeftBisectorUpperTriangle // const optimalBisectorUpperTriangle = upperRightBisectorMinAngle > bottomLeftBisectorMinAngle ? upperRightBisectorUpperTriangle : bottomLeftBisectorUpperTriangle
// const optimalBisectorLowerTriangle = upperRightBisectorMinAngle > bottomLeftBisectorMinAngle ? upperRightBisectorLowerTriangle : bottomLeftBisectorLowerTriangle // const optimalBisectorLowerTriangle = upperRightBisectorMinAngle > bottomLeftBisectorMinAngle ? upperRightBisectorLowerTriangle : bottomLeftBisectorLowerTriangle
//
// Add the triangles to the graph // // Add the triangles to the graph
// const upperTriangleSegments = optimalBisectorUpperTriangle.sides.map(side => graph.findExistingSegmentOrAdd(side)) // const upperTriangleSegments = optimalBisectorUpperTriangle.sides.map(side => graph.findExistingSegmentOrAdd(side))
// graph.findExistingTriangleOrAdd(new Triangle(upperTriangleSegments as [Segment, Segment, Segment])) // graph.findExistingTriangleOrAdd(new Triangle(upperTriangleSegments as [Segment, Segment, Segment]))
//
// const lowerTriangleSegments = optimalBisectorLowerTriangle.sides.map(side => graph.findExistingSegmentOrAdd(side)) // const lowerTriangleSegments = optimalBisectorLowerTriangle.sides.map(side => graph.findExistingSegmentOrAdd(side))
// graph.findExistingTriangleOrAdd(new Triangle(lowerTriangleSegments as [Segment, Segment, Segment])) // graph.findExistingTriangleOrAdd(new Triangle(lowerTriangleSegments as [Segment, Segment, Segment]))
} }