diff --git a/src/Handler.ts b/src/Handler.ts index 45b09ceef..0adb5c089 100644 --- a/src/Handler.ts +++ b/src/Handler.ts @@ -12,6 +12,7 @@ import Storage from './Storage'; import Element, {ElementEvent} from './Element'; import CanvasPainter from './canvas/Painter'; import BoundingRect from './core/BoundingRect'; +import { PI, PI2, mathCos, mathSin } from './core/math'; /** * [The interface between `Handler` and `HandlerProxy`]: @@ -382,12 +383,11 @@ class Handler extends Eventful { */ if (candidates.length) { const rStep = 4; - const thetaStep = Math.PI / 12; - const PI2 = Math.PI * 2; + const thetaStep = PI / 12; for (let r = 0; r < targetSizeHalf; r += rStep) { for (let theta = 0; theta < PI2; theta += thetaStep) { - const x1 = x + r * Math.cos(theta); - const y1 = y + r * Math.sin(theta); + const x1 = x + r * mathCos(theta); + const y1 = y + r * mathSin(theta); setHoverTarget(candidates, out, x1, y1, exclude); if (out.target) { return out; diff --git a/src/animation/Animator.ts b/src/animation/Animator.ts index bfeb27446..2d7fd487d 100644 --- a/src/animation/Animator.ts +++ b/src/animation/Animator.ts @@ -21,6 +21,7 @@ import easingFuncs, { AnimationEasing } from './easing'; import Animation from './Animation'; import { createCubicEasingFunc } from './cubicEasing'; import { isLinearGradient, isRadialGradient } from '../svg/helper'; +import { mathFloor, mathMax, mathMin } from '../core/math'; type NumberArray = ArrayLike type InterpolatableType = string | number | NumberArray | NumberArray[]; @@ -120,9 +121,9 @@ function fillColorStops(val0: ParsedColorStop[], val1: ParsedColorStop[]) { const len1 = val1.length; const shorterArr = len0 > len1 ? val1 : val0; - const shorterLen = Math.min(len0, len1); + const shorterLen = mathMin(len0, len1); const last = shorterArr[shorterLen - 1] || { color: [0, 0, 0, 0], offset: 0 }; - for (let i = shorterLen; i < Math.max(len0, len1); i++) { + for (let i = shorterLen; i < mathMax(len0, len1); i++) { // Use last color stop to fill the shorter array shorterArr.push({ offset: last.offset, @@ -195,9 +196,9 @@ export function cloneValue(value: InterpolatableType) { } function rgba2String(rgba: number[]): string { - rgba[0] = Math.floor(rgba[0]) || 0; - rgba[1] = Math.floor(rgba[1]) || 0; - rgba[2] = Math.floor(rgba[2]) || 0; + rgba[0] = mathFloor(rgba[0]) || 0; + rgba[1] = mathFloor(rgba[1]) || 0; + rgba[2] = mathFloor(rgba[2]) || 0; rgba[3] = rgba[3] == null ? 1 : rgba[3]; return 'rgba(' + rgba.join(',') + ')'; @@ -468,7 +469,6 @@ class Track { // find kf2 and kf3 and do interpolation let frameIdx; const lastFrame = this._lastFr; - const mathMin = Math.min; let frame; let nextFrame; if (kfsNum === 1) { @@ -787,7 +787,7 @@ export default class Animator { } track.addKeyframe(time, cloneValue(props[propName]), easing); } - this._maxTime = Math.max(this._maxTime, time); + this._maxTime = mathMax(this._maxTime, time); return this; } diff --git a/src/animation/Clip.ts b/src/animation/Clip.ts index 0acf6bf40..b1de6f4fe 100644 --- a/src/animation/Clip.ts +++ b/src/animation/Clip.ts @@ -16,6 +16,7 @@ import easingFuncs, {AnimationEasing} from './easing'; import type Animation from './Animation'; import { isFunction, noop } from '../core/util'; import { createCubicEasingFunc } from './cubicEasing'; +import { mathMin } from '../core/math'; type OnframeCallback = (percent: number) => void; type ondestroyCallback = () => void @@ -100,7 +101,7 @@ export default class Clip { percent = 0; } - percent = Math.min(percent, 1); + percent = mathMin(percent, 1); const easingFunc = this.easingFunc; const schedule = easingFunc ? easingFunc(percent) : percent; diff --git a/src/animation/easing.ts b/src/animation/easing.ts index e240ee18d..9e6fdf460 100644 --- a/src/animation/easing.ts +++ b/src/animation/easing.ts @@ -4,6 +4,8 @@ * @exports zrender/animation/easing */ +import { PI_OVER_2, PI2, PI, mathCos, mathSin, mathPow, mathSqrt, mathASin } from '../core/math'; + type easingFunc = (percent: number) => number; export type AnimationEasing = keyof typeof easingFuncs | easingFunc; @@ -126,21 +128,21 @@ const easingFuncs = { * @return {number} */ sinusoidalIn(k: number) { - return 1 - Math.cos(k * Math.PI / 2); + return 1 - mathCos(k * PI_OVER_2); }, /** * @param {number} k * @return {number} */ sinusoidalOut(k: number) { - return Math.sin(k * Math.PI / 2); + return mathSin(k * PI_OVER_2); }, /** * @param {number} k * @return {number} */ sinusoidalInOut(k: number) { - return 0.5 * (1 - Math.cos(Math.PI * k)); + return 0.5 * (1 - mathCos(PI * k)); }, // 指数曲线的缓动(2^t) @@ -149,14 +151,14 @@ const easingFuncs = { * @return {number} */ exponentialIn(k: number) { - return k === 0 ? 0 : Math.pow(1024, k - 1); + return k === 0 ? 0 : mathPow(1024, k - 1); }, /** * @param {number} k * @return {number} */ exponentialOut(k: number) { - return k === 1 ? 1 : 1 - Math.pow(2, -10 * k); + return k === 1 ? 1 : 1 - mathPow(2, -10 * k); }, /** * @param {number} k @@ -170,9 +172,9 @@ const easingFuncs = { return 1; } if ((k *= 2) < 1) { - return 0.5 * Math.pow(1024, k - 1); + return 0.5 * mathPow(1024, k - 1); } - return 0.5 * (-Math.pow(2, -10 * (k - 1)) + 2); + return 0.5 * (-mathPow(2, -10 * (k - 1)) + 2); }, // 圆形曲线的缓动(sqrt(1-t^2)) @@ -181,14 +183,14 @@ const easingFuncs = { * @return {number} */ circularIn(k: number) { - return 1 - Math.sqrt(1 - k * k); + return 1 - mathSqrt(1 - k * k); }, /** * @param {number} k * @return {number} */ circularOut(k: number) { - return Math.sqrt(1 - (--k * k)); + return mathSqrt(1 - (--k * k)); }, /** * @param {number} k @@ -196,9 +198,9 @@ const easingFuncs = { */ circularInOut(k: number) { if ((k *= 2) < 1) { - return -0.5 * (Math.sqrt(1 - k * k) - 1); + return -0.5 * (mathSqrt(1 - k * k) - 1); } - return 0.5 * (Math.sqrt(1 - (k -= 2) * k) + 1); + return 0.5 * (mathSqrt(1 - (k -= 2) * k) + 1); }, // 创建类似于弹簧在停止前来回振荡的动画 @@ -221,10 +223,10 @@ const easingFuncs = { s = p / 4; } else { - s = p * Math.asin(1 / a) / (2 * Math.PI); + s = p * mathASin(1 / a) / PI2; } - return -(a * Math.pow(2, 10 * (k -= 1)) - * Math.sin((k - s) * (2 * Math.PI) / p)); + return -(a * mathPow(2, 10 * (k -= 1)) + * mathSin((k - s) * PI2 / p)); }, /** * @param {number} k @@ -245,38 +247,38 @@ const easingFuncs = { s = p / 4; } else { - s = p * Math.asin(1 / a) / (2 * Math.PI); + s = p * mathASin(1 / a) / PI2; } - return (a * Math.pow(2, -10 * k) - * Math.sin((k - s) * (2 * Math.PI) / p) + 1); + return (a * mathPow(2, -10 * k) + * mathSin((k - s) * PI2 / p) + 1); }, /** * @param {number} k * @return {number} */ elasticInOut(k: number) { - let s; - let a = 0.1; - let p = 0.4; if (k === 0) { return 0; } if (k === 1) { return 1; } + let s; + let a = 0.1; + const p = 0.4; if (!a || a < 1) { a = 1; s = p / 4; } else { - s = p * Math.asin(1 / a) / (2 * Math.PI); + s = p * mathASin(1 / a) / PI2; } if ((k *= 2) < 1) { - return -0.5 * (a * Math.pow(2, 10 * (k -= 1)) - * Math.sin((k - s) * (2 * Math.PI) / p)); + return -0.5 * (a * mathPow(2, 10 * (k -= 1)) + * mathSin((k - s) * PI2 / p)); } - return a * Math.pow(2, -10 * (k -= 1)) - * Math.sin((k - s) * (2 * Math.PI) / p) * 0.5 + 1; + return a * mathPow(2, -10 * (k -= 1)) + * mathSin((k - s) * PI2 / p) * 0.5 + 1; }, @@ -286,7 +288,7 @@ const easingFuncs = { * @return {number} */ backIn(k: number) { - let s = 1.70158; + const s = 1.70158; return k * k * ((s + 1) * k - s); }, /** @@ -294,7 +296,7 @@ const easingFuncs = { * @return {number} */ backOut(k: number) { - let s = 1.70158; + const s = 1.70158; return --k * k * ((s + 1) * k + s) + 1; }, /** @@ -302,7 +304,7 @@ const easingFuncs = { * @return {number} */ backInOut(k: number) { - let s = 1.70158 * 1.525; + const s = 1.70158 * 1.525; if ((k *= 2) < 1) { return 0.5 * (k * k * ((s + 1) * k - s)); } diff --git a/src/canvas/Painter.ts b/src/canvas/Painter.ts index 568885692..13531f02a 100644 --- a/src/canvas/Painter.ts +++ b/src/canvas/Painter.ts @@ -14,6 +14,7 @@ import BoundingRect from '../core/BoundingRect'; import { REDRAW_BIT } from '../graphic/constants'; import { getSize } from './helper'; import type IncrementalDisplayable from '../graphic/IncrementalDisplayable'; +import { mathRandom } from '../core/math'; const HOVER_LAYER_ZLEVEL = 1e5; const CANVAS_ZLEVEL = 314159; @@ -236,7 +237,7 @@ export default class CanvasPainter implements PainterBase { const zlevelList = this._zlevelList; - this._redrawId = Math.random(); + this._redrawId = mathRandom(); this._paintList(list, prevList, paintAll, this._redrawId); diff --git a/src/canvas/graphic.ts b/src/canvas/graphic.ts index 72d554fbf..9a2f3d301 100644 --- a/src/canvas/graphic.ts +++ b/src/canvas/graphic.ts @@ -11,7 +11,7 @@ import Path, { PathStyleProps } from '../graphic/Path'; import ZRImage, { ImageStyleProps } from '../graphic/Image'; import TSpan, {TSpanStyleProps} from '../graphic/TSpan'; import { MatrixArray } from '../core/matrix'; -import { RADIAN_TO_DEGREE } from '../core/util'; +import { mathMax, mathMin, RADIAN_TO_DEGREE } from '../core/math'; import { getLineDash } from './dashStyle'; import { REDRAW_BIT, SHAPE_CHANGED_BIT } from '../graphic/constants'; import type IncrementalDisplayable from '../graphic/IncrementalDisplayable'; @@ -383,7 +383,7 @@ function bindCommonProps( flushPathDrawn(ctx, scope); styleChanged = true; // Ensure opacity is between 0 ~ 1. Invalid opacity will lead to a failure set and use the leaked opacity from the previous. - const opacity = Math.max(Math.min(style.opacity, 1), 0); + const opacity = mathMax(mathMin(style.opacity, 1), 0); ctx.globalAlpha = isNaN(opacity) ? DEFAULT_COMMON_STYLE.opacity : opacity; } diff --git a/src/canvas/helper.ts b/src/canvas/helper.ts index 29d0510aa..ec88622de 100644 --- a/src/canvas/helper.ts +++ b/src/canvas/helper.ts @@ -3,6 +3,7 @@ import { RadialGradientObject } from '../graphic/RadialGradient'; import { GradientObject } from '../graphic/Gradient'; import { RectLike } from '../core/BoundingRect'; import Path from '../graphic/Path'; +import { mathMin } from '../core/math'; function isSafeNum(num: number) { // NaN、Infinity、undefined、'xx' @@ -46,7 +47,7 @@ export function createRadialGradient( ) { const width = rect.width; const height = rect.height; - const min = Math.min(width, height); + const min = mathMin(width, height); let x = obj.x == null ? 0.5 : obj.x; let y = obj.y == null ? 0.5 : obj.y; diff --git a/src/config.ts b/src/config.ts index fe7528abe..f04b5f2be 100644 --- a/src/config.ts +++ b/src/config.ts @@ -1,10 +1,11 @@ import env from './core/env'; +import { mathMax } from './core/math'; let dpr = 1; // If in browser environment if (env.hasGlobalWindow) { - dpr = Math.max( + dpr = mathMax( window.devicePixelRatio || (window.screen && (window.screen as any).deviceXDPI / (window.screen as any).logicalXDPI) || 1, 1 diff --git a/src/contain/arc.ts b/src/contain/arc.ts index cf805afe2..b062492f8 100644 --- a/src/contain/arc.ts +++ b/src/contain/arc.ts @@ -1,7 +1,6 @@ import {normalizeRadian} from './util'; - -const PI2 = Math.PI * 2; +import { PI2, EPSILON4, mathAbs, mathSqrt, mathATan2 } from '../core/math'; /** * 圆弧描边包含判断 @@ -19,13 +18,13 @@ export function containStroke( x -= cx; y -= cy; - const d = Math.sqrt(x * x + y * y); + const d = mathSqrt(x * x + y * y); if ((d - _l > r) || (d + _l < r)) { return false; } // TODO - if (Math.abs(startAngle - endAngle) % PI2 < 1e-4) { + if (mathAbs(startAngle - endAngle) % PI2 < EPSILON4) { // Is a circle return true; } @@ -42,7 +41,7 @@ export function containStroke( endAngle += PI2; } - let angle = Math.atan2(y, x); + let angle = mathATan2(y, x); if (angle < 0) { angle += PI2; } diff --git a/src/contain/line.ts b/src/contain/line.ts index 884aeb7e5..59e996e24 100644 --- a/src/contain/line.ts +++ b/src/contain/line.ts @@ -1,3 +1,4 @@ +import { mathAbs } from '../core/math'; /** * 线段包含判断 @@ -35,7 +36,7 @@ export function containStroke( _b = (x0 * y1 - x1 * y0) / (x0 - x1); } else { - return Math.abs(x - x0) <= _l / 2; + return mathAbs(x - x0) <= _l / 2; } const tmp = _a * x - y + _b; const _s = tmp * tmp / (_a * _a + 1); diff --git a/src/contain/path.ts b/src/contain/path.ts index 16a37aa1f..11bf24a91 100644 --- a/src/contain/path.ts +++ b/src/contain/path.ts @@ -5,14 +5,12 @@ import * as quadratic from './quadratic'; import * as arc from './arc'; import * as curve from '../core/curve'; import windingLine from './windingLine'; +import { PI, PI2, PI_OVER_2, EPSILON4, mathAbs, mathSqrt, mathATan2, mathCos, mathSin } from '../core/math'; const CMD = PathProxy.CMD; -const PI2 = Math.PI * 2; - -const EPSILON = 1e-4; function isAroundEqual(a: number, b: number) { - return Math.abs(a - b) < EPSILON; + return mathAbs(a - b) < EPSILON4; } // 临时数组 @@ -151,15 +149,15 @@ function windingArc( if (y > r || y < -r) { return 0; } - const tmp = Math.sqrt(r * r - y * y); + const tmp = mathSqrt(r * r - y * y); roots[0] = -tmp; roots[1] = tmp; - const dTheta = Math.abs(startAngle - endAngle); - if (dTheta < 1e-4) { + const dTheta = mathAbs(startAngle - endAngle); + if (dTheta < EPSILON4) { return 0; } - if (dTheta >= PI2 - 1e-4) { + if (dTheta >= PI2 - EPSILON4) { // Is a circle startAngle = 0; endAngle = PI2; @@ -189,7 +187,7 @@ function windingArc( for (let i = 0; i < 2; i++) { const x_ = roots[i]; if (x_ + cx > x) { - let angle = Math.atan2(y, x_); + let angle = mathATan2(y, x_); let dir = anticlockwise ? 1 : -1; if (angle < 0) { angle = PI2 + angle; @@ -198,7 +196,7 @@ function windingArc( (angle >= startAngle && angle <= endAngle) || (angle + PI2 >= startAngle && angle + PI2 <= endAngle) ) { - if (angle > Math.PI / 2 && angle < Math.PI * 1.5) { + if (angle > PI_OVER_2 && angle < PI * 1.5) { dir = -dir; } w += dir; @@ -320,8 +318,8 @@ function containPath( // TODO Arc 旋转 i += 1; const anticlockwise = !!(1 - data[i++]); - x1 = Math.cos(theta) * rx + cx; - y1 = Math.sin(theta) * ry + cy; + x1 = mathCos(theta) * rx + cx; + y1 = mathSin(theta) * ry + cy; // 不是直接使用 arc 命令 if (!isFirst) { w += windingLine(xi, yi, x1, y1, x, y); @@ -347,8 +345,8 @@ function containPath( _x, y ); } - xi = Math.cos(theta + dTheta) * rx + cx; - yi = Math.sin(theta + dTheta) * ry + cy; + xi = mathCos(theta + dTheta) * rx + cx; + yi = mathSin(theta + dTheta) * ry + cy; break; case CMD.R: x0 = xi = data[i++]; diff --git a/src/contain/polygon.ts b/src/contain/polygon.ts index 2295b7134..59c276e33 100644 --- a/src/contain/polygon.ts +++ b/src/contain/polygon.ts @@ -1,10 +1,10 @@ import windingLine from './windingLine'; import { VectorArray } from '../core/vector'; +import { EPSILON8, mathAbs } from '../core/math'; -const EPSILON = 1e-8; function isAroundEqual(a: number, b: number): boolean { - return Math.abs(a - b) < EPSILON; + return mathAbs(a - b) < EPSILON8; } export function contain(points: VectorArray[], x: number, y: number) { diff --git a/src/contain/util.ts b/src/contain/util.ts index 8fe899453..5366e0c81 100644 --- a/src/contain/util.ts +++ b/src/contain/util.ts @@ -1,5 +1,4 @@ - -const PI2 = Math.PI * 2; +import { PI2 } from '../core/math'; export function normalizeRadian(angle: number): number { angle %= PI2; diff --git a/src/core/BoundingRect.ts b/src/core/BoundingRect.ts index c660f7f97..5094fe413 100644 --- a/src/core/BoundingRect.ts +++ b/src/core/BoundingRect.ts @@ -1,10 +1,8 @@ import * as matrix from './matrix'; import Point, { PointLike } from './Point'; import { NullUndefined } from './types'; +import { mathMin, mathMax, mathAbs, mathCos, mathSin } from './math'; -const mathMin = Math.min; -const mathMax = Math.max; -const mathAbs = Math.abs; const XY = ['x', 'y'] as const; const WH = ['width', 'height'] as const; @@ -458,7 +456,7 @@ export function createIntersectContext() { _direction = opt.direction; _ctx.bidirectional = opt.bidirectional == null || !!opt.bidirectional; if (!_ctx.bidirectional) { - _dirCheckVec.set(Math.cos(_direction), Math.sin(_direction)); + _dirCheckVec.set(mathCos(_direction), mathSin(_direction)); } } }, @@ -467,8 +465,8 @@ export function createIntersectContext() { const minTv = _ctx.minTv; const dirMinTv = _ctx.dirMinTv; const squareMag = minTv.y * minTv.y + minTv.x * minTv.x; - const dirSin = Math.sin(_direction); - const dirCos = Math.cos(_direction); + const dirSin = mathSin(_direction); + const dirCos = mathCos(_direction); const dotProd = dirSin * minTv.y + dirCos * minTv.x; if (nearZero(dotProd)) { diff --git a/src/core/GestureMgr.ts b/src/core/GestureMgr.ts index 3ddff8ecc..e3f87bfbb 100644 --- a/src/core/GestureMgr.ts +++ b/src/core/GestureMgr.ts @@ -5,6 +5,7 @@ import * as eventUtil from './event'; import { ZRRawTouchEvent, ZRPinchEvent, Dictionary } from './types'; import Displayable from '../graphic/Displayable'; +import { mathSqrt } from './math'; interface TrackItem { points: number[][] @@ -69,7 +70,7 @@ function dist(pointPair: number[][]): number { const dx = pointPair[1][0] - pointPair[0][0]; const dy = pointPair[1][1] - pointPair[0][1]; - return Math.sqrt(dx * dx + dy * dy); + return mathSqrt(dx * dx + dy * dy); } function center(pointPair: number[][]): number[] { diff --git a/src/core/OrientedBoundingRect.ts b/src/core/OrientedBoundingRect.ts index 3788a34cb..8c3f39a8c 100644 --- a/src/core/OrientedBoundingRect.ts +++ b/src/core/OrientedBoundingRect.ts @@ -20,10 +20,8 @@ import Point, { PointLike } from './Point'; import BoundingRect, { BoundingRectIntersectOpt, createIntersectContext } from './BoundingRect'; import { MatrixArray } from './matrix'; +import { mathMin, mathMax, mathAbs } from './math'; -const mathMin = Math.min; -const mathMax = Math.max; -const mathAbs = Math.abs; const _extent = [0, 0]; const _extent2 = [0, 0]; diff --git a/src/core/PathProxy.ts b/src/core/PathProxy.ts index a6af1151c..9706423df 100644 --- a/src/core/PathProxy.ts +++ b/src/core/PathProxy.ts @@ -12,6 +12,7 @@ import BoundingRect from './BoundingRect'; import {devicePixelRatio as dpr} from '../config'; import { fromLine, fromCubic, fromQuadratic, fromArc } from './bbox'; import { cubicLength, cubicSubdivide, quadraticLength, quadraticSubdivide } from './curve'; +import { PI, PI2, mathMin, mathMax, mathCos, mathSin, mathAbs, mathRound, mathSqrt } from './math'; const CMD = { M: 1, @@ -45,14 +46,6 @@ const min: number[] = []; const max: number[] = []; const min2: number[] = []; const max2: number[] = []; -const mathMin = Math.min; -const mathMax = Math.max; -const mathCos = Math.cos; -const mathSin = Math.sin; -const mathAbs = Math.abs; - -const PI = Math.PI; -const PI2 = PI * 2; const hasTypedArray = typeof Float32Array !== 'undefined'; @@ -60,7 +53,7 @@ const tmpAngles: number[] = []; function modPI2(radian: number) { // It's much more stable to mod N instedof PI - const n = Math.round(radian / PI * 1e8) / 1e8; + const n = mathRound(radian / PI * 1e8) / 1e8; return (n % 2) * PI; } /** @@ -647,7 +640,7 @@ export default class PathProxy { const dx = x2 - xi; const dy = y2 - yi; if (mathAbs(dx) > ux || mathAbs(dy) > uy || i === len - 1) { - l = Math.sqrt(dx * dx + dy * dy); + l = mathSqrt(dx * dx + dy * dy); xi = x2; yi = y2; } @@ -695,7 +688,7 @@ export default class PathProxy { } // TODO Ellipse - l = mathMax(rx, ry) * mathMin(PI2, Math.abs(delta)); + l = mathMax(rx, ry) * mathMin(PI2, mathAbs(delta)); xi = mathCos(endAngle) * rx + cx; yi = mathSin(endAngle) * ry + cy; @@ -711,7 +704,7 @@ export default class PathProxy { case CMD.Z: { const dx = x0 - xi; const dy = y0 - yi; - l = Math.sqrt(dx * dx + dy * dy); + l = mathSqrt(dx * dx + dy * dy); xi = x0; yi = y0; diff --git a/src/core/Point.ts b/src/core/Point.ts index 7dd767efa..2e3275776 100644 --- a/src/core/Point.ts +++ b/src/core/Point.ts @@ -1,4 +1,5 @@ import { MatrixArray } from './matrix'; +import { mathSqrt } from './math'; export interface PointLike { x: number @@ -86,7 +87,7 @@ export default class Point { * Get length of point */ len() { - return Math.sqrt(this.x * this.x + this.y * this.y); + return mathSqrt(this.x * this.x + this.y * this.y); } /** @@ -112,7 +113,7 @@ export default class Point { distance(other: PointLike) { const dx = this.x - other.x; const dy = this.y - other.y; - return Math.sqrt(dx * dx + dy * dy); + return mathSqrt(dx * dx + dy * dy); } /** @@ -169,7 +170,7 @@ export default class Point { } static len(p: PointLike) { - return Math.sqrt(p.x * p.x + p.y * p.y); + return mathSqrt(p.x * p.x + p.y * p.y); } static lenSquare(p: PointLike) { diff --git a/src/core/Transformable.ts b/src/core/Transformable.ts index d2cda359c..63873b533 100644 --- a/src/core/Transformable.ts +++ b/src/core/Transformable.ts @@ -1,5 +1,6 @@ import * as matrix from './matrix'; import * as vector from './vector'; +import { PI_OVER_2, EPSILON10, mathAbs, mathSqrt, mathTan, mathATan2, mathCos } from './math'; const mIdentity = matrix.identity; @@ -12,7 +13,6 @@ function isNotAroundZero(val: number) { const scaleTmp: vector.VectorArray = []; const tmpTransform: matrix.MatrixArray = []; const originTransform = matrix.create(); -const abs = Math.abs; class Transformable { @@ -190,11 +190,11 @@ class Transformable { let sx = m[0] * m[0] + m[1] * m[1]; let sy = m[2] * m[2] + m[3] * m[3]; - const rotation = Math.atan2(m[1], m[0]); + const rotation = mathATan2(m[1], m[0]); - const shearX = Math.PI / 2 + rotation - Math.atan2(m[3], m[2]); - sy = Math.sqrt(sy) * Math.cos(shearX); - sx = Math.sqrt(sx); + const shearX = PI_OVER_2 + rotation - mathATan2(m[3], m[2]); + sy = mathSqrt(sy) * mathCos(shearX); + sx = mathSqrt(sx); this.skewX = shearX; this.skewY = 0; @@ -248,8 +248,8 @@ class Transformable { out[1] = 1; return out; } - out[0] = Math.sqrt(m[0] * m[0] + m[1] * m[1]); - out[1] = Math.sqrt(m[2] * m[2] + m[3] * m[3]); + out[0] = mathSqrt(m[0] * m[0] + m[1] * m[1]); + out[1] = mathSqrt(m[2] * m[2] + m[3] * m[3]); if (m[0] < 0) { out[0] = -out[0]; } @@ -289,8 +289,8 @@ class Transformable { // Determinant of `m` means how much the area is enlarged by the // transformation. So its square root can be used as a scale factor // for width. - return m && abs(m[0] - 1) > 1e-10 && abs(m[3] - 1) > 1e-10 - ? Math.sqrt(abs(m[0] * m[3] - m[2] * m[1])) + return m && mathAbs(m[0] - 1) > EPSILON10 && mathAbs(m[3] - 1) > EPSILON10 + ? mathSqrt(mathAbs(m[0] * m[3] - m[2] * m[1])) : 1; } @@ -311,9 +311,9 @@ class Transformable { const rotation = target.rotation || 0; const x = target.x; const y = target.y; - const skewX = target.skewX ? Math.tan(target.skewX) : 0; + const skewX = target.skewX ? mathTan(target.skewX) : 0; // TODO: zrender use different hand in coordinate system and y axis is inversed. - const skewY = target.skewY ? Math.tan(-target.skewY) : 0; + const skewY = target.skewY ? mathTan(-target.skewY) : 0; // The order of transform (-anchor * -origin * scale * skew * rotate * origin * translate). // We merge (-origin * scale * skew) into one. Also did identity in these operations. diff --git a/src/core/WeakMap.ts b/src/core/WeakMap.ts index d4d00b1ad..ea3e5270c 100644 --- a/src/core/WeakMap.ts +++ b/src/core/WeakMap.ts @@ -1,4 +1,6 @@ -let wmUniqueIndex = Math.round(Math.random() * 9); +import { mathRound, mathRandom } from './math'; + +let wmUniqueIndex = mathRound(mathRandom() * 9); const supportDefineProperty = typeof Object.defineProperty === 'function'; diff --git a/src/core/bbox.ts b/src/core/bbox.ts index 921a62239..acd9d5486 100644 --- a/src/core/bbox.ts +++ b/src/core/bbox.ts @@ -4,12 +4,7 @@ import * as vec2 from './vector'; import * as curve from './curve'; - -const mathMin = Math.min; -const mathMax = Math.max; -const mathSin = Math.sin; -const mathCos = Math.cos; -const PI2 = Math.PI * 2; +import { PI2, PI_OVER_2, mathMin, mathMax, mathSin, mathCos, EPSILON4, mathAbs } from './math'; const start = vec2.create(); const end = vec2.create(); @@ -130,10 +125,10 @@ export function fromArc( const vec2Min = vec2.min; const vec2Max = vec2.max; - const diff = Math.abs(startAngle - endAngle); + const diff = mathAbs(startAngle - endAngle); - if (diff % PI2 < 1e-4 && diff > 1e-4) { + if (diff % PI2 < EPSILON4 && diff > EPSILON4) { // Is a circle min[0] = x - rx; min[1] = y - ry; @@ -175,7 +170,7 @@ export function fromArc( // const number = 0; // const step = (anticlockwise ? -Math.PI : Math.PI) / 2; - for (let angle = 0; angle < endAngle; angle += Math.PI / 2) { + for (let angle = 0; angle < endAngle; angle += PI_OVER_2) { if (angle > startAngle) { extremity[0] = mathCos(angle) * rx + x; extremity[1] = mathSin(angle) * ry + y; diff --git a/src/core/curve.ts b/src/core/curve.ts index 9e3580723..4310aa025 100644 --- a/src/core/curve.ts +++ b/src/core/curve.ts @@ -7,12 +7,8 @@ import { distSquare as v2DistSquare, VectorArray } from './vector'; +import { EPSILON4, EPSILON8, mathACos, mathCos, mathPow, mathSin, mathSqrt } from './math'; -const mathPow = Math.pow; -const mathSqrt = Math.sqrt; - -const EPSILON = 1e-8; -const EPSILON_NUMERIC = 1e-4; const THREE_SQRT = mathSqrt(3); const ONE_THIRD = 1 / 3; @@ -23,10 +19,10 @@ const _v1 = v2Create(); const _v2 = v2Create(); function isAroundZero(val: number) { - return val > -EPSILON && val < EPSILON; + return val > -EPSILON8 && val < EPSILON8; } function isNotAroundZero(val: number) { - return val > EPSILON || val < -EPSILON; + return val > EPSILON8 || val < -EPSILON8; } /** * 计算三次贝塞尔值 @@ -112,13 +108,13 @@ export function cubicRootAt(p0: number, p1: number, p2: number, p3: number, val: } else { const T = (2 * A * b - 3 * a * B) / (2 * mathSqrt(A * A * A)); - const theta = Math.acos(T) / 3; + const theta = mathACos(T) / 3; const ASqrt = mathSqrt(A); - const tmp = Math.cos(theta); + const tmp = mathCos(theta); const t1 = (-b - 2 * ASqrt * tmp) / (3 * a); - const t2 = (-b + ASqrt * (tmp + THREE_SQRT * Math.sin(theta))) / (3 * a); - const t3 = (-b + ASqrt * (tmp - THREE_SQRT * Math.sin(theta))) / (3 * a); + const t2 = (-b + ASqrt * (tmp + THREE_SQRT * mathSin(theta))) / (3 * a); + const t3 = (-b + ASqrt * (tmp - THREE_SQRT * mathSin(theta))) / (3 * a); if (t1 >= 0 && t1 <= 1) { roots[n++] = t1; } @@ -230,7 +226,7 @@ export function cubicProjectPoint( // At most 32 iteration for (let i = 0; i < 32; i++) { - if (interval < EPSILON_NUMERIC) { + if (interval < EPSILON4) { break; } prev = t - interval; @@ -291,7 +287,7 @@ export function cubicLength( const dx = x - px; const dy = y - py; - d += Math.sqrt(dx * dx + dy * dy); + d += mathSqrt(dx * dx + dy * dy); px = x; py = y; @@ -430,7 +426,7 @@ export function quadraticProjectPoint( // At most 32 iteration for (let i = 0; i < 32; i++) { - if (interval < EPSILON_NUMERIC) { + if (interval < EPSILON4) { break; } const prev = t - interval; @@ -490,7 +486,7 @@ export function quadraticLength( const dx = x - px; const dy = y - py; - d += Math.sqrt(dx * dx + dy * dy); + d += mathSqrt(dx * dx + dy * dy); px = x; py = y; diff --git a/src/core/event.ts b/src/core/event.ts index e872db58c..eab9da126 100644 --- a/src/core/event.ts +++ b/src/core/event.ts @@ -6,6 +6,7 @@ import Eventful from './Eventful'; import env from './env'; import { ZRRawEvent } from './types'; import {isCanvasEl, transformCoordWithViewport} from './dom'; +import { mathAbs } from './math'; const MOUSE_EVENT_REG = /^(?:mouse|pointer|contextmenu|drag|drop)|click/; const _calcOut: number[] = []; @@ -221,7 +222,7 @@ function getWheelDeltaMayPolyfill(e: ZRRawEvent): number { // Test in Chrome and Safari (MacOS): // The sign is corrent. // The abs value is 99% corrent (inconsist case only like 62~63, 125~126 ...) - const delta = deltaY !== 0 ? Math.abs(deltaY) : Math.abs(deltaX); + const delta = deltaY !== 0 ? mathAbs(deltaY) : mathAbs(deltaX); const sign = deltaY > 0 ? -1 : deltaY < 0 ? 1 : deltaX > 0 ? -1 diff --git a/src/core/fourPointsTransform.ts b/src/core/fourPointsTransform.ts index 62322c6ce..8e9e73751 100644 --- a/src/core/fourPointsTransform.ts +++ b/src/core/fourPointsTransform.ts @@ -6,7 +6,9 @@ * "cv::getPerspectiveTransform", "Direct Linear Transformation". */ -const LN2 = Math.log(2); +import { mathLog, mathRound } from './math'; + +const LN2 = mathLog(2); function determinant( rows: number[][], @@ -25,7 +27,7 @@ function determinant( if (rank === 1) { // In this case the colMask must be like: `11101111`. We can find the place of `0`. - const colStart = Math.round(Math.log(((1 << fullRank) - 1) & ~colMask) / LN2); + const colStart = mathRound(mathLog(((1 << fullRank) - 1) & ~colMask) / LN2); return rows[rowStart][colStart]; } diff --git a/src/core/math.ts b/src/core/math.ts new file mode 100644 index 000000000..1f0c104ac --- /dev/null +++ b/src/core/math.ts @@ -0,0 +1,53 @@ +export const PI = Math.PI; + +export const PI2 = PI * 2; + +export const PI_OVER_2 = PI / 2; + +export const RADIAN_TO_DEGREE = 180 / Math.PI; + +export const DEGREE_TO_RADIAN = Math.PI / 180; + +export const EPSILON = Number.EPSILON || Math.pow(2, -52); + +export const EPSILON4 = 1e-4; + +export const EPSILON5 = 1e-5; + +export const EPSILON8 = 1e-8; + +export const EPSILON10 = 1e-10; + +export const mathMin = Math.min; + +export const mathMax = Math.max; + +export const mathAbs = Math.abs; + +export const mathRound = Math.round; + +export const mathCeil = Math.ceil; + +export const mathFloor = Math.floor; + +export const mathPow = Math.pow; + +export const mathSqrt = Math.sqrt; + +export const mathSin = Math.sin; + +export const mathCos = Math.cos; + +export const mathTan = Math.tan; + +export const mathASin = Math.asin; + +export const mathACos = Math.acos; + +export const mathATan = Math.atan; + +export const mathATan2 = Math.atan2; + +export const mathLog = Math.log; + +export const mathRandom = Math.random; \ No newline at end of file diff --git a/src/core/matrix.ts b/src/core/matrix.ts index 435ab5a4b..3d60360a6 100644 --- a/src/core/matrix.ts +++ b/src/core/matrix.ts @@ -6,6 +6,7 @@ /* global Float32Array */ import {VectorArray} from './vector'; +import { mathCos, mathSin } from './math'; export type MatrixArray = number[] /** @@ -92,8 +93,8 @@ export function rotate( const ab = a[1]; const ad = a[3]; const aty = a[5]; - const st = Math.sin(rad); - const ct = Math.cos(rad); + const st = mathSin(rad); + const ct = mathCos(rad); out[0] = aa * ct + ab * st; out[1] = -aa * st + ab * ct; diff --git a/src/core/util.ts b/src/core/util.ts index b36f7f714..cd3df214a 100644 --- a/src/core/util.ts +++ b/src/core/util.ts @@ -819,6 +819,5 @@ export function hasOwn(own: object, prop: string): boolean { export function noop() {} -export const RADIAN_TO_DEGREE = 180 / Math.PI; - -export const EPSILON = Number.EPSILON || Math.pow(2, -52); +// For backward compatibility +export { RADIAN_TO_DEGREE, EPSILON } from './math'; \ No newline at end of file diff --git a/src/core/vector.ts b/src/core/vector.ts index 254dc7185..7740b9657 100644 --- a/src/core/vector.ts +++ b/src/core/vector.ts @@ -3,6 +3,7 @@ * Use zrender.Point class instead */ import { MatrixArray } from './matrix'; +import { mathMax, mathMin, mathSqrt } from './math'; /* global Float32Array */ @@ -80,7 +81,7 @@ export function sub(out: T, v1: VectorArray, v2: VectorAr * 向量长度 */ export function len(v: VectorArray): number { - return Math.sqrt(lenSquare(v)); + return mathSqrt(lenSquare(v)); } export const length = len; @@ -146,7 +147,7 @@ export function normalize(out: T, v: VectorArray): T { * 计算向量间距离 */ export function distance(v1: VectorArray, v2: VectorArray): number { - return Math.sqrt( + return mathSqrt( (v1[0] - v2[0]) * (v1[0] - v2[0]) + (v1[1] - v2[1]) * (v1[1] - v2[1]) ); @@ -195,8 +196,8 @@ export function applyTransform(out: T, v: VectorArray, m: * 求两个向量最小值 */ export function min(out: T, v1: VectorArray, v2: VectorArray): T { - out[0] = Math.min(v1[0], v2[0]); - out[1] = Math.min(v1[1], v2[1]); + out[0] = mathMin(v1[0], v2[0]); + out[1] = mathMin(v1[1], v2[1]); return out; } @@ -204,7 +205,7 @@ export function min(out: T, v1: VectorArray, v2: VectorAr * 求两个向量最大值 */ export function max(out: T, v1: VectorArray, v2: VectorArray): T { - out[0] = Math.max(v1[0], v2[0]); - out[1] = Math.max(v1[1], v2[1]); + out[0] = mathMax(v1[0], v2[0]); + out[1] = mathMax(v1[1], v2[1]); return out; } diff --git a/src/graphic/Displayable.ts b/src/graphic/Displayable.ts index 839156f2a..9107de503 100644 --- a/src/graphic/Displayable.ts +++ b/src/graphic/Displayable.ts @@ -9,10 +9,11 @@ import Path from './Path'; import { keys, extend, createObject } from '../core/util'; import Animator from '../animation/Animator'; import { REDRAW_BIT, STYLE_CHANGED_BIT } from './constants'; +import { mathRandom, mathRound, mathMin, mathAbs, mathFloor, mathCeil } from '../core/math'; // type CalculateTextPositionResult = ReturnType -const STYLE_MAGIC_KEY = '__zr_style_' + Math.round((Math.random() * 10)); +const STYLE_MAGIC_KEY = '__zr_style_' + mathRound(mathRandom() * 10); export interface CommonStyleProps { shadowBlur?: number @@ -285,20 +286,20 @@ class Displayable extends Ele } if (shadowSize || shadowOffsetX || shadowOffsetY) { - rect.width += shadowSize * 2 + Math.abs(shadowOffsetX); - rect.height += shadowSize * 2 + Math.abs(shadowOffsetY); - rect.x = Math.min(rect.x, rect.x + shadowOffsetX - shadowSize); - rect.y = Math.min(rect.y, rect.y + shadowOffsetY - shadowSize); + rect.width += shadowSize * 2 + mathAbs(shadowOffsetX); + rect.height += shadowSize * 2 + mathAbs(shadowOffsetY); + rect.x = mathMin(rect.x, rect.x + shadowOffsetX - shadowSize); + rect.y = mathMin(rect.y, rect.y + shadowOffsetY - shadowSize); } // For the accuracy tolerance of text height or line joint point const tolerance = this.dirtyRectTolerance; if (!rect.isZero()) { - rect.x = Math.floor(rect.x - tolerance); - rect.y = Math.floor(rect.y - tolerance); - rect.width = Math.ceil(rect.width + 1 + tolerance * 2); - rect.height = Math.ceil(rect.height + 1 + tolerance * 2); + rect.x = mathFloor(rect.x - tolerance); + rect.y = mathFloor(rect.y - tolerance); + rect.width = mathCeil(rect.width + 1 + tolerance * 2); + rect.height = mathCeil(rect.height + 1 + tolerance * 2); } } return rect; diff --git a/src/graphic/Path.ts b/src/graphic/Path.ts index b1b16d087..e4f219474 100644 --- a/src/graphic/Path.ts +++ b/src/graphic/Path.ts @@ -18,6 +18,7 @@ import { lum } from '../tool/color'; import { DARK_LABEL_COLOR, LIGHT_LABEL_COLOR, DARK_MODE_THRESHOLD, LIGHTER_LABEL_COLOR } from '../config'; import { REDRAW_BIT, SHAPE_CHANGED_BIT, STYLE_CHANGED_BIT } from './constants'; import { TRANSFORMABLE_PROPS } from '../core/Transformable'; +import { mathMax } from '../core/math'; export interface PathStyleProps extends CommonStyleProps { @@ -370,7 +371,7 @@ class Path extends Displayable { // Only add extra hover lineWidth when there are no fill if (!this.hasFill()) { const strokeContainThreshold = this.strokeContainThreshold; - w = Math.max(w, strokeContainThreshold == null ? 4 : strokeContainThreshold); + w = mathMax(w, strokeContainThreshold == null ? 4 : strokeContainThreshold); } // Consider line width // Line scale can't be 0; @@ -405,7 +406,7 @@ class Path extends Displayable { if (lineScale > 1e-10) { // Only add extra hover lineWidth when there are no fill if (!this.hasFill()) { - lineWidth = Math.max(lineWidth, this.strokeContainThreshold); + lineWidth = mathMax(lineWidth, this.strokeContainThreshold); } if (pathContain.containStroke( pathProxy, lineWidth / lineScale, x, y diff --git a/src/graphic/helper/parseText.ts b/src/graphic/helper/parseText.ts index 69c63a694..743d6b1b8 100644 --- a/src/graphic/helper/parseText.ts +++ b/src/graphic/helper/parseText.ts @@ -14,6 +14,7 @@ import { ensureFontMeasureInfo, FontMeasureInfo, getLineHeight, measureCharWidth, measureWidth, parsePercent, } from '../../contain/text'; import BoundingRect, { BoundingRectIntersectOpt } from '../../core/BoundingRect'; +import { mathFloor, mathMax } from '../../core/math'; const STYLE_REG = /\{([a-zA-Z0-9_]+)\|([^}]*)\}/g; @@ -103,7 +104,7 @@ function prepareTruncateOptions( // Example 1: minChar: 3, text: 'asdfzxcv', truncate result: 'asdf', but not: 'a...'. // Example 2: minChar: 3, text: '维度', truncate result: '维', but not: '...'. - let contentWidth = containerWidth = Math.max(0, containerWidth - 1); // Reserve some gap. + let contentWidth = containerWidth = mathMax(0, containerWidth - 1); // Reserve some gap. for (let i = 0; i < minChar && contentWidth >= ascCharWidth; i++) { contentWidth -= ascCharWidth; } @@ -156,7 +157,7 @@ function truncateSingleLine( const subLength = j === 0 ? estimateLength(textLine, contentWidth, fontMeasureInfo) : lineWidth > 0 - ? Math.floor(textLine.length * contentWidth / lineWidth) + ? mathFloor(textLine.length * contentWidth / lineWidth) : 0; textLine = textLine.substr(0, subLength); @@ -256,7 +257,7 @@ export function parsePlainText( // Truncate lines. if (contentHeight > height && truncateLineOverflow) { - const lineCount = Math.floor(height / lineHeight); + const lineCount = mathFloor(height / lineHeight); isTruncated = isTruncated || (lines.length > lineCount); lines = lines.slice(0, lineCount); @@ -291,7 +292,7 @@ export function parsePlainText( let contentWidth = 0; const fontMeasureInfo = ensureFontMeasureInfo(font); for (let i = 0; i < lines.length; i++) { - contentWidth = Math.max(measureWidth(fontMeasureInfo, lines[i]), contentWidth); + contentWidth = mathMax(measureWidth(fontMeasureInfo, lines[i]), contentWidth); } if (width == null) { // When width is not explicitly set, use contentWidth as width. @@ -445,7 +446,7 @@ export function parseRichText( line.width = lineWidth; line.lineHeight = lineHeight; calculatedHeight += lineHeight; - calculatedWidth = Math.max(calculatedWidth, lineWidth); + calculatedWidth = mathMax(calculatedWidth, lineWidth); } // Calculate layout info of tokens. outer: for (let i = 0; i < contentBlock.lines.length; i++) { @@ -521,7 +522,7 @@ export function parseRichText( bgImg = imageHelper.findExistImage(bgImg); if (imageHelper.isImageReady(bgImg)) { // Update token width from image size. - token.width = Math.max(token.width, bgImg.width * tokenHeight / bgImg.height); + token.width = mathMax(token.width, bgImg.width * tokenHeight / bgImg.height); } } } @@ -553,7 +554,7 @@ export function parseRichText( token.width += paddingH; lineWidth += token.width; - tokenStyle && (lineHeight = Math.max(lineHeight, token.lineHeight)); + tokenStyle && (lineHeight = mathMax(lineHeight, token.lineHeight)); // prevToken = token; } diff --git a/src/graphic/helper/roundRect.ts b/src/graphic/helper/roundRect.ts index db79d2c4c..4f14dabc2 100644 --- a/src/graphic/helper/roundRect.ts +++ b/src/graphic/helper/roundRect.ts @@ -1,4 +1,5 @@ import PathProxy from '../../core/PathProxy'; +import { PI, PI_OVER_2 } from '../../core/math'; export function buildPath(ctx: CanvasRenderingContext2D | PathProxy, shape: { x: number @@ -77,11 +78,11 @@ export function buildPath(ctx: CanvasRenderingContext2D | PathProxy, shape: { } ctx.moveTo(x + r1, y); ctx.lineTo(x + width - r2, y); - r2 !== 0 && ctx.arc(x + width - r2, y + r2, r2, -Math.PI / 2, 0); + r2 !== 0 && ctx.arc(x + width - r2, y + r2, r2, -PI_OVER_2, 0); ctx.lineTo(x + width, y + height - r3); - r3 !== 0 && ctx.arc(x + width - r3, y + height - r3, r3, 0, Math.PI / 2); + r3 !== 0 && ctx.arc(x + width - r3, y + height - r3, r3, 0, PI_OVER_2); ctx.lineTo(x + r4, y + height); - r4 !== 0 && ctx.arc(x + r4, y + height - r4, r4, Math.PI / 2, Math.PI); + r4 !== 0 && ctx.arc(x + r4, y + height - r4, r4, PI_OVER_2, PI); ctx.lineTo(x, y + r1); - r1 !== 0 && ctx.arc(x + r1, y + r1, r1, Math.PI, Math.PI * 1.5); + r1 !== 0 && ctx.arc(x + r1, y + r1, r1, PI, PI * 1.5); } diff --git a/src/graphic/helper/roundSector.ts b/src/graphic/helper/roundSector.ts index 9ba5f313b..e55736abf 100644 --- a/src/graphic/helper/roundSector.ts +++ b/src/graphic/helper/roundSector.ts @@ -1,17 +1,10 @@ import PathProxy from '../../core/PathProxy'; import { isArray } from '../../core/util'; +import { + PI, PI2, EPSILON4, + mathSin, mathCos, mathACos, mathATan2, mathAbs, mathSqrt, mathMax, mathMin, +} from '../../core/math'; -const PI = Math.PI; -const PI2 = PI * 2; -const mathSin = Math.sin; -const mathCos = Math.cos; -const mathACos = Math.acos; -const mathATan2 = Math.atan2; -const mathAbs = Math.abs; -const mathSqrt = Math.sqrt; -const mathMax = Math.max; -const mathMin = Math.min; -const e = 1e-4; function intersect( x0: number, y0: number, @@ -24,7 +17,7 @@ function intersect( const dx32 = x3 - x2; const dy32 = y3 - y2; let t = dy32 * dx10 - dx32 * dy10; - if (t * t < e) { + if (t * t < EPSILON4) { return; } t = (dx32 * (y0 - y2) - dy32 * (x0 - x2)) / t; @@ -155,21 +148,21 @@ export function buildPath(ctx: CanvasRenderingContext2D | PathProxy, shape: { let arc = mathAbs(endAngle - startAngle); const mod = arc > PI2 && arc % PI2; - mod > e && (arc = mod); + mod > EPSILON4 && (arc = mod); // is a point - if (!(radius > e)) { + if (!(radius > EPSILON4)) { ctx.moveTo(cx, cy); } // is a circle or annulus - else if (arc > PI2 - e) { + else if (arc > PI2 - EPSILON4) { ctx.moveTo( cx + radius * mathCos(startAngle), cy + radius * mathSin(startAngle) ); ctx.arc(cx, cy, radius, startAngle, endAngle, !clockwise); - if (innerRadius > e) { + if (innerRadius > EPSILON4) { ctx.moveTo( cx + innerRadius * mathCos(endAngle), cy + innerRadius * mathSin(endAngle) @@ -204,7 +197,7 @@ export function buildPath(ctx: CanvasRenderingContext2D | PathProxy, shape: { const xire = innerRadius * mathCos(endAngle); const yire = innerRadius * mathSin(endAngle); - const hasArc = arc > e; + const hasArc = arc > EPSILON4; if (hasArc) { const cornerRadius = shape.cornerRadius; if (cornerRadius) { @@ -221,7 +214,7 @@ export function buildPath(ctx: CanvasRenderingContext2D | PathProxy, shape: { limitedIcrMax = icrMax = mathMax(icrs, icre); // draw corner radius - if (ocrMax > e || icrMax > e) { + if (ocrMax > EPSILON4 || icrMax > EPSILON4) { xre = radius * mathCos(endAngle); yre = radius * mathSin(endAngle); xirs = innerRadius * mathCos(startAngle); @@ -252,7 +245,7 @@ export function buildPath(ctx: CanvasRenderingContext2D | PathProxy, shape: { ctx.moveTo(cx + xrs, cy + yrs); } // the outer ring has corners - else if (limitedOcrMax > e) { + else if (limitedOcrMax > EPSILON4) { const crStart = mathMin(ocrStart, limitedOcrMax); const crEnd = mathMin(ocrEnd, limitedOcrMax); const ct0 = computeCornerTangents(xirs, yirs, xrs, yrs, radius, crStart, clockwise); @@ -282,11 +275,11 @@ export function buildPath(ctx: CanvasRenderingContext2D | PathProxy, shape: { } // no inner ring, is a circular sector - if (!(innerRadius > e) || !hasArc) { + if (!(innerRadius > EPSILON4) || !hasArc) { ctx.lineTo(cx + xire, cy + yire); } // the inner ring has corners - else if (limitedIcrMax > e) { + else if (limitedIcrMax > EPSILON4) { const crStart = mathMin(icrStart, limitedIcrMax); const crEnd = mathMin(icrEnd, limitedIcrMax); const ct0 = computeCornerTangents(xire, yire, xre, yre, innerRadius, -crEnd, clockwise); diff --git a/src/graphic/helper/smoothSpline.ts b/src/graphic/helper/smoothSpline.ts index de31ae0eb..eb8108581 100644 --- a/src/graphic/helper/smoothSpline.ts +++ b/src/graphic/helper/smoothSpline.ts @@ -3,6 +3,7 @@ */ import {distance as v2Distance, VectorArray} from '../../core/vector'; +import { mathFloor } from '../../core/math'; function interpolate( p0: number, p1: number, p2: number, p3: number, t: number, t2: number, t3: number @@ -27,7 +28,7 @@ export default function smoothSpline(points: VectorArray[], isLoop?: boolean): V segs = segs < len ? len : segs; for (let i = 0; i < segs; i++) { const pos = i / (segs - 1) * (isLoop ? len : len - 1); - const idx = Math.floor(pos); + const idx = mathFloor(pos); const w = pos - idx; diff --git a/src/graphic/helper/subPixelOptimize.ts b/src/graphic/helper/subPixelOptimize.ts index 6e4231108..8fc6a0278 100644 --- a/src/graphic/helper/subPixelOptimize.ts +++ b/src/graphic/helper/subPixelOptimize.ts @@ -1,12 +1,11 @@ import { PathStyleProps } from '../Path'; +import { mathMax, mathRound } from '../../core/math'; /** * Sub-pixel optimize for canvas rendering, prevent from blur * when rendering a thin vertical/horizontal line. */ -const round = Math.round; - type LineShape = { x1: number y1: number @@ -53,10 +52,10 @@ export function subPixelOptimizeLine( return outputShape as LineShape; } - if (round(x1 * 2) === round(x2 * 2)) { + if (mathRound(x1 * 2) === mathRound(x2 * 2)) { outputShape.x1 = outputShape.x2 = subPixelOptimize(x1, lineWidth, true); } - if (round(y1 * 2) === round(y2 * 2)) { + if (mathRound(y1 * 2) === mathRound(y2 * 2)) { outputShape.y1 = outputShape.y2 = subPixelOptimize(y1, lineWidth, true); } @@ -97,11 +96,11 @@ export function subPixelOptimizeRect( outputShape.x = subPixelOptimize(originX, lineWidth, true); outputShape.y = subPixelOptimize(originY, lineWidth, true); - outputShape.width = Math.max( + outputShape.width = mathMax( subPixelOptimize(originX + originWidth, lineWidth, false) - outputShape.x, originWidth === 0 ? 0 : 1 ); - outputShape.height = Math.max( + outputShape.height = mathMax( subPixelOptimize(originY + originHeight, lineWidth, false) - outputShape.y, originHeight === 0 ? 0 : 1 ); @@ -127,8 +126,8 @@ export function subPixelOptimize( } // Assure that (position + lineWidth / 2) is near integer edge, // otherwise line will be fuzzy in canvas. - const doubledPosition = round(position * 2); - return (doubledPosition + round(lineWidth)) % 2 === 0 + const doubledPosition = mathRound(position * 2); + return (doubledPosition + mathRound(lineWidth)) % 2 === 0 ? doubledPosition / 2 : (doubledPosition + (positiveOrNegative ? 1 : -1)) / 2; } diff --git a/src/graphic/shape/Arc.ts b/src/graphic/shape/Arc.ts index a8b93a738..fac416629 100644 --- a/src/graphic/shape/Arc.ts +++ b/src/graphic/shape/Arc.ts @@ -3,13 +3,14 @@ */ import Path, { PathProps } from '../Path'; +import { mathCos, mathMax, mathSin, PI2 } from '../../core/math'; export class ArcShape { cx = 0; cy = 0; r = 0; startAngle = 0; - endAngle = Math.PI * 2 + endAngle = PI2 clockwise? = true } @@ -40,13 +41,13 @@ class Arc extends Path { const x = shape.cx; const y = shape.cy; - const r = Math.max(shape.r, 0); + const r = mathMax(shape.r, 0); const startAngle = shape.startAngle; const endAngle = shape.endAngle; const clockwise = shape.clockwise; - const unitX = Math.cos(startAngle); - const unitY = Math.sin(startAngle); + const unitX = mathCos(startAngle); + const unitY = mathSin(startAngle); ctx.moveTo(unitX * r + x, unitY * r + y); ctx.arc(x, y, r, startAngle, endAngle, !clockwise); diff --git a/src/graphic/shape/Circle.ts b/src/graphic/shape/Circle.ts index eac75e1de..1567e4aa7 100644 --- a/src/graphic/shape/Circle.ts +++ b/src/graphic/shape/Circle.ts @@ -3,6 +3,7 @@ */ import Path, { PathProps } from '../Path'; +import { PI2 } from '../../core/math'; export class CircleShape { cx = 0 @@ -29,7 +30,7 @@ class Circle extends Path { // Use moveTo to start a new sub path. // Or it will be connected to other subpaths when in CompoundPath ctx.moveTo(shape.cx + shape.r, shape.cy); - ctx.arc(shape.cx, shape.cy, shape.r, 0, Math.PI * 2); + ctx.arc(shape.cx, shape.cy, shape.r, 0, PI2); } }; diff --git a/src/graphic/shape/Isogon.ts b/src/graphic/shape/Isogon.ts index 39e95aeb2..192252494 100644 --- a/src/graphic/shape/Isogon.ts +++ b/src/graphic/shape/Isogon.ts @@ -3,10 +3,8 @@ */ import Path, { PathProps } from '../Path'; +import { PI_OVER_2, PI2, mathSin, mathCos } from '../../core/math'; -const PI = Math.PI; -const sin = Math.sin; -const cos = Math.cos; export class IsogonShape { x = 0 @@ -40,13 +38,13 @@ class Isogon extends Path { const y = shape.y; const r = shape.r; - const dStep = 2 * PI / n; - let deg = -PI / 2; + const dStep = PI2 / n; + let deg = -PI_OVER_2; - ctx.moveTo(x + r * cos(deg), y + r * sin(deg)); + ctx.moveTo(x + r * mathCos(deg), y + r * mathSin(deg)); for (let i = 0, end = n - 1; i < end; i++) { deg += dStep; - ctx.lineTo(x + r * cos(deg), y + r * sin(deg)); + ctx.lineTo(x + r * mathCos(deg), y + r * mathSin(deg)); } ctx.closePath(); diff --git a/src/graphic/shape/Ring.ts b/src/graphic/shape/Ring.ts index 95a1ae5c1..c0c715bd9 100644 --- a/src/graphic/shape/Ring.ts +++ b/src/graphic/shape/Ring.ts @@ -3,6 +3,7 @@ */ import Path, { PathProps } from '../Path'; +import { PI2 } from '../../core/math'; export class RingShape { cx = 0 @@ -29,7 +30,6 @@ class Ring extends Path { buildPath(ctx: CanvasRenderingContext2D, shape: RingShape) { const x = shape.cx; const y = shape.cy; - const PI2 = Math.PI * 2; ctx.moveTo(x + shape.r, y); ctx.arc(x, y, shape.r, 0, PI2, false); ctx.moveTo(x + shape.r0, y); diff --git a/src/graphic/shape/Rose.ts b/src/graphic/shape/Rose.ts index 0162f848f..1ce0fd8b0 100644 --- a/src/graphic/shape/Rose.ts +++ b/src/graphic/shape/Rose.ts @@ -4,10 +4,7 @@ */ import Path, { PathProps } from '../Path'; - -const sin = Math.sin; -const cos = Math.cos; -const radian = Math.PI / 180; +import { DEGREE_TO_RADIAN, mathSin, mathCos } from '../../core/math'; export class RoseShape { cx = 0 @@ -58,12 +55,12 @@ class Rose extends Path { for (let j = 0; j <= 360 * n; j++) { x = r - * sin(k / n * j % 360 * radian) - * cos(j * radian) + * mathSin(k / n * j % 360 * DEGREE_TO_RADIAN) + * mathCos(j * DEGREE_TO_RADIAN) + x0; y = r - * sin(k / n * j % 360 * radian) - * sin(j * radian) + * mathSin(k / n * j % 360 * DEGREE_TO_RADIAN) + * mathSin(j * DEGREE_TO_RADIAN) + y0; ctx.lineTo(x, y); } diff --git a/src/graphic/shape/Sector.ts b/src/graphic/shape/Sector.ts index 96550325f..4d24d0ae6 100644 --- a/src/graphic/shape/Sector.ts +++ b/src/graphic/shape/Sector.ts @@ -1,5 +1,6 @@ import Path, { PathProps } from '../Path'; import * as roundSectorHelper from '../helper/roundSector'; +import { PI2 } from '../../core/math'; export class SectorShape { cx = 0 @@ -7,7 +8,7 @@ export class SectorShape { r0 = 0 r = 0 startAngle = 0 - endAngle = Math.PI * 2 + endAngle = PI2 clockwise = true /** * Corner radius of sector diff --git a/src/graphic/shape/Star.ts b/src/graphic/shape/Star.ts index 86f1a5145..451ffb980 100644 --- a/src/graphic/shape/Star.ts +++ b/src/graphic/shape/Star.ts @@ -4,10 +4,8 @@ */ import Path, { PathProps } from '../Path'; +import { PI, PI2, mathSin, mathCos, PI_OVER_2 } from '../../core/math'; -const PI = Math.PI; -const cos = Math.cos; -const sin = Math.sin; export class StarShape { cx = 0 @@ -49,22 +47,22 @@ class Star extends Path { r0 = n > 4 // 相隔的外部顶点的连线的交点, // 被取为内部交点,以此计算r0 - ? r * cos(2 * PI / n) / cos(PI / n) + ? r * mathCos(PI2 / n) / mathCos(PI / n) // 二三四角星的特殊处理 : r / 3; } const dStep = PI / n; - let deg = -PI / 2; - const xStart = x + r * cos(deg); - const yStart = y + r * sin(deg); + let deg = -PI_OVER_2; + const xStart = x + r * mathCos(deg); + const yStart = y + r * mathSin(deg); deg += dStep; // 记录边界点,用于判断inside ctx.moveTo(xStart, yStart); for (let i = 0, end = n * 2 - 1, ri; i < end; i++) { ri = i % 2 === 0 ? r0 : r; - ctx.lineTo(x + ri * cos(deg), y + ri * sin(deg)); + ctx.lineTo(x + ri * mathCos(deg), y + ri * mathSin(deg)); deg += dStep; } diff --git a/src/graphic/shape/Trochoid.ts b/src/graphic/shape/Trochoid.ts index 744333673..a07db617a 100644 --- a/src/graphic/shape/Trochoid.ts +++ b/src/graphic/shape/Trochoid.ts @@ -4,9 +4,8 @@ */ import Path, { PathProps } from '../Path'; +import { DEGREE_TO_RADIAN, mathSin, mathCos } from '../../core/math'; -const cos = Math.cos; -const sin = Math.sin; export class TrochoidShape { cx = 0 @@ -59,10 +58,10 @@ class Trochoid extends Path { let i = 1; let theta; - x1 = (R + delta * r) * cos(0) - - delta * d * cos(0) + offsetX; - y1 = (R + delta * r) * sin(0) - - d * sin(0) + offsetY; + x1 = (R + delta * r) * mathCos(0) + - delta * d * mathCos(0) + offsetX; + y1 = (R + delta * r) * mathSin(0) + - d * mathSin(0) + offsetY; ctx.moveTo(x1, y1); @@ -73,12 +72,12 @@ class Trochoid extends Path { while ((r * num) % (R + delta * r) !== 0); do { - theta = Math.PI / 180 * i; - x2 = (R + delta * r) * cos(theta) - - delta * d * cos((R / r + delta) * theta) + theta = DEGREE_TO_RADIAN * i; + x2 = (R + delta * r) * mathCos(theta) + - delta * d * mathCos((R / r + delta) * theta) + offsetX; - y2 = (R + delta * r) * sin(theta) - - d * sin((R / r + delta) * theta) + y2 = (R + delta * r) * mathSin(theta) + - d * mathSin((R / r + delta) * theta) + offsetY; ctx.lineTo(x2, y2); i++; diff --git a/src/svg-legacy/helper/PatternManager.ts b/src/svg-legacy/helper/PatternManager.ts index 38afcbda3..f834cbcfe 100644 --- a/src/svg-legacy/helper/PatternManager.ts +++ b/src/svg-legacy/helper/PatternManager.ts @@ -11,6 +11,7 @@ import {createOrUpdateImage} from '../../graphic/helper/image'; import WeakMap from '../../core/WeakMap'; import { getIdURL, isPattern, isSVGPattern } from '../../svg/helper'; import { createElement } from '../../svg/core'; +import { RADIAN_TO_DEGREE } from '../../core/math'; const patternDomMap = new WeakMap(); @@ -194,7 +195,7 @@ export default class PatternManager extends Definable { const x = pattern.x || 0; const y = pattern.y || 0; - const rotation = (pattern.rotation || 0) / Math.PI * 180; + const rotation = (pattern.rotation || 0) * RADIAN_TO_DEGREE; const scaleX = pattern.scaleX || 1; const scaleY = pattern.scaleY || 1; const transform = `translate(${x}, ${y}) rotate(${rotation}) scale(${scaleX}, ${scaleY})`; diff --git a/src/svg/Painter.ts b/src/svg/Painter.ts index 5d1981c77..6d52460de 100644 --- a/src/svg/Painter.ts +++ b/src/svg/Painter.ts @@ -29,6 +29,7 @@ import patch, { updateAttrs } from './patch'; import { getSize } from '../canvas/helper'; import { GradientObject } from '../graphic/Gradient'; import { PatternObject } from '../graphic/Pattern'; +import { mathMax } from '../core/math'; let svgId = 0; @@ -222,7 +223,7 @@ class SVGPainter implements PainterBase { const prevLen = prevClipPaths && prevClipPaths.length || 0; let lca; // Find the lowest common ancestor - for (lca = Math.max(len - 1, prevLen - 1); lca >= 0; lca--) { + for (lca = mathMax(len - 1, prevLen - 1); lca >= 0; lca--) { if (clipPaths && prevClipPaths && clipPaths[lca] === prevClipPaths[lca] ) { diff --git a/src/svg/SVGPathRebuilder.ts b/src/svg/SVGPathRebuilder.ts index 1c9cc2245..b8b8173bf 100644 --- a/src/svg/SVGPathRebuilder.ts +++ b/src/svg/SVGPathRebuilder.ts @@ -1,12 +1,6 @@ import { PathRebuilder } from '../core/PathProxy'; import { isAroundZero } from './helper'; - -const mathSin = Math.sin; -const mathCos = Math.cos; -const PI = Math.PI; -const PI2 = Math.PI * 2; -const degree = 180 / PI; - +import { PI, PI2, mathSin, mathCos, RADIAN_TO_DEGREE, mathPow, mathAbs, mathRound } from '../core/math'; export default class SVGPathRebuilder implements PathRebuilder { private _d: (string | number)[] @@ -22,7 +16,7 @@ export default class SVGPathRebuilder implements PathRebuilder { this._d = []; this._str = ''; - this._p = Math.pow(10, precision || 4); + this._p = mathPow(10, precision || 4); } moveTo(x: number, y: number) { this._add('M', x, y); @@ -50,7 +44,7 @@ export default class SVGPathRebuilder implements PathRebuilder { let dTheta = endAngle - startAngle; const clockwise = !anticlockwise; - const dThetaPositive = Math.abs(dTheta); + const dThetaPositive = mathAbs(dTheta); const isCircle = isAroundZero(dThetaPositive - PI2) || (clockwise ? dTheta >= PI2 : -dTheta >= PI2); @@ -80,7 +74,7 @@ export default class SVGPathRebuilder implements PathRebuilder { this._add('M', x0, y0); } - const xRot = Math.round(psi * degree); + const xRot = mathRound(psi * RADIAN_TO_DEGREE); // It will not draw if start point and end point are exactly the same // We need to add two arcs if (isCircle) { @@ -135,7 +129,7 @@ export default class SVGPathRebuilder implements PathRebuilder { this._invalid = true; return; } - vals.push(Math.round(val * p) / p); + vals.push(mathRound(val * p) / p); } this._d.push(cmd + vals.join(' ')); this._start = cmd === 'Z'; diff --git a/src/svg/cssAnimation.ts b/src/svg/cssAnimation.ts index f14eea43d..fa9553b8e 100644 --- a/src/svg/cssAnimation.ts +++ b/src/svg/cssAnimation.ts @@ -11,6 +11,7 @@ import CompoundPath from '../graphic/CompoundPath'; import { AnimationEasing } from '../animation/easing'; import { createCubicEasingFunc } from '../animation/cubicEasing'; import { getClassId } from './cssClassId'; +import { mathRound } from '../core/math'; export const EASING_MAP: Record = { // From https://easings.net/ @@ -210,7 +211,7 @@ export function createCSSAnimation( if (attrName) { for (let i = 0; i < kfs.length; i++) { const kf = kfs[i]; - const percent = Math.round(kf.time / maxTime * 100) + '%'; + const percent = mathRound(kf.time / maxTime * 100) + '%'; const kfEasing = getEasingFunc(kf.easing); const rawValue = kf.rawValue; diff --git a/src/svg/graphic.ts b/src/svg/graphic.ts index da81eb41a..c079446ff 100644 --- a/src/svg/graphic.ts +++ b/src/svg/graphic.ts @@ -41,8 +41,8 @@ import { hasSeparateFont, parseFontSize } from '../graphic/Text'; import { DEFAULT_FONT, DEFAULT_FONT_FAMILY } from '../core/platform'; import { createCSSEmphasis } from './cssEmphasis'; import { getElementSSRData } from '../zrender'; +import { mathPow, mathRound } from '../core/math'; -const round = Math.round; function isImageLike(val: any): val is HTMLImageElement { return val && isString(val.src); @@ -105,7 +105,7 @@ function setTransform(attrs: SVGVNodeAttrs, m: MatrixArray, compress?: boolean) const mul = compress ? 10 : 1e4; // Use translate possible to reduce the size a bit. attrs.transform = noRotateScale(m) - ? `translate(${round(m[4] * mul) / mul} ${round(m[5] * mul) / mul})` : getMatrixStr(m); + ? `translate(${mathRound(m[4] * mul) / mul} ${mathRound(m[5] * mul) / mul})` : getMatrixStr(m); } } @@ -117,8 +117,8 @@ function convertPolyShape(shape: Polygon['shape'], attrs: SVGVNodeAttrs, mul: nu const points = shape.points; const strArr = []; for (let i = 0; i < points.length; i++) { - strArr.push(round(points[i][0] * mul) / mul); - strArr.push(round(points[i][1] * mul) / mul); + strArr.push(mathRound(points[i][0] * mul) / mul); + strArr.push(mathRound(points[i][1] * mul) / mul); } attrs.points = strArr.join(' '); } @@ -137,7 +137,7 @@ function createAttrsConvert(desc: ShapeMapDesc): ConvertShapeToAttr { const item = normalizedDesc[i]; const val = shape[item[0]]; if (val != null) { - attrs[item[1]] = round(val * mul) / mul; + attrs[item[1]] = mathRound(val * mul) / mul; } } }; @@ -186,7 +186,7 @@ export function brushSVGPath(el: Path, scope: BrushScope) { && !(strokePercent < 1) ) { svgElType = el.type; - const mul = Math.pow(10, precision); + const mul = mathPow(10, precision); builtinShpDef[0](shape, attrs, mul); } else { diff --git a/src/svg/helper.ts b/src/svg/helper.ts index c8eaa2b84..22aa95c8b 100644 --- a/src/svg/helper.ts +++ b/src/svg/helper.ts @@ -2,7 +2,7 @@ import { MatrixArray } from '../core/matrix'; import Transformable, { TransformProp } from '../core/Transformable'; -import { RADIAN_TO_DEGREE, retrieve2, logError, isFunction } from '../core/util'; +import { retrieve2, logError, isFunction } from '../core/util'; import Displayable from '../graphic/Displayable'; import { GradientObject } from '../graphic/Gradient'; import { LinearGradientObject } from '../graphic/LinearGradient'; @@ -11,8 +11,7 @@ import { ImagePatternObject, PatternObject, SVGPatternObject } from '../graphic/ import { RadialGradientObject } from '../graphic/RadialGradient'; import { parse } from '../tool/color'; import env from '../core/env'; - -const mathRound = Math.round; +import { RADIAN_TO_DEGREE, mathRound, mathMax, mathLog, mathCeil, EPSILON4 } from '../core/math'; export function normalizeColor(color: string): { color: string; opacity: number; } { let opacity; @@ -32,9 +31,12 @@ export function normalizeColor(color: string): { color: string; opacity: number; opacity: opacity == null ? 1 : opacity }; } -const EPSILON = 1e-4; + +/** + * EPSILON: 1e-4 + */ export function isAroundZero(transform: number) { - return transform < EPSILON && transform > -EPSILON; + return transform < EPSILON4 && transform > -EPSILON4; } export function round3(transform: number) { @@ -140,8 +142,8 @@ export function getIdURL(id: string) { export function getPathPrecision(el: Path) { const scale = el.getGlobalScale(); - const size = Math.max(scale[0], scale[1]); - return Math.max(Math.ceil(Math.log(size) / Math.log(10)), 1); + const size = mathMax(scale[0], scale[1]); + return mathMax(mathCeil(mathLog(size) / mathLog(10)), 1); } export function getSRTTransformString( diff --git a/src/svg/mapStyleToAttrs.ts b/src/svg/mapStyleToAttrs.ts index 486f3bda7..613d11cd5 100644 --- a/src/svg/mapStyleToAttrs.ts +++ b/src/svg/mapStyleToAttrs.ts @@ -5,11 +5,11 @@ import TSpan, { TSpanStyleProps } from '../graphic/TSpan'; import { getLineDash } from '../canvas/dashStyle'; import { map } from '../core/util'; import { normalizeColor } from './helper'; +import { mathRound } from '../core/math'; type AllStyleOption = PathStyleProps | TSpanStyleProps | ImageStyleProps; const NONE = 'none'; -const mathRound = Math.round; function pathHasFill(style: AllStyleOption): style is PathStyleProps { const fill = (style as PathStyleProps).fill; diff --git a/src/tool/color.ts b/src/tool/color.ts index 54c64303c..b87970754 100644 --- a/src/tool/color.ts +++ b/src/tool/color.ts @@ -1,6 +1,7 @@ import LRU from '../core/LRU'; import { extend, isFunction, isGradientObject, isString, map } from '../core/util'; import { GradientObject } from '../graphic/Gradient'; +import { mathCeil, mathFloor, mathMax, mathMin, mathRandom, mathRound } from '../core/math'; const kCSSColorTable = { 'transparent': [0, 0, 0, 0], 'aliceblue': [240, 248, 255, 1], @@ -80,12 +81,12 @@ const kCSSColorTable = { }; function clampCssByte(i: number): number { // Clamp to integer 0 .. 255. - i = Math.round(i); // Seems to be what Chrome does (vs truncation). + i = mathRound(i); // Seems to be what Chrome does (vs truncation). return i < 0 ? 0 : i > 255 ? 255 : i; } function clampCssAngle(i: number): number { // Clamp to integer 0 .. 360. - i = Math.round(i); // Seems to be what Chrome does (vs truncation). + i = mathRound(i); // Seems to be what Chrome does (vs truncation). return i < 0 ? 0 : i > 360 ? 360 : i; } @@ -312,8 +313,8 @@ function rgba2hsla(rgba: number[]): number[] { const G = rgba[1] / 255; const B = rgba[2] / 255; - const vMin = Math.min(R, G, B); // Min. value of RGB - const vMax = Math.max(R, G, B); // Max. value of RGB + const vMin = mathMin(R, G, B); // Min. value of RGB + const vMax = mathMax(R, G, B); // Max. value of RGB const delta = vMax - vMin; // Delta RGB value const L = (vMax + vMin) / 2; @@ -413,8 +414,8 @@ export function fastLerp( out = out || []; const value = normalizedValue * (colors.length - 1); - const leftIndex = Math.floor(value); - const rightIndex = Math.ceil(value); + const leftIndex = mathFloor(value); + const rightIndex = mathCeil(value); const leftColor = colors[leftIndex]; const rightColor = colors[rightIndex]; const dv = value - leftIndex; @@ -465,8 +466,8 @@ export function lerp( } const value = normalizedValue * (colors.length - 1); - const leftIndex = Math.floor(value); - const rightIndex = Math.ceil(value); + const leftIndex = mathFloor(value); + const rightIndex = mathCeil(value); const leftColor = parse(colors[leftIndex]); const rightColor = parse(colors[rightIndex]); const dv = value - leftIndex; @@ -568,9 +569,9 @@ export function lum(color: string, backgroundLum: number) { */ export function random(): string { return stringify([ - Math.round(Math.random() * 255), - Math.round(Math.random() * 255), - Math.round(Math.random() * 255) + mathRound(mathRandom() * 255), + mathRound(mathRandom() * 255), + mathRound(mathRandom() * 255) ], 'rgb'); } diff --git a/src/tool/convertPath.ts b/src/tool/convertPath.ts index d0848de20..588d88598 100644 --- a/src/tool/convertPath.ts +++ b/src/tool/convertPath.ts @@ -1,10 +1,11 @@ import { cubicSubdivide } from '../core/curve'; import PathProxy from '../core/PathProxy'; +import { PI_OVER_2, EPSILON5, mathAbs, mathTan, mathCos, mathSin, mathMax, mathMin, mathSqrt } from '../core/math'; const CMD = PathProxy.CMD; function aroundEqual(a: number, b: number) { - return Math.abs(a - b) < 1e-5; + return mathAbs(a - b) < EPSILON5; } export function pathToBezierCurves(path: PathProxy) { @@ -36,14 +37,14 @@ export function pathToBezierCurves(path: PathProxy) { function addArc(startAngle: number, endAngle: number, cx: number, cy: number, rx: number, ry: number) { // https://stackoverflow.com/questions/1734745/how-to-create-circle-with-b%C3%A9zier-curves - const delta = Math.abs(endAngle - startAngle); - const len = Math.tan(delta / 4) * 4 / 3; + const delta = mathAbs(endAngle - startAngle); + const len = mathTan(delta / 4) * 4 / 3; const dir = endAngle < startAngle ? -1 : 1; - const c1 = Math.cos(startAngle); - const s1 = Math.sin(startAngle); - const c2 = Math.cos(endAngle); - const s2 = Math.sin(endAngle); + const c1 = mathCos(startAngle); + const s1 = mathSin(startAngle); + const c2 = mathCos(endAngle); + const s2 = mathSin(endAngle); const x1 = c1 * rx + cx; const y1 = s1 * ry + cy; @@ -134,8 +135,8 @@ export function pathToBezierCurves(path: PathProxy) { i += 1; const anticlockwise = !data[i++]; - x1 = Math.cos(startAngle) * rx + cx; - y1 = Math.sin(startAngle) * ry + cy; + x1 = mathCos(startAngle) * rx + cx; + y1 = mathSin(startAngle) * ry + cy; if (isFirst) { // 直接使用 arc 命令 // 第一个命令起点还未定义 @@ -148,14 +149,14 @@ export function pathToBezierCurves(path: PathProxy) { addLine(xi, yi, x1, y1); } - xi = Math.cos(endAngle) * rx + cx; - yi = Math.sin(endAngle) * ry + cy; + xi = mathCos(endAngle) * rx + cx; + yi = mathSin(endAngle) * ry + cy; - const step = (anticlockwise ? -1 : 1) * Math.PI / 2; + const step = (anticlockwise ? -1 : 1) * PI_OVER_2; for (let angle = startAngle; anticlockwise ? angle > endAngle : angle < endAngle; angle += step) { - const nextAngle = anticlockwise ? Math.max(angle + step, endAngle) - : Math.min(angle + step, endAngle); + const nextAngle = anticlockwise ? mathMax(angle + step, endAngle) + : mathMin(angle + step, endAngle); addArc(angle, nextAngle, cx, cy, rx, ry); } @@ -204,7 +205,7 @@ function adpativeBezier( // Determine if curve is straight enough let dx = x3 - x0; let dy = y3 - y0; - const d = Math.sqrt(dx * dx + dy * dy); + const d = mathSqrt(dx * dx + dy * dy); dx /= d; dy /= d; diff --git a/src/tool/dividePath.ts b/src/tool/dividePath.ts index 6c670a894..84b4b59ad 100644 --- a/src/tool/dividePath.ts +++ b/src/tool/dividePath.ts @@ -8,6 +8,7 @@ import Rect from '../graphic/shape/Rect'; import Sector from '../graphic/shape/Sector'; import { pathToPolygons } from './convertPath'; import { clonePath } from './path'; +import { PI2, mathAbs, mathCeil, mathFloor, mathMax, mathSqrt } from '../core/math'; // Default shape dividers // TODO divide polygon by grids. @@ -22,9 +23,9 @@ function getDividingGrids(dimSize: number[], rowDim: number, count: number) { const rowSize = dimSize[rowDim]; const columnSize = dimSize[1 - rowDim]; - const ratio = Math.abs(rowSize / columnSize); - let rowCount = Math.ceil(Math.sqrt(ratio * count)); - let columnCount = Math.floor(count / rowCount); + const ratio = mathAbs(rowSize / columnSize); + let rowCount = mathCeil(mathSqrt(ratio * count)); + let columnCount = mathFloor(count / rowCount); if (columnCount === 0) { columnCount = 1; rowCount = count; @@ -38,7 +39,7 @@ function getDividingGrids(dimSize: number[], rowDim: number, count: number) { // Distribute the remaind grid evenly on each row. const remained = count - currentCount; if (remained > 0) { - // const stride = Math.max(Math.floor(rowCount / remained), 1); + // const stride = mathMax(mathFloor(rowCount / remained), 1); for (let i = 0; i < remained; i++) { grids[i % rowCount] += 1; } @@ -53,11 +54,11 @@ function divideSector(sectorShape: Sector['shape'], count: number, outShapes: Se const r = sectorShape.r; const startAngle = sectorShape.startAngle; const endAngle = sectorShape.endAngle; - const angle = Math.abs(endAngle - startAngle); + const angle = mathAbs(endAngle - startAngle); const arcLen = angle * r; const deltaR = r - r0; - const isAngleRow = arcLen > Math.abs(deltaR); + const isAngleRow = arcLen > mathAbs(deltaR); const grids = getDividingGrids([arcLen, deltaR], isAngleRow ? 0 : 1, count); const rowSize = (isAngleRow ? angle : deltaR) / grids.length; @@ -132,7 +133,7 @@ function lineLineIntersect( const ny = b2y - b1y; const nmCrossProduct = crossProduct2d(nx, ny, mx, my); - if (Math.abs(nmCrossProduct) < 1e-6) { + if (mathAbs(nmCrossProduct) < 1e-6) { return null; } @@ -275,7 +276,7 @@ function binaryDivideRecursive( out.push(shape); } else { - const mid = Math.floor(count / 2); + const mid = mathFloor(count / 2); const sub = divider(shape); binaryDivideRecursive(divider, sub[0], mid, out); binaryDivideRecursive(divider, sub[1], count - mid, out); @@ -325,14 +326,14 @@ export function split( break; case 'circle': divideSector({ - r0: 0, r: shape.r, startAngle: 0, endAngle: Math.PI * 2, + r0: 0, r: shape.r, startAngle: 0, endAngle: PI2, cx: shape.cx, cy: shape.cy } as Sector['shape'], count, outShapes as Sector['shape'][]); OutShapeCtor = Sector; break; default: const m = path.getComputedTransform(); - const scale = m ? Math.sqrt(Math.max(m[0] * m[0] + m[1] * m[1], m[2] * m[2] + m[3] * m[3])) : 1; + const scale = m ? mathSqrt(mathMax(m[0] * m[0] + m[1] * m[1], m[2] * m[2] + m[3] * m[3])) : 1; const polygons = map( pathToPolygons(path.getUpdatedPathProxy(), scale), poly => polygonConvert(poly) @@ -373,7 +374,7 @@ export function split( const selfCount = i === polygonCount - 1 ? left // Use the last piece directly - : Math.ceil(item.area / totalArea * count); + : mathCeil(item.area / totalArea * count); if (selfCount < 0) { continue; diff --git a/src/tool/morphPath.ts b/src/tool/morphPath.ts index c29d33d59..de7bea30a 100644 --- a/src/tool/morphPath.ts +++ b/src/tool/morphPath.ts @@ -11,6 +11,7 @@ import Transformable from '../core/Transformable'; import { ZRenderType } from '../zrender'; import { split } from './dividePath'; import { pathToBezierCurves } from './convertPath'; +import { PI, mathMin, mathMax, mathRound, mathCeil, mathAbs, mathSin, mathCos } from '../core/math'; function alignSubpath(subpath1: number[], subpath2: number[]): [number[], number[]] { const len1 = subpath1.length; @@ -22,12 +23,12 @@ function alignSubpath(subpath1: number[], subpath2: number[]): [number[], number const tmpSegY: number[] = []; const shorterPath = len1 < len2 ? subpath1 : subpath2; - const shorterLen = Math.min(len1, len2); + const shorterLen = mathMin(len1, len2); // Should divide excatly - const diff = Math.abs(len2 - len1) / 6; + const diff = mathAbs(len2 - len1) / 6; const shorterBezierCount = (shorterLen - 2) / 6; // Add `diff` number of beziers - const eachCurveSubDivCount = Math.ceil(diff / shorterBezierCount) + 1; + const eachCurveSubDivCount = mathCeil(diff / shorterBezierCount) + 1; const newSubpath = [shorterPath[0], shorterPath[1]]; let remained = diff; @@ -47,7 +48,7 @@ function alignSubpath(subpath1: number[], subpath2: number[]): [number[], number continue; } - let actualSubDivCount = Math.min(remained, eachCurveSubDivCount - 1) + 1; + let actualSubDivCount = mathMin(remained, eachCurveSubDivCount - 1) + 1; for (let k = 1; k <= actualSubDivCount; k++) { const p = k / actualSubDivCount; @@ -101,7 +102,7 @@ export function alignBezierCurves(array1: number[][], array2: number[][]) { let newArray1 = []; let newArray2 = []; - for (let i = 0; i < Math.max(array1.length, array2.length); i++) { + for (let i = 0; i < mathMax(array1.length, array2.length); i++) { const subpath1 = array1[i]; const subpath2 = array2[i]; @@ -277,8 +278,8 @@ function findBestMorphingRotation( if (searchAngleIteration > 0) { const step = searchAngleRange / searchAngleIteration; for (let angle = -searchAngleRange / 2; angle <= searchAngleRange / 2; angle += step) { - const sa = Math.sin(angle); - const ca = Math.cos(angle); + const sa = mathSin(angle); + const ca = mathCos(angle); let score = 0; for (let k = 0; k < fromSubpathBezier.length; k += 2) { @@ -298,7 +299,7 @@ function findBestMorphingRotation( const dy = newY1 - y0; // Use dot product to have min direction change. - // const d = Math.sqrt(x0 * x0 + y0 * y0); + // const d = mathSqrt(x0 * x0 + y0 * y0); // score += x0 * dx / d + y0 * dy / d; score += dx * dx + dy * dy; } @@ -414,7 +415,7 @@ function prepareMorphPath( saveAndModifyMethod(toPath, 'updateTransform', { replace: updateIdentityTransform }); toPath.transform = null; - const morphingData = findBestMorphingRotation(fromBezierCurves, toBezierCurves, 10, Math.PI); + const morphingData = findBestMorphingRotation(fromBezierCurves, toBezierCurves, 10, PI); const tmpArr: number[] = []; @@ -431,8 +432,8 @@ function prepareMorphPath( const angle = item.rotation * t; const fromCp = item.fromCp; const toCp = item.toCp; - const sa = Math.sin(angle); - const ca = Math.cos(angle); + const sa = mathSin(angle); + const ca = mathCos(angle); lerp(newCp, fromCp, toCp, t); @@ -532,8 +533,8 @@ export function morphPath( // function zOrder(x: number, y: number, minX: number, minY: number, maxX: number, maxY: number) { // // Normalize coords to 0 - 1 // // The transformed into non-negative 15-bit integer range -// x = (maxX === minX) ? 0 : Math.round(32767 * (x - minX) / (maxX - minX)); -// y = (maxY === minY) ? 0 : Math.round(32767 * (y - minY) / (maxY - minY)); +// x = (maxX === minX) ? 0 : mathRound(32767 * (x - minX) / (maxX - minX)); +// y = (maxY === minY) ? 0 : mathRound(32767 * (y - minY) / (maxY - minY)); // x = (x | (x << 8)) & 0x00FF00FF; // x = (x | (x << 4)) & 0x0F0F0F0F; @@ -552,8 +553,8 @@ export function morphPath( // https://jsfiddle.net/pissang/xdnbzg6v/ function hilbert(x: number, y: number, minX: number, minY: number, maxX: number, maxY: number) { const bits = 16; - x = (maxX === minX) ? 0 : Math.round(32767 * (x - minX) / (maxX - minX)); - y = (maxY === minY) ? 0 : Math.round(32767 * (y - minY) / (maxY - minY)); + x = (maxX === minX) ? 0 : mathRound(32767 * (x - minX) / (maxX - minX)); + y = (maxY === minY) ? 0 : mathRound(32767 * (y - minY) / (maxY - minY)); let d = 0; let tmp: number; @@ -596,10 +597,10 @@ function sortPaths(pathList: Path[]): Path[] { const m = path.getComputedTransform(); const x = rect.x + rect.width / 2 + (m ? m[4] : 0); const y = rect.y + rect.height / 2 + (m ? m[5] : 0); - xMin = Math.min(x, xMin); - yMin = Math.min(y, yMin); - xMax = Math.max(x, xMax); - yMax = Math.max(y, yMax); + xMin = mathMin(x, xMin); + yMin = mathMin(y, yMin); + xMax = mathMax(x, xMax); + yMax = mathMax(y, yMax); return [x, y]; }); diff --git a/src/tool/parseSVG.ts b/src/tool/parseSVG.ts index 5d0587042..872e11c2d 100644 --- a/src/tool/parseSVG.ts +++ b/src/tool/parseSVG.ts @@ -20,6 +20,7 @@ import Gradient, { GradientObject } from '../graphic/Gradient'; import TSpan, { TSpanStyleProps } from '../graphic/TSpan'; import { parseXML } from './parseXML'; import * as colorTool from './color'; +import { DEGREE_TO_RADIAN, mathMin, mathTan } from '../core/math'; interface SVGParserOption { @@ -829,7 +830,6 @@ function splitNumberSequence(rawStr: string): string[] { const transformRegex = /(translate|scale|rotate|skewX|skewY|matrix)\(([\-\s0-9\.eE,]*)\)/g; -const DEGREE_TO_ANGLE = Math.PI / 180; function parseTransformAttribute(xmlNode: SVGElement, node: Element): void { let transform = xmlNode.getAttribute('transform'); @@ -856,17 +856,17 @@ function parseTransformAttribute(xmlNode: SVGElement, node: Element): void { break; case 'rotate': // TODO: zrender use different hand in coordinate system. - matrix.rotate(mt, mt, -parseFloat(valueArr[0]) * DEGREE_TO_ANGLE, [ + matrix.rotate(mt, mt, -parseFloat(valueArr[0]) * DEGREE_TO_RADIAN, [ parseFloat(valueArr[1] || '0'), parseFloat(valueArr[2] || '0') ]); break; case 'skewX': - const sx = Math.tan(parseFloat(valueArr[0]) * DEGREE_TO_ANGLE); + const sx = mathTan(parseFloat(valueArr[0]) * DEGREE_TO_RADIAN); matrix.mul(mt, [1, 0, sx, 1, 0, 0], mt); break; case 'skewY': - const sy = Math.tan(parseFloat(valueArr[0]) * DEGREE_TO_ANGLE); + const sy = mathTan(parseFloat(valueArr[0]) * DEGREE_TO_RADIAN); matrix.mul(mt, [1, sy, 0, 1, 0, 0], mt); break; case 'matrix': @@ -945,7 +945,7 @@ export function makeViewBoxTransform(viewBoxRect: RectLike, boundingRect: RectLi } { const scaleX = boundingRect.width / viewBoxRect.width; const scaleY = boundingRect.height / viewBoxRect.height; - const scale = Math.min(scaleX, scaleY); + const scale = mathMin(scaleX, scaleY); // preserveAspectRatio 'xMidYMid' return { diff --git a/src/tool/path.ts b/src/tool/path.ts index 97a0e465d..f04850a5e 100644 --- a/src/tool/path.ts +++ b/src/tool/path.ts @@ -4,6 +4,7 @@ import transformPath from './transformPath'; import { VectorArray } from '../core/vector'; import { MatrixArray } from '../core/matrix'; import { extend } from '../core/util'; +import { PI, PI2, mathSqrt, mathSin, mathCos, mathRound, mathACos } from '../core/math'; // command chars // const cc = [ @@ -11,20 +12,15 @@ import { extend } from '../core/util'; // 'c', 'C', 'q', 'Q', 't', 'T', 's', 'S', 'a', 'A' // ]; -const mathSqrt = Math.sqrt; -const mathSin = Math.sin; -const mathCos = Math.cos; -const PI = Math.PI; - function vMag(v: VectorArray): number { - return Math.sqrt(v[0] * v[0] + v[1] * v[1]); + return mathSqrt(v[0] * v[0] + v[1] * v[1]); }; function vRatio(u: VectorArray, v: VectorArray): number { return (u[0] * v[0] + u[1] * v[1]) / (vMag(u) * vMag(v)); }; function vAngle(u: VectorArray, v: VectorArray): number { return (u[0] * v[1] < u[1] * v[0] ? -1 : 1) - * Math.acos(vRatio(u, v)); + * mathACos(vRatio(u, v)); }; function processArc( @@ -75,9 +71,9 @@ function processArc( } if (dTheta < 0) { - const n = Math.round(dTheta / PI * 1e6) / 1e6; + const n = mathRound(dTheta / PI * 1e6) / 1e6; // Convert to positive - dTheta = PI * 2 + (n % 2) * PI; + dTheta = PI2 + (n % 2) * PI; } path.addData(cmd, cx, cy, rx, ry, theta, dTheta, psi, fs); diff --git a/src/tool/transformPath.ts b/src/tool/transformPath.ts index e6ff2f433..7fde96cea 100644 --- a/src/tool/transformPath.ts +++ b/src/tool/transformPath.ts @@ -1,12 +1,11 @@ import PathProxy from '../core/PathProxy'; import {applyTransform as v2ApplyTransform, VectorArray} from '../core/vector'; import { MatrixArray } from '../core/matrix'; +import { mathSqrt, mathATan2 } from '../core/math'; const CMD = PathProxy.CMD; const points: VectorArray[] = [[], [], []]; -const mathSqrt = Math.sqrt; -const mathAtan2 = Math.atan2; export default function transformPath(path: PathProxy, m: MatrixArray) { if (!m) { @@ -52,7 +51,7 @@ export default function transformPath(path: PathProxy, m: MatrixArray) { const y = m[5]; const sx = mathSqrt(m[0] * m[0] + m[1] * m[1]); const sy = mathSqrt(m[2] * m[2] + m[3] * m[3]); - const angle = mathAtan2(-m[1] / sy, m[0] / sx); + const angle = mathATan2(-m[1] / sy, m[0] / sx); // cx data[i] *= sx; data[i++] += x;