@leafer/math
Version:
369 lines (286 loc) • 12.4 kB
text/typescript
import { IPointData, IBoundsData, IMatrixData, IFourNumber, IBoundsDataFn, IObject, IMatrix, IOffsetBoundsData, IRadiusPointData, IMatrixWithScaleData, ISide, IAlign, ISizeData, IScrollPointData } from '@leafer/interface'
import { isArray, isString } from '@leafer/data'
import { Matrix } from './Matrix'
import { MatrixHelper as M } from './MatrixHelper'
import { TwoPointBoundsHelper as TB } from './TwoPointBoundsHelper'
import { PointHelper as P } from './PointHelper'
import { MathHelper, getBoundsData } from './MathHelper'
import { AlignHelper } from './AlignHelper'
const { tempPointBounds, setPoint, addPoint, toBounds } = TB
const { toOuterPoint } = M
const { float, fourNumber } = MathHelper
const { floor, ceil } = Math
let right: number, bottom: number, boundsRight: number, boundsBottom: number
const point = {} as IPointData
const toPoint = {} as IPointData
const tempBounds = {} as IBoundsData
export const BoundsHelper = {
tempBounds,
set(t: IBoundsData, x = 0, y = 0, width = 0, height = 0): void {
t.x = x
t.y = y
t.width = width
t.height = height
},
copy(t: IBoundsData, bounds: IBoundsData): void {
t.x = bounds.x
t.y = bounds.y
t.width = bounds.width
t.height = bounds.height
},
copyAndSpread(t: IBoundsData, bounds: IBoundsData, spread: IFourNumber, isShrink?: boolean, side?: ISide): void {
const { x, y, width, height } = bounds
if (isArray(spread)) {
const four = fourNumber(spread)
isShrink
? B.set(t, x + four[3], y + four[0], width - four[1] - four[3], height - four[2] - four[0])
: B.set(t, x - four[3], y - four[0], width + four[1] + four[3], height + four[2] + four[0])
} else {
if (isShrink) spread = -spread
B.set(t, x - spread, y - spread, width + spread * 2, height + spread * 2)
}
if (side) { // 只扩展/收缩一边
if (side === 'width') t.y = y, t.height = height
else t.x = x, t.width = width
}
},
minX(t: IBoundsData): number { return t.width > 0 ? t.x : t.x + t.width },
minY(t: IBoundsData): number { return t.height > 0 ? t.y : t.y + t.height },
maxX(t: IBoundsData): number { return t.width > 0 ? t.x + t.width : t.x },
maxY(t: IBoundsData): number { return t.height > 0 ? t.y + t.height : t.y },
move(t: IBoundsData, x: number, y: number): void {
t.x += x
t.y += y
},
scroll(t: IBoundsData, data: IScrollPointData): void {
t.x += data.scrollX
t.y += data.scrollY
},
getByMove(t: IBoundsData, x: number, y: number): IBoundsData {
t = { ...t }
B.move(t, x, y)
return t
},
toOffsetOutBounds(t: IBoundsData, to?: IOffsetBoundsData, offsetBounds?: IBoundsData): void {
if (!to) to = t as IOffsetBoundsData
else copy(to, t)
if (!offsetBounds) offsetBounds = t
to.offsetX = B.maxX(offsetBounds)
to.offsetY = B.maxY(offsetBounds)
B.move(to, -to.offsetX, -to.offsetY)
},
scale(t: IBoundsData, scaleX: number, scaleY = scaleX, onlySize?: boolean): void {
onlySize || P.scale(t, scaleX, scaleY)
t.width *= scaleX
t.height *= scaleY
},
scaleOf(t: IBoundsData, origin: IPointData, scaleX: number, scaleY = scaleX): void {
P.scaleOf(t, origin, scaleX, scaleY)
t.width *= scaleX
t.height *= scaleY
},
tempToOuterOf(t: IBoundsData, matrix: IMatrixData): IBoundsData {
B.copy(tempBounds, t)
B.toOuterOf(tempBounds, matrix)
return tempBounds
},
getOuterOf(t: IBoundsData, matrix: IMatrixData): IBoundsData {
t = { ...t }
B.toOuterOf(t, matrix)
return t
},
toOuterOf(t: IBoundsData, matrix: IMatrixData, to?: IBoundsData): void {
to || (to = t)
if (matrix.b === 0 && matrix.c === 0) {
const { a, d, e, f } = matrix
if (a > 0) {
to.width = t.width * a
to.x = e + t.x * a
} else {
to.width = t.width * -a
to.x = e + t.x * a - to.width
}
if (d > 0) {
to.height = t.height * d
to.y = f + t.y * d
} else {
to.height = t.height * -d
to.y = f + t.y * d - to.height
}
} else {
point.x = t.x
point.y = t.y
toOuterPoint(matrix, point, toPoint)
setPoint(tempPointBounds, toPoint.x, toPoint.y)
point.x = t.x + t.width
toOuterPoint(matrix, point, toPoint)
addPoint(tempPointBounds, toPoint.x, toPoint.y)
point.y = t.y + t.height
toOuterPoint(matrix, point, toPoint)
addPoint(tempPointBounds, toPoint.x, toPoint.y)
point.x = t.x
toOuterPoint(matrix, point, toPoint)
addPoint(tempPointBounds, toPoint.x, toPoint.y)
toBounds(tempPointBounds, to)
}
},
toInnerOf(t: IBoundsData, matrix: IMatrixData, to?: IBoundsData): void {
to || (to = t)
B.move(to, -matrix.e, -matrix.f)
B.scale(to, 1 / matrix.a, 1 / matrix.d)
},
getFitMatrix(t: IBoundsData, put: IBoundsData, baseScale = 1): IMatrix {
const scale = Math.min(baseScale, B.getFitScale(t, put))
return new Matrix(scale, 0, 0, scale, -put.x * scale, -put.y * scale)
},
getFitScale(t: ISizeData, put: ISizeData, isCoverMode?: boolean): number {
const sw = t.width / put.width, sh = t.height / put.height
return isCoverMode ? Math.max(sw, sh) : Math.min(sw, sh)
},
put(t: ISizeData, put: ISizeData, align: IAlign = 'center', putScale: number | 'fit' | 'cover' = 1, changeSize = true, to?: IPointData): void {
to || (to = put as unknown as IPointData)
if (isString(putScale)) putScale = B.getFitScale(t, put, putScale === 'cover')
tempBounds.width = changeSize ? put.width *= putScale : put.width * putScale
tempBounds.height = changeSize ? put.height *= putScale : put.height * putScale
AlignHelper.toPoint(align, tempBounds, t as IBoundsData, to, true, true)
},
getSpread(t: IBoundsData, spread: IFourNumber, side?: ISide): IBoundsData {
const n = {} as IBoundsData
B.copyAndSpread(n, t, spread, false, side)
return n
},
spread(t: IBoundsData, spread: IFourNumber, side?: ISide): void {
B.copyAndSpread(t, t, spread, false, side)
},
shrink(t: IBoundsData, shrink: IFourNumber, side?: ISide): void {
B.copyAndSpread(t, t, shrink, true, side)
},
ceil(t: IBoundsData): void {
const { x, y } = t
t.x = floor(t.x)
t.y = floor(t.y)
t.width = x > t.x ? ceil(t.width + x - t.x) : ceil(t.width)
t.height = y > t.y ? ceil(t.height + y - t.y) : ceil(t.height)
},
unsign(t: IBoundsData): void {
if (t.width < 0) {
t.x += t.width
t.width = -t.width
}
if (t.height < 0) {
t.y += t.height
t.height = -t.height
}
},
float(t: IBoundsData, maxLength?: number): void {
t.x = float(t.x, maxLength)
t.y = float(t.y, maxLength)
t.width = float(t.width, maxLength)
t.height = float(t.height, maxLength)
},
add(t: IBoundsData, bounds: IBoundsData, isPoint?: boolean): void {
right = t.x + t.width
bottom = t.y + t.height
boundsRight = bounds.x
boundsBottom = bounds.y
if (!isPoint) {
boundsRight += bounds.width
boundsBottom += bounds.height
}
right = right > boundsRight ? right : boundsRight
bottom = bottom > boundsBottom ? bottom : boundsBottom
t.x = t.x < bounds.x ? t.x : bounds.x
t.y = t.y < bounds.y ? t.y : bounds.y
t.width = right - t.x
t.height = bottom - t.y
},
addList(t: IBoundsData, list: IBoundsData[]): void {
B.setListWithFn(t, list, undefined, true)
},
setList(t: IBoundsData, list: IBoundsData[], addMode = false): void {
B.setListWithFn(t, list, undefined, addMode)
},
addListWithFn(t: IBoundsData, list: IObject[], boundsDataFn: IBoundsDataFn): void {
B.setListWithFn(t, list, boundsDataFn, true)
},
setListWithFn(t: IBoundsData, list: IObject[], boundsDataFn: IBoundsDataFn, addMode = false): void {
let bounds: IBoundsData, first = true
for (let i = 0, len = list.length; i < len; i++) {
bounds = boundsDataFn ? boundsDataFn(list[i], i) : list[i] as IBoundsData
if (bounds && (bounds.width || bounds.height)) {
if (first) {
first = false
if (!addMode) copy(t, bounds)
} else {
add(t, bounds)
}
}
}
if (first) B.reset(t)
},
setPoints(t: IBoundsData, points: IPointData[]): void {
points.forEach((point, index) => index === 0 ? setPoint(tempPointBounds, point.x, point.y) : addPoint(tempPointBounds, point.x, point.y))
toBounds(tempPointBounds, t)
},
setPoint(t: IBoundsData, point: IPointData): void {
B.set(t, point.x, point.y)
},
addPoint(t: IBoundsData, point: IPointData): void {
add(t, point as IBoundsData, true)
},
getPoints(t: IBoundsData): IPointData[] {
const { x, y, width, height } = t
return [
{ x, y }, // topLeft
{ x: x + width, y }, // topRight
{ x: x + width, y: y + height }, // bottomRight
{ x, y: y + height } // bottomLeft
]
},
hitRadiusPoint(t: IBoundsData, point: IRadiusPointData, pointMatrix?: IMatrixWithScaleData): boolean {
if (pointMatrix) point = P.tempToInnerRadiusPointOf(point, pointMatrix)
return (point.x >= t.x - point.radiusX && point.x <= t.x + t.width + point.radiusX) && (point.y >= t.y - point.radiusY && point.y <= t.y + t.height + point.radiusY)
},
hitPoint(t: IBoundsData, point: IPointData, pointMatrix?: IMatrixData): boolean {
if (pointMatrix) point = P.tempToInnerOf(point, pointMatrix)
return (point.x >= t.x && point.x <= t.x + t.width) && (point.y >= t.y && point.y <= t.y + t.height)
},
hit(t: IBoundsData, other: IBoundsData, otherMatrix?: IMatrixData): boolean {
if (otherMatrix) other = B.tempToOuterOf(other, otherMatrix)
return !((t.y + t.height < other.y) || (other.y + other.height < t.y) || (t.x + t.width < other.x) || (other.x + other.width < t.x))
},
includes(t: IBoundsData, other: IBoundsData, otherMatrix?: IMatrixData): boolean {
if (otherMatrix) other = B.tempToOuterOf(other, otherMatrix)
return (t.x <= other.x) && (t.y <= other.y) && (t.x + t.width >= other.x + other.width) && (t.y + t.height >= other.y + other.height)
},
getIntersectData(t: IBoundsData, other: IBoundsData, otherMatrix?: IMatrixData): IBoundsData {
if (otherMatrix) other = B.tempToOuterOf(other, otherMatrix)
if (!B.hit(t, other)) return getBoundsData()
let { x, y, width, height } = other
right = x + width
bottom = y + height
boundsRight = t.x + t.width
boundsBottom = t.y + t.height
x = x > t.x ? x : t.x
y = y > t.y ? y : t.y
right = right < boundsRight ? right : boundsRight
bottom = bottom < boundsBottom ? bottom : boundsBottom
width = right - x
height = bottom - y
return { x, y, width, height }
},
intersect(t: IBoundsData, other: IBoundsData, otherMatrix?: IMatrixData): void {
B.copy(t, B.getIntersectData(t, other, otherMatrix))
},
isSame(t: IBoundsData, bounds: IBoundsData): boolean {
return t.x === bounds.x && t.y === bounds.y && t.width === bounds.width && t.height === bounds.height
},
isEmpty(t: IBoundsData): boolean {
return t.x === 0 && t.y === 0 && t.width === 0 && t.height === 0
},
reset(t: IBoundsData): void {
B.set(t)
}
}
const B = BoundsHelper
const { add, copy } = B