Trapezoid drawing working, triangles still broken...
This commit is contained in:
parent
08945dd15b
commit
45099df295
138
src/pslg.ts
138
src/pslg.ts
@ -28,7 +28,12 @@ export class Point {
|
||||
constructor(
|
||||
public readonly coordinate: Coordinate,
|
||||
public readonly name?: string,
|
||||
) {}
|
||||
) {
|
||||
this.coordinate = {
|
||||
x: parseFloat(coordinate.x.toFixed(12)),
|
||||
y: parseFloat(coordinate.y.toFixed(12)),
|
||||
}
|
||||
}
|
||||
|
||||
get 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 {
|
||||
const x = (a.x + b.x) / 2
|
||||
const y = (a.y + b.y) / 2
|
||||
@ -68,6 +81,17 @@ export class Point {
|
||||
}
|
||||
|
||||
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(
|
||||
public readonly from: Point,
|
||||
public readonly to: Point,
|
||||
@ -112,6 +136,19 @@ export class Segment {
|
||||
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) {
|
||||
return (
|
||||
(
|
||||
@ -148,64 +185,69 @@ 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) {
|
||||
return !(
|
||||
return (
|
||||
this.from.is(x)
|
||||
|| this.to.is(x)
|
||||
)
|
||||
}
|
||||
|
||||
getIntersectionWith(x: Segment): Point | undefined {
|
||||
const slope = this.slope
|
||||
const xSlope = x.slope
|
||||
getIntersectionWithin(x: Segment): Point | undefined {
|
||||
const intersection = this.getIntersectionWith(x)
|
||||
|
||||
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 ( intersection ) {
|
||||
if ( !this.hasPoint(intersection) && !x.hasPoint(intersection) ) {
|
||||
return intersection
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
const slope = this.slope
|
||||
const xSlope = x.slope
|
||||
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]
|
||||
|
||||
// FIXME account for overlapping parallel lines
|
||||
if ( slope === xSlope ) return
|
||||
const x12 = x1 - x2
|
||||
const x34 = x3 - x4
|
||||
const y12 = y1 - y2
|
||||
const y34 = y3 - y4
|
||||
|
||||
const b1 = this.yIntercept
|
||||
const b2 = x.yIntercept
|
||||
const m1 = this.slope
|
||||
const m2 = x.slope
|
||||
const c = x12 * y34 - y12 * x34
|
||||
|
||||
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)
|
||||
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 {
|
||||
@ -268,6 +310,10 @@ export class Segment {
|
||||
get ymax() {
|
||||
return Math.max(this.from.y, this.to.y)
|
||||
}
|
||||
|
||||
toQuickDisplay() {
|
||||
return [this.from.coordinate, this.to.coordinate]
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
this.segments = this.segments.filter(segment => !segment.is(x))
|
||||
}
|
||||
|
@ -55,72 +55,72 @@ export function triangulate(originalGraph: Graph): Graph {
|
||||
// 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 ) {
|
||||
const originalPoints = [...graph.points]
|
||||
for ( const point of originalPoints ) {
|
||||
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)
|
||||
// First, check if there is a horizontal segment ending at this point
|
||||
// extending toward the left
|
||||
const hasLeftHorizon = graph.getSegmentsEndingAt(point)
|
||||
.some(segment => segment.isHorizontal() && segment.getOtherPoint(point).isLeftOf(point))
|
||||
|
||||
// Get segments that intersect with this
|
||||
const leftIntersectingSegments = (graph.segments
|
||||
.map(segment => {
|
||||
if ( boundary.isBoundarySegment(segment) ) {
|
||||
// Exclude boundary segments
|
||||
return undefined
|
||||
}
|
||||
let leftmostPoint: Point = point
|
||||
let rightmostPoint: Point = point
|
||||
let leftIntersectionRay: Segment
|
||||
let rightIntersectionRay: Segment
|
||||
|
||||
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
|
||||
})
|
||||
if ( !hasLeftHorizon ) {
|
||||
const leftRaySegment = new Segment(point, Point.from(boundary.getLeftBoundary().xmin, point.y))
|
||||
const [leftIntersectingSegment, leftIntersectingPoint] = getFirstIntersectingSegmentInDirection(
|
||||
leftRaySegment,
|
||||
boundary,
|
||||
graph,
|
||||
GraphDirection.LEFT
|
||||
)
|
||||
|
||||
// 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)
|
||||
leftmostPoint = leftIntersectingPoint
|
||||
leftIntersectionRay = leftIntersectingSegment
|
||||
}
|
||||
|
||||
// Create the segment extending out to the right boundary
|
||||
const rightPoint = Point.from(rightBound.from.x, point.y)
|
||||
let rightSegment = new Segment(point, rightPoint)
|
||||
// First, check if there is a horizontal segment ending at this point
|
||||
// extending toward the left
|
||||
const hasRightHorizon = graph.getSegmentsEndingAt(point)
|
||||
.some(segment => segment.isHorizontal() && segment.getOtherPoint(point).isRightOf(point))
|
||||
|
||||
// Get segments that intersect with this
|
||||
const rightIntersectingSegments = (graph.segments
|
||||
.map(segment => {
|
||||
if ( boundary.isBoundarySegment(segment) ) {
|
||||
// Exclude boundary segments
|
||||
return undefined
|
||||
}
|
||||
if ( !hasRightHorizon ) {
|
||||
const rightRaySegment = new Segment(point, Point.from(boundary.getRightBoundary().xmin, point.y))
|
||||
const [rightIntersectingSegment, rightIntersectingPoint] = getFirstIntersectingSegmentInDirection(
|
||||
rightRaySegment,
|
||||
boundary,
|
||||
graph,
|
||||
GraphDirection.RIGHT
|
||||
)
|
||||
|
||||
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)
|
||||
rightmostPoint = rightIntersectingPoint
|
||||
rightIntersectionRay = rightIntersectingSegment
|
||||
}
|
||||
|
||||
const graphLeftSegment = graph.findExistingSegmentOrAdd(leftSegment)
|
||||
const graphRightSegment = graph.findExistingSegmentOrAdd(rightSegment)
|
||||
trapezoidSegments.push(graphLeftSegment, graphRightSegment)
|
||||
if ( !leftmostPoint.is(rightmostPoint) ) {
|
||||
// Check if this point has a line segment extending from both sides.
|
||||
// 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
|
||||
})
|
||||
|
||||
if ( hasSegmentAbove && hasSegmentBelow ) {
|
||||
trapezoidSegments.push(graph.findExistingSegmentOrAdd(new Segment(leftmostPoint, point)))
|
||||
trapezoidSegments.push(graph.findExistingSegmentOrAdd(new Segment(rightmostPoint, point)))
|
||||
} else {
|
||||
trapezoidSegments.push(graph.findExistingSegmentOrAdd(new Segment(leftmostPoint, rightmostPoint)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
)
|
||||
|
||||
console.log('got leftIntersectSegment', leftBoundaryHorizontalSegment.toQuickDisplay(), leftIntersectSegment.toQuickDisplay(), leftIntersectPoint.coordinate)
|
||||
leftBoundaryHorizontalSegment = new Segment(verticalMidpoint, leftIntersectPoint)
|
||||
|
||||
// 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
|
||||
// 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)
|
||||
const leftSegmentIntersectPoint = leftIntersectSegment.getIntersectionWith(segment)
|
||||
if ( !leftSegmentIntersectPoint ) {
|
||||
console.log('!leftSegmentIntersectPoint', segment.toQuickDisplay(), leftIntersectSegment.toQuickDisplay())
|
||||
throw new Error('Unable to find trapezoid segment intersection')
|
||||
}
|
||||
|
||||
// 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
|
||||
// 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)
|
||||
leftSegment1 = graph.findExistingSegmentOrAdd(localLeftSegment1)
|
||||
leftSegment2 = graph.findExistingSegmentOrAdd(localLeftSegment2)
|
||||
|
||||
// We care about the upper-segment from the split, as that is the bound of our trapezoid
|
||||
trapezoidLeftBoundSegment = leftSegment1.ymin === leftSegmentIntersectPoint.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 rightSegmentIntersectPoint = rightIntersectSegment.getIntersectionWith(segment)
|
||||
if ( !rightSegmentIntersectPoint ) throw new Error('Unable to find trapezoid segment intersection')
|
||||
|
||||
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
|
||||
// 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 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 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 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
|
||||
//
|
||||
// // 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]))
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user