import {ICurve} from './icurve'
import {Point, PointJSON, TriangleOrientation} from './point'
import {Parallelogram} from './parallelogram'
import {PlaneTransformation} from './planeTransformation'
import {Rectangle} from './rectangle'
import {GeomConstants} from './geomConstants'
import {PN} from './parallelogramNode'
export type LineSegmentJSON = {start: PointJSON; end: PointJSON}
export class LineSegment implements ICurve {
static fromJSON(lineData: LineSegmentJSON): LineSegment {
return LineSegment.mkPP(Point.fromJSON(lineData.start), Point.fromJSON(lineData.end))
}
toJSON(): LineSegmentJSON {
return {start: this.start.toJSON(), end: this.end.toJSON()}
}
start: Point //the line goes from start to end
end: Point // the line end point
readonly parStart = 0
readonly parEnd = 1
// Offsets the curve in the direction of dir
// eslint-disable-next-line @typescript-eslint/no-unused-vars
offsetCurve(offset: number, dir: Point): ICurve {
return null
}
constructor(x: number, y: number, x1: number, y1: number) {
this.start = new Point(x, y)
this.end = new Point(x1, y1)
}
// Returns the trim curve
trim(start: number, end: number): ICurve {
start = Math.max(this.parStart, start)
end = Math.min(this.parEnd, end)
if (start > end) throw 'wrong params in trimming'
const p1 = this.value(start)
const p2 = this.value(end)
if (Point.close(p1, p2, GeomConstants.distanceEpsilon)) {
return null
}
return LineSegment.mkPP(p1, p2)
}
value(t: number): Point {
return this.start.add(this.end.sub(this.start).mul(t))
}
// Not Implemented: Returns the trimmed curve, wrapping around the end if start is greater than end.
// eslint-disable-next-line @typescript-eslint/no-unused-vars
trimWithWrap(start: number, end: number): ICurve {
return null
} // not implemented
// A tree of ParallelogramNodes covering the curve.
// This tree is used in curve intersections routines.
//
pNodeOverICurve(): PN {
const side = this.end.sub(this.start).mul(0.5)
return {
parallelogram: Parallelogram.parallelogramByCornerSideSide(this.start, side, side),
seg: this,
leafBoxesOffset: 0,
node: {
low: 0,
high: 1,
chord: this,
},
}
}
normal(): Point {
let t = this.start.sub(this.end)
t = t.div(t.length)
return new Point(-t.y, t.x)
}
// construct a line segment
static mkPP(start: Point, end: Point): LineSegment {
return new LineSegment(start.x, start.y, end.x, end.y)
}
// constructs a line segment
static mkLinePXY(p: Point, x: number, y: number) {
return new LineSegment(p.x, p.y, x, y)
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
derivative(t: number) {
return this.end.sub(this.start)
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
secondDerivative(t: number) {
return new Point(0, 0)
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
thirdDerivative(t: number) {
return new Point(0, 0)
}
reverse() {
return LineSegment.mkPP(this.end, this.start)
}
/*
static internal IntersectionInfo Cross(LineSeg coeff, LineSeg side1){
IntersectionInfo xx=CrossTwoLines(coeff.start, coeff.End-coeff.start,side1.start, side1.End-side1.start);
if (xx == null )
{
//parallel segs
Point adir=coeff.d1(0);
Point bdir=side1.d1(0);
if (adir.length > bdir.length)
{
if (adir.length > Curve.DistEps)
{
adir = adir.normalize();
if(Math.Abs((coeff-side1)*adir1){
if (Point.closeDistEps(coeff.End, xx.x))
{
xx.x = coeff.End;
xx.Par0 = 1;
}
else
return null;
}
else if(xx.Par0<0){
if(Point.closeDistEps(coeff.start,xx.x)){
xx.x=coeff.start;
xx.Par0=1;
}
else
return null;
}
if (xx.Par1 > 1)
{
if (Point.closeDistEps(side1.End, xx.x))
{
xx.x = coeff.End;
xx.Par1 = 1;
}
else
return null;
}
else if (xx.Par1 < 0)
{
if (Point.closeDistEps(side1.start, xx.x))
{
xx.x = coeff.start;
xx.Par1 = 1;
}
else
return null;
}
return xx;
}
* */
// mutable! changes this
// Returns the curved moved by delta
translate(delta: Point) {
this.start = this.start.add(delta)
this.end = this.end.add(delta)
}
// Scale (multiply) from origin by x and y
scaleFromOrigin(xScale: number, yScale: number) {
return LineSegment.mkPP(this.start.scale(xScale, yScale), this.end.scale(xScale, yScale))
}
// gets the parameter at a specific length from the start along the curve
getParameterAtLength(length: number): number {
const len = this.end.sub(this.start).length
if (len < GeomConstants.tolerance) return 0
const t = length / len
return t > 1 ? 1 : t < 0 ? 0 : t
}
// Return the transformed curve
transform(transformation: PlaneTransformation): ICurve {
return LineSegment.mkPP(transformation.multiplyPoint(this.start), transformation.multiplyPoint(this.end))
}
// returns a parameter t such that the distance between curve[t] and targetPoint is minimal
// and t belongs to the closed segment [low,high]
closestParameterWithinBounds(targetPoint: Point, low: number, high: number) {
let t = this.closestParameter(targetPoint)
if (t < low) t = low
if (t > high) t = high
return t
}
// return length of the curve segment [start,end]
lengthPartial(start: number, end: number) {
return this.value(end).sub(this.value(start)).length
}
// Get the length of the curve
get length() {
return this.start.sub(this.end).length
}
// The bounding box of the line
get boundingBox() {
return Rectangle.mkPP(this.start, this.end)
}
// clones the curve.
clone() {
return LineSegment.mkPP(this.start.clone(), this.end.clone())
}
static closestParameterOnLineSegment(point: Point, segmentStart: Point, segmentEnd: Point): number {
const bc = segmentEnd.sub(segmentStart)
const ba = point.sub(segmentStart)
const c1 = bc.dot(ba)
if (c1 <= 0.0 + GeomConstants.tolerance) return 0
const c2 = bc.dot(bc)
if (c2 <= c1 + GeomConstants.tolerance) return 1
return c1 / c2
}
// returns a parameter t such that the distance between curve[t] and a is minimal
closestParameter(targetPoint: Point) {
return LineSegment.closestParameterOnLineSegment(targetPoint, this.start, this.end)
}
// left derivative at t
leftDerivative(t: number) {
return this.derivative(t)
}
// right derivative at t
rightDerivative(t: number) {
return this.derivative(t)
}
// returns true if segments are not parallel and are intesecting
static IntersectPPPP(a: Point, b: Point, c: Point, d: Point): Point | undefined {
const r = Point.lineLineIntersection(a, b, c, d)
if (r == null) return
if (pointIsOnSegment(r, a, b) && pointIsOnSegment(r, c, d)) {
return r
} else {
return undefined
}
}
//
// eslint-disable-next-line @typescript-eslint/no-unused-vars
curvature(t: number) {
return 0
}
//
// eslint-disable-next-line @typescript-eslint/no-unused-vars
curvatureDerivative(t: number) {
return 0
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
curvatureSecondDerivative(_: number) {
return 0
}
// [a,b] and [c,d] are the segments. u and v are the corresponding closest point params
// see http://www.geometrictools.com/Documentation/DistanceLine3Line3.pdf
static minDistBetweenLineSegments(a: Point, b: Point, c: Point, d: Point): {dist: number; parab: number; parcd: number} {
const u = b.sub(a)
const v = d.sub(c)
const w = a.sub(c)
const D = Point.crossProduct(u, v)
const uu = u.dot(u) // always >= 0
const uv = u.dot(v)
const vv = v.dot(v) // always >= 0
const uw = u.dot(w)
const vw = v.dot(w)
let sN: number, tN: number
const absD = Math.abs(D)
let sD = absD,
tD = absD
// compute the line parameters of the two closest points
if (absD < GeomConstants.tolerance) {
// the lines are almost parallel
sN = 0.0 // force using point a on segment [a..b]
sD = 1.0 // to prevent possible division by 0.0 later
tN = vw
tD = vv
} else {
// get the closest points on the infinite lines
sN = Point.crossProduct(v, w)
tN = Point.crossProduct(u, w)
if (D < 0) {
sN = -sN
tN = -tN
}
if (sN < 0.0) {
// parab < 0 => the s=0 edge is visible
sN = 0.0
tN = vw
tD = vv
} else if (sN > sD) {
// parab > 1 => the s=1 edge is visible
sN = sD = 1
tN = vw + uv
tD = vv
}
}
if (tN < 0.0) {
// tc < 0 => the t=0 edge is visible
tN = 0.0
// recompute parab for this edge
if (-uw < 0.0) sN = 0.0
else if (-uw > uu) sN = sD
else {
sN = -uw
sD = uu
}
} else if (tN > tD) {
// tc > 1 => the t=1 edge is visible
tN = tD = 1
// recompute parab for this edge
if (-uw + uv < 0.0) sN = 0
else if (-uw + uv > uu) sN = sD
else {
sN = -uw + uv
sD = uu
}
}
const parab_ = Math.abs(sN) < GeomConstants.tolerance ? 0.0 : sN / sD
const parcd_ = Math.abs(tN) < GeomConstants.tolerance ? 0.0 : tN / tD
// finally do the division to get parameters
return {
parab: parab_,
parcd: parcd_,
// get the difference of the two closest points
// const dP = w + (parab * u) - (parcd * v),
dist: w.add(u.mul(parab_).sub(v.mul(parcd_))).length, // return the closest distance
}
}
}
/** a - is the point to test
* [c,b] - is the segment
* The function actually checks that a is inside of the bounding box of [c,b].
* ! Use it only when a,b,c are collinear !
*/
export function pointIsOnSegment(a: Point, b: Point, c: Point): boolean {
return (
a.x >= Math.min(b.x, c.x) - GeomConstants.distanceEpsilon &&
a.y >= Math.min(b.y, c.y) - GeomConstants.distanceEpsilon &&
a.x <= Math.max(b.x, c.x) + GeomConstants.distanceEpsilon &&
a.y <= Math.max(b.y, c.y) + GeomConstants.distanceEpsilon
)
}
/** returns true if segments intersect */
export function segmentsIntersect(a: Point, b: Point, c: Point, d: Point): boolean {
const abc = Point.getTriangleOrientation(a, b, c)
const abd = Point.getTriangleOrientation(a, b, d)
const cda = Point.getTriangleOrientation(c, d, a)
const cdb = Point.getTriangleOrientation(c, d, b)
// if abc != abd then ab separates c and d
// if cda != cdb then cd separates b and a
if (abc != abd && cda != cdb) return true
// If the orientations are collinear and the points lie on the segments,
// the segments intersect
if (abc == TriangleOrientation.Collinear && pointIsOnSegment(c, a, b)) return true
if (abd == TriangleOrientation.Collinear && pointIsOnSegment(d, a, b)) return true
if (cda == TriangleOrientation.Collinear && pointIsOnSegment(a, c, d)) return true
if (cdb == TriangleOrientation.Collinear && pointIsOnSegment(b, c, d)) return true
// Otherwise, the segments do not intersect
return false
}