From 0c66f8327a44554344dbd83b0d536f8b2cd4bb26 Mon Sep 17 00:00:00 2001 From: zhouxinyu Date: Fri, 14 Feb 2025 16:34:41 +0800 Subject: [PATCH 1/2] feat: support dynamicTexture --- ...feat-dynamic-texture_2025-02-14-08-32.json | 10 + ...feat-dynamic-texture_2025-02-14-08-32.json | 10 + ...feat-dynamic-texture_2025-02-14-08-32.json | 10 + packages/vrender-core/src/graphic/config.ts | 2 +- packages/vrender-core/src/graphic/symbol.ts | 14 + .../src/interface/graphic/symbol.ts | 1 + .../base-texture-contribution-render.ts | 44 +- packages/vrender-kits/src/index.ts | 1 + .../vrender-kits/src/tools/dynamicTexture.ts | 229 +++++++++ .../src/tools/dynamicTexture/effect.ts | 435 ++++++++++++++++++ .../browser/src/pages/dynamic-texture.ts | 117 +++++ .../__tests__/browser/src/pages/index.ts | 4 + 12 files changed, 875 insertions(+), 2 deletions(-) create mode 100644 common/changes/@visactor/vrender-core/feat-dynamic-texture_2025-02-14-08-32.json create mode 100644 common/changes/@visactor/vrender-kits/feat-dynamic-texture_2025-02-14-08-32.json create mode 100644 common/changes/@visactor/vrender/feat-dynamic-texture_2025-02-14-08-32.json create mode 100644 packages/vrender-kits/src/tools/dynamicTexture.ts create mode 100644 packages/vrender-kits/src/tools/dynamicTexture/effect.ts create mode 100644 packages/vrender/__tests__/browser/src/pages/dynamic-texture.ts diff --git a/common/changes/@visactor/vrender-core/feat-dynamic-texture_2025-02-14-08-32.json b/common/changes/@visactor/vrender-core/feat-dynamic-texture_2025-02-14-08-32.json new file mode 100644 index 000000000..adf56fc41 --- /dev/null +++ b/common/changes/@visactor/vrender-core/feat-dynamic-texture_2025-02-14-08-32.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@visactor/vrender-core", + "comment": "feat: support dynamicTexture", + "type": "none" + } + ], + "packageName": "@visactor/vrender-core" +} \ No newline at end of file diff --git a/common/changes/@visactor/vrender-kits/feat-dynamic-texture_2025-02-14-08-32.json b/common/changes/@visactor/vrender-kits/feat-dynamic-texture_2025-02-14-08-32.json new file mode 100644 index 000000000..5ead0083f --- /dev/null +++ b/common/changes/@visactor/vrender-kits/feat-dynamic-texture_2025-02-14-08-32.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@visactor/vrender-kits", + "comment": "feat: support dynamicTexture", + "type": "none" + } + ], + "packageName": "@visactor/vrender-kits" +} \ No newline at end of file diff --git a/common/changes/@visactor/vrender/feat-dynamic-texture_2025-02-14-08-32.json b/common/changes/@visactor/vrender/feat-dynamic-texture_2025-02-14-08-32.json new file mode 100644 index 000000000..598ce1c03 --- /dev/null +++ b/common/changes/@visactor/vrender/feat-dynamic-texture_2025-02-14-08-32.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@visactor/vrender", + "comment": "", + "type": "none" + } + ], + "packageName": "@visactor/vrender" +} \ No newline at end of file diff --git a/packages/vrender-core/src/graphic/config.ts b/packages/vrender-core/src/graphic/config.ts index 54537a8b5..edfd5638a 100644 --- a/packages/vrender-core/src/graphic/config.ts +++ b/packages/vrender-core/src/graphic/config.ts @@ -130,7 +130,7 @@ export const DefaultStyle: IGraphicStyle = { opacity: 1, background: null, autoAnimateTexture: false, - textureRatio: 0, + textureRatio: 1, textureOptions: null, backgroundOpacity: 1, backgroundCornerRadius: 0, diff --git a/packages/vrender-core/src/graphic/symbol.ts b/packages/vrender-core/src/graphic/symbol.ts index 97738e288..649faae54 100644 --- a/packages/vrender-core/src/graphic/symbol.ts +++ b/packages/vrender-core/src/graphic/symbol.ts @@ -44,6 +44,20 @@ export class Symbol extends Graphic implements ISymbol return this._parsedPath as ISymbolClass; } + getParsedPath2D(x = 0, y = 0, size = 1): Path2D | null { + let path: Path2D | null = null; + try { + path = new Path2D(); + } catch (err) { + return null; + } + const parsedPath = this.getParsedPath(); + if (!parsedPath) { + return null; + } + parsedPath.draw(path, size, x, y); + } + isValid(): boolean { return super.isValid() && this._isValid(); } diff --git a/packages/vrender-core/src/interface/graphic/symbol.ts b/packages/vrender-core/src/interface/graphic/symbol.ts index 20c15f384..659de5bf1 100644 --- a/packages/vrender-core/src/interface/graphic/symbol.ts +++ b/packages/vrender-core/src/interface/graphic/symbol.ts @@ -12,6 +12,7 @@ export type ISymbolGraphicAttribute = Partial & Partial { getParsedPath: () => ISymbolClass; + getParsedPath2D: (x?: number, y?: number, size?: number) => Path2D | null; } export type SymbolType = diff --git a/packages/vrender-core/src/render/contributions/render/contributions/base-texture-contribution-render.ts b/packages/vrender-core/src/render/contributions/render/contributions/base-texture-contribution-render.ts index 413a701d6..252153623 100644 --- a/packages/vrender-core/src/render/contributions/render/contributions/base-texture-contribution-render.ts +++ b/packages/vrender-core/src/render/contributions/render/contributions/base-texture-contribution-render.ts @@ -1,5 +1,6 @@ import { canvasAllocate } from '../../../../allocator/canvas-allocate'; import { BaseRenderContributionTime } from '../../../../common/enums'; +import { createSymbol } from '../../../../graphic'; import type { IBaseRenderContribution, IContext2d, @@ -7,6 +8,7 @@ import type { IGraphic, IGraphicAttribute, IStage, + ISymbol, IThemeAttribute } from '../../../../interface'; import { pi2 } from '@visactor/vutils'; @@ -68,6 +70,7 @@ export class DefaultBaseTextureRenderContribution implements IBaseRenderContribu useStyle: boolean = true; textureMap?: Map; order: number = 10; + _tempSymbolGraphic: ISymbol | null = null; createCommonPattern( size: number, @@ -274,7 +277,46 @@ export class DefaultBaseTextureRenderContribution implements IBaseRenderContribu } } - if (pattern) { + if (textureOptions && textureOptions.dynamicTexture) { + // 动态纹理 + context.save(); + context.setCommonStyle(graphic, graphic.attribute, x, y, graphicAttribute); + context.clip(); + const { gridConfig = {} } = textureOptions; + const b = graphic.AABBBounds; + const width = b.width(); + const height = b.height(); + const padding = texturePadding; + const cellSize = textureSize; + const gridColumns = gridConfig.columns ? gridConfig.columns : Math.ceil(width / cellSize); + const gridRows = gridConfig.rows ? gridConfig.rows : Math.ceil(height / cellSize); + const gutterColumn = gridConfig.gutterColumn ? gridConfig.gutterColumn : padding * 2; + const gutterRow = gridConfig.gutterRow ? gridConfig.gutterRow : padding * 2; + if (!this._tempSymbolGraphic) { + this._tempSymbolGraphic = createSymbol({}); + } + const sizeW = gridConfig.columns ? width / gridConfig.columns : cellSize; + const sizeH = gridConfig.rows ? height / gridConfig.rows : cellSize; + this._tempSymbolGraphic.setAttributes({ + size: [sizeW - gutterColumn, sizeH - gutterRow], + symbolType: texture + }); + const parsedPath = this._tempSymbolGraphic.getParsedPath(); + for (let i = 0; i < gridRows; i++) { + for (let j = 0; j < gridColumns; j++) { + const _x = x + cellSize / 2 + j * cellSize; + const _y = y + cellSize / 2 + i * cellSize; + context.beginPath(); + if (parsedPath.draw(context, Math.min(sizeW - gutterColumn, sizeH - gutterRow), _x, _y, 0) === false) { + context.closePath(); + } + context.fillStyle = textureColor; + textureOptions.dynamicTexture(context, i, j, gridRows, gridColumns, textureRatio, graphic); + } + } + + context.restore(); + } else if (pattern) { context.highPerformanceSave(); context.setCommonStyle(graphic, graphic.attribute, x, y, graphicAttribute); context.fillStyle = pattern; diff --git a/packages/vrender-kits/src/index.ts b/packages/vrender-kits/src/index.ts index 82848e655..13eeab914 100644 --- a/packages/vrender-kits/src/index.ts +++ b/packages/vrender-kits/src/index.ts @@ -77,5 +77,6 @@ export * from './register/register-shadowRoot'; export * from './register/register-symbol'; export * from './register/register-text'; export * from './register/register-wraptext'; +export * from './tools/dynamicTexture/effect'; // export const canvasModuleLoader = _canvasModuleLoader; // export { nodeLoader } from './node-bind'; // nodeLoader只在node入口暴露 diff --git a/packages/vrender-kits/src/tools/dynamicTexture.ts b/packages/vrender-kits/src/tools/dynamicTexture.ts new file mode 100644 index 000000000..2176b35d1 --- /dev/null +++ b/packages/vrender-kits/src/tools/dynamicTexture.ts @@ -0,0 +1,229 @@ +// import type { IContext2d, IGraphic } from '@visactor/vrender-core'; + +// export function randomOpacity( +// ctx: IContext2d, +// row: number, +// column: number, +// rowCount: number, +// columnCount: number, +// ratio: number, +// graphic: IGraphic +// ) { +// if (!graphic.dynamicTextureCache) { +// graphic.dynamicTextureCache = new Array(rowCount * columnCount).fill(0).map(item => Math.random() * 2 * Math.PI); +// } +// const targetRandomValue = graphic.dynamicTextureCache[row * columnCount + column]; +// const _r = (Math.sin(ratio * 2 * Math.PI + targetRandomValue) + 1) / 2; +// ctx.globalAlpha = _r; +// ctx.fill(); +// } + +// // 从左到右的列式渐变 +// export function columnLeftToRight( +// ctx: IContext2d, +// row: number, +// column: number, +// rowCount: number, +// columnCount: number, +// ratio: number, +// graphic: IGraphic +// ) { +// // 根据列号计算延迟 +// const delay = column / columnCount; +// // 使用连续的sin函数,不需要max(0,ratio-delay)的截断 +// const _r = (Math.sin(ratio * 2 * Math.PI - delay * 2 * Math.PI) + 1) / 2; +// ctx.globalAlpha = _r; +// ctx.fill(); +// } + +// // 从右到左的列式渐变 +// export function columnRightToLeft( +// ctx: IContext2d, +// row: number, +// column: number, +// rowCount: number, +// columnCount: number, +// ratio: number, +// graphic: IGraphic +// ) { +// const delay = (columnCount - 1 - column) / columnCount; +// const _r = (Math.sin(ratio * 2 * Math.PI - delay * 2 * Math.PI) + 1) / 2; +// ctx.globalAlpha = _r; +// ctx.fill(); +// } + +// // 从上到下的行式渐变 +// export function rowTopToBottom( +// ctx: IContext2d, +// row: number, +// column: number, +// rowCount: number, +// columnCount: number, +// ratio: number, +// graphic: IGraphic +// ) { +// const delay = row / rowCount; +// const _r = (Math.sin(ratio * 2 * Math.PI - delay * 2 * Math.PI) + 1) / 2; +// ctx.globalAlpha = _r; +// ctx.fill(); +// } + +// // 从下到上的行式渐变 +// export function rowBottomToTop( +// ctx: IContext2d, +// row: number, +// column: number, +// rowCount: number, +// columnCount: number, +// ratio: number, +// graphic: IGraphic +// ) { +// const delay = (rowCount - 1 - row) / rowCount; +// const _r = (Math.sin(ratio * 2 * Math.PI - delay * 2 * Math.PI) + 1) / 2; +// ctx.globalAlpha = _r; +// ctx.fill(); +// } + +// // 从中心向两边的对角线渐变 +// export function diagonalCenterToEdge( +// ctx: IContext2d, +// row: number, +// column: number, +// rowCount: number, +// columnCount: number, +// ratio: number, +// graphic: IGraphic +// ) { +// const centerRow = rowCount / 2; +// const centerCol = columnCount / 2; +// const distance = Math.sqrt( +// Math.pow((row - centerRow) / rowCount, 2) + Math.pow((column - centerCol) / columnCount, 2) +// ); +// const _r = (Math.sin(ratio * 2 * Math.PI - distance * 2 * Math.PI) + 1) / 2; +// ctx.globalAlpha = _r; +// ctx.fill(); +// } + +// // 从左上角到右下角的对角线渐变 +// export function diagonalTopLeftToBottomRight( +// ctx: IContext2d, +// row: number, +// column: number, +// rowCount: number, +// columnCount: number, +// ratio: number, +// graphic: IGraphic +// ) { +// const delay = (row / rowCount + column / columnCount) / 2; +// const _r = (Math.sin(ratio * 2 * Math.PI - delay * 2 * Math.PI) + 1) / 2; +// ctx.globalAlpha = _r; +// ctx.fill(); +// } + +// // 旋转扫描效果 +// export function rotationScan( +// ctx: IContext2d, +// row: number, +// column: number, +// rowCount: number, +// columnCount: number, +// ratio: number, +// graphic: IGraphic +// ) { +// // 计算当前点相对于中心点的角度 +// const centerRow = rowCount / 2; +// const centerCol = columnCount / 2; +// const angle = Math.atan2(row - centerRow, column - centerCol); +// // 将角度归一化到 [0, 2π] +// const normalizedAngle = angle < 0 ? angle + 2 * Math.PI : angle; +// // 计算扫描延迟 +// const delay = normalizedAngle / (2 * Math.PI); +// const _r = (Math.sin(ratio * 2 * Math.PI - delay * 2 * Math.PI) + 1) / 2; +// ctx.globalAlpha = _r; +// ctx.fill(); +// } + +// // 波纹扩散效果 +// export function rippleEffect( +// ctx: IContext2d, +// row: number, +// column: number, +// rowCount: number, +// columnCount: number, +// ratio: number, +// graphic: IGraphic +// ) { +// const centerRow = rowCount / 2; +// const centerCol = columnCount / 2; +// // 计算到中心的距离 +// const distance = Math.sqrt(Math.pow(row - centerRow, 2) + Math.pow(column - centerCol, 2)); +// // 归一化距离 +// const normalizedDistance = distance / Math.sqrt(Math.pow(rowCount / 2, 2) + Math.pow(columnCount / 2, 2)); +// // 创建多个波纹 +// const waves = 3; +// const _r = (Math.sin(ratio * 2 * Math.PI * waves - normalizedDistance * 2 * Math.PI) + 1) / 2; +// ctx.globalAlpha = _r; +// ctx.fill(); +// } + +// // 蛇形波动效果 +// export function snakeWave( +// ctx: IContext2d, +// row: number, +// column: number, +// rowCount: number, +// columnCount: number, +// ratio: number, +// graphic: IGraphic +// ) { +// // 使用行和列的位置创建蛇形路径 +// const delay = ((row + column) % (rowCount + columnCount)) / (rowCount + columnCount); +// const _r = (Math.sin(ratio * 2 * Math.PI - delay * 4 * Math.PI) + 1) / 2; +// ctx.globalAlpha = _r; +// ctx.fill(); +// } + +// // 交错波纹效果 +// export function alternatingWave( +// ctx: IContext2d, +// row: number, +// column: number, +// rowCount: number, +// columnCount: number, +// ratio: number, +// graphic: IGraphic +// ) { +// // 行和列的交错波纹 +// const rowPhase = row / rowCount; +// const colPhase = column / columnCount; +// const _r = +// (Math.sin(ratio * 2 * Math.PI - rowPhase * 2 * Math.PI) * Math.sin(ratio * 2 * Math.PI - colPhase * 2 * Math.PI) + +// 1) / +// 2; +// ctx.globalAlpha = _r; +// ctx.fill(); +// } + +// // 螺旋效果 +// export function spiralEffect( +// ctx: IContext2d, +// row: number, +// column: number, +// rowCount: number, +// columnCount: number, +// ratio: number, +// graphic: IGraphic +// ) { +// const centerRow = rowCount / 2; +// const centerCol = columnCount / 2; +// // 计算到中心的距离和角度 +// const distance = Math.sqrt(Math.pow(row - centerRow, 2) + Math.pow(column - centerCol, 2)); +// const angle = Math.atan2(row - centerRow, column - centerCol); +// // 归一化距离 +// const normalizedDistance = distance / Math.sqrt(Math.pow(rowCount / 2, 2) + Math.pow(columnCount / 2, 2)); +// // 组合距离和角度创建螺旋效果 +// const delay = (normalizedDistance + angle / (2 * Math.PI)) / 2; +// const _r = (Math.sin(ratio * 2 * Math.PI - delay * 4 * Math.PI) + 1) / 2; +// ctx.globalAlpha = _r; +// ctx.fill(); +// } diff --git a/packages/vrender-kits/src/tools/dynamicTexture/effect.ts b/packages/vrender-kits/src/tools/dynamicTexture/effect.ts new file mode 100644 index 000000000..60ae92e1d --- /dev/null +++ b/packages/vrender-kits/src/tools/dynamicTexture/effect.ts @@ -0,0 +1,435 @@ +import type { IContext2d, IGraphic } from '@visactor/vrender-core'; + +export function randomOpacity( + ctx: IContext2d, + row: number, + column: number, + rowCount: number, + columnCount: number, + ratio: number, + graphic: IGraphic +): number { + if (!graphic.dynamicTextureCache) { + graphic.dynamicTextureCache = new Array(rowCount * columnCount).fill(0).map(item => Math.random() * 2 * Math.PI); + } + const targetRandomValue = graphic.dynamicTextureCache[row * columnCount + column]; + const _r = (Math.sin(ratio * 2 * Math.PI + targetRandomValue) + 1) / 2; + // ctx.globalAlpha = _r; + // ctx.fill(); + return _r; +} + +// 从左到右的列式渐变 +export function columnLeftToRight( + ctx: IContext2d, + row: number, + column: number, + rowCount: number, + columnCount: number, + ratio: number, + graphic: IGraphic +): number { + // 根据列号计算延迟 + const delay = column / columnCount; + // 使用连续的sin函数,不需要max(0,ratio-delay)的截断 + const _r = (Math.sin(ratio * 2 * Math.PI - delay * 2 * Math.PI) + 1) / 2; + // ctx.globalAlpha = _r; + // ctx.fill(); + return _r; +} + +// 从右到左的列式渐变 +export function columnRightToLeft( + ctx: IContext2d, + row: number, + column: number, + rowCount: number, + columnCount: number, + ratio: number, + graphic: IGraphic +): number { + const delay = (columnCount - 1 - column) / columnCount; + const _r = (Math.sin(ratio * 2 * Math.PI - delay * 2 * Math.PI) + 1) / 2; + // ctx.globalAlpha = _r; + // ctx.fill(); + return _r; +} + +// 从上到下的行式渐变 +export function rowTopToBottom( + ctx: IContext2d, + row: number, + column: number, + rowCount: number, + columnCount: number, + ratio: number, + graphic: IGraphic +): number { + const delay = row / rowCount; + const _r = (Math.sin(ratio * 2 * Math.PI - delay * 2 * Math.PI) + 1) / 2; + // ctx.globalAlpha = _r; + // ctx.fill(); + return _r; +} + +// 从下到上的行式渐变 +export function rowBottomToTop( + ctx: IContext2d, + row: number, + column: number, + rowCount: number, + columnCount: number, + ratio: number, + graphic: IGraphic +): number { + const delay = (rowCount - 1 - row) / rowCount; + const _r = (Math.sin(ratio * 2 * Math.PI - delay * 2 * Math.PI) + 1) / 2; + // ctx.globalAlpha = _r; + // ctx.fill(); + return _r; +} + +// 从中心向两边的对角线渐变 +export function diagonalCenterToEdge( + ctx: IContext2d, + row: number, + column: number, + rowCount: number, + columnCount: number, + ratio: number, + graphic: IGraphic +): number { + const centerRow = rowCount / 2; + const centerCol = columnCount / 2; + const distance = Math.sqrt( + Math.pow((row - centerRow) / rowCount, 2) + Math.pow((column - centerCol) / columnCount, 2) + ); + const _r = (Math.sin(ratio * 2 * Math.PI - distance * 2 * Math.PI) + 1) / 2; + // ctx.globalAlpha = _r; + // ctx.fill(); + return _r; +} + +// 从左上角到右下角的对角线渐变 +export function diagonalTopLeftToBottomRight( + ctx: IContext2d, + row: number, + column: number, + rowCount: number, + columnCount: number, + ratio: number, + graphic: IGraphic +): number { + const delay = (row / rowCount + column / columnCount) / 2; + const _r = (Math.sin(ratio * 2 * Math.PI - delay * 2 * Math.PI) + 1) / 2; + // ctx.globalAlpha = _r; + // ctx.fill(); + return _r; +} + +// 旋转扫描效果 +export function rotationScan( + ctx: IContext2d, + row: number, + column: number, + rowCount: number, + columnCount: number, + ratio: number, + graphic: IGraphic +): number { + // 计算当前点相对于中心点的角度 + const centerRow = rowCount / 2; + const centerCol = columnCount / 2; + const angle = Math.atan2(row - centerRow, column - centerCol); + // 将角度归一化到 [0, 2π] + const normalizedAngle = angle < 0 ? angle + 2 * Math.PI : angle; + // 计算扫描延迟 + const delay = normalizedAngle / (2 * Math.PI); + const _r = (Math.sin(ratio * 2 * Math.PI - delay * 2 * Math.PI) + 1) / 2; + // ctx.globalAlpha = _r; + // ctx.fill(); + return _r; +} + +// 波纹扩散效果 +export function rippleEffect( + ctx: IContext2d, + row: number, + column: number, + rowCount: number, + columnCount: number, + ratio: number, + graphic: IGraphic +): number { + const centerRow = rowCount / 2; + const centerCol = columnCount / 2; + // 计算到中心的距离 + const distance = Math.sqrt(Math.pow(row - centerRow, 2) + Math.pow(column - centerCol, 2)); + // 归一化距离 + const normalizedDistance = distance / Math.sqrt(Math.pow(rowCount / 2, 2) + Math.pow(columnCount / 2, 2)); + // 创建多个波纹 + const waves = 3; + const _r = (Math.sin(ratio * 2 * Math.PI * waves - normalizedDistance * 2 * Math.PI) + 1) / 2; + // ctx.globalAlpha = _r; + // ctx.fill(); + return _r; +} + +// 蛇形波动效果 +export function snakeWave( + ctx: IContext2d, + row: number, + column: number, + rowCount: number, + columnCount: number, + ratio: number, + graphic: IGraphic +): number { + // 使用行和列的位置创建蛇形路径 + const delay = ((row + column) % (rowCount + columnCount)) / (rowCount + columnCount); + const _r = (Math.sin(ratio * 2 * Math.PI - delay * 4 * Math.PI) + 1) / 2; + // ctx.globalAlpha = _r; + // ctx.fill(); + return _r; +} + +// 交错波纹效果 +export function alternatingWave( + ctx: IContext2d, + row: number, + column: number, + rowCount: number, + columnCount: number, + ratio: number, + graphic: IGraphic +): number { + // 行和列的交错波纹 + const rowPhase = row / rowCount; + const colPhase = column / columnCount; + const _r = + (Math.sin(ratio * 2 * Math.PI - rowPhase * 2 * Math.PI) * Math.sin(ratio * 2 * Math.PI - colPhase * 2 * Math.PI) + + 1) / + 2; + // ctx.globalAlpha = _r; + // ctx.fill(); + return _r; +} + +// 螺旋效果 +export function spiralEffect( + ctx: IContext2d, + row: number, + column: number, + rowCount: number, + columnCount: number, + ratio: number, + graphic: IGraphic +): number { + const centerRow = rowCount / 2; + const centerCol = columnCount / 2; + // 计算到中心的距离和角度 + const distance = Math.sqrt(Math.pow(row - centerRow, 2) + Math.pow(column - centerCol, 2)); + const angle = Math.atan2(row - centerRow, column - centerCol); + // 归一化距离 + const normalizedDistance = distance / Math.sqrt(Math.pow(rowCount / 2, 2) + Math.pow(columnCount / 2, 2)); + // 组合距离和角度创建螺旋效果 + const delay = (normalizedDistance + angle / (2 * Math.PI)) / 2; + const _r = (Math.sin(ratio * 2 * Math.PI - delay * 4 * Math.PI) + 1) / 2; + // ctx.globalAlpha = _r; + // ctx.fill(); + return _r; +} + +// 从两边向中间的列式渐变 +export function columnCenterToEdge( + ctx: IContext2d, + row: number, + column: number, + rowCount: number, + columnCount: number, + ratio: number, + graphic: IGraphic +): number { + const centerCol = columnCount / 2; + // 计算到中心的距离,并归一化到[0,1]区间 + const distance = Math.abs(column - centerCol) / centerCol; + const _r = (Math.sin(ratio * 2 * Math.PI - distance * 2 * Math.PI) + 1) / 2; + return _r; +} + +// 从中间向两边的列式渐变 +export function columnEdgeToCenter( + ctx: IContext2d, + row: number, + column: number, + rowCount: number, + columnCount: number, + ratio: number, + graphic: IGraphic +): number { + const centerCol = columnCount / 2; + // 计算到中心的距离,并归一化到[0,1]区间,然后用1减去它来反转延迟 + const distance = 1 - Math.abs(column - centerCol) / centerCol; + const _r = (Math.sin(ratio * 2 * Math.PI - distance * 2 * Math.PI) + 1) / 2; + return _r; +} + +// 从上下向中间的行式渐变 +export function rowCenterToEdge( + ctx: IContext2d, + row: number, + column: number, + rowCount: number, + columnCount: number, + ratio: number, + graphic: IGraphic +): number { + const centerRow = rowCount / 2; + // 计算到中心的距离,并归一化到[0,1]区间 + const distance = Math.abs(row - centerRow) / centerRow; + const _r = (Math.sin(ratio * 2 * Math.PI - distance * 2 * Math.PI) + 1) / 2; + return _r; +} + +// 从中间向上下的行式渐变 +export function rowEdgeToCenter( + ctx: IContext2d, + row: number, + column: number, + rowCount: number, + columnCount: number, + ratio: number, + graphic: IGraphic +): number { + const centerRow = rowCount / 2; + // 计算到中心的距离,并归一化到[0,1]区间,然后用1减去它来反转延迟 + const distance = 1 - Math.abs(row - centerRow) / centerRow; + const _r = (Math.sin(ratio * 2 * Math.PI - distance * 2 * Math.PI) + 1) / 2; + return _r; +} + +// 从四个角向中心的渐变 +export function cornerToCenter( + ctx: IContext2d, + row: number, + column: number, + rowCount: number, + columnCount: number, + ratio: number, + graphic: IGraphic +): number { + const centerRow = rowCount / 2; + const centerCol = columnCount / 2; + // 计算到中心的距离,使用欧几里得距离 + const distance = Math.sqrt( + Math.pow((row - centerRow) / centerRow, 2) + Math.pow((column - centerCol) / centerCol, 2) + ); + // 归一化到[0,1]区间 + const normalizedDistance = Math.min(distance, 1); + const _r = (Math.sin(ratio * 2 * Math.PI - normalizedDistance * 2 * Math.PI) + 1) / 2; + return _r; +} + +// 从中心向四个角的渐变 +export function centerToCorner( + ctx: IContext2d, + row: number, + column: number, + rowCount: number, + columnCount: number, + ratio: number, + graphic: IGraphic +): number { + const centerRow = rowCount / 2; + const centerCol = columnCount / 2; + // 计算到中心的距离,使用欧几里得距离 + const distance = Math.sqrt( + Math.pow((row - centerRow) / centerRow, 2) + Math.pow((column - centerCol) / centerCol, 2) + ); + // 归一化到[0,1]区间并反转 + const normalizedDistance = 1 - Math.min(distance, 1); + const _r = (Math.sin(ratio * 2 * Math.PI - normalizedDistance * 2 * Math.PI) + 1) / 2; + return _r; +} + +// 脉冲波纹效果 +export function pulseWave( + ctx: IContext2d, + row: number, + column: number, + rowCount: number, + columnCount: number, + ratio: number, + graphic: IGraphic +): number { + const centerRow = rowCount / 2; + const centerCol = columnCount / 2; + + // 计算到中心的距离 + const distance = Math.sqrt( + Math.pow((row - centerRow) / centerRow, 2) + Math.pow((column - centerCol) / centerCol, 2) + ); + const normalizedDistance = Math.min(distance, 1); + + // 创建多个波纹 + const waves = 3; + const wavePhase = ratio * 2 * Math.PI * waves; + + // 添加距离衰减 + const decay = Math.max(0, 1 - normalizedDistance); + + // 组合波纹和衰减效果 + const wave = Math.sin(wavePhase - normalizedDistance * 4 * Math.PI); + const _r = ((wave + 1) / 2) * (decay * 0.7 + 0.3); + + return _r; +} + +// 粒子动画效果 +export function particleEffect( + ctx: IContext2d, + row: number, + column: number, + rowCount: number, + columnCount: number, + ratio: number, + graphic: IGraphic +): number { + // 初始化随机种子 + if (!graphic.dynamicTextureCache) { + graphic.dynamicTextureCache = { + phases: new Array(rowCount * columnCount).fill(0).map(() => Math.random() * 2 * Math.PI), + speeds: new Array(rowCount * columnCount).fill(0).map(() => 0.5 + Math.random() * 0.5), + directions: new Array(rowCount * columnCount).fill(0).map(() => Math.random() * 2 * Math.PI) + }; + } + + const index = row * columnCount + column; + const phase = graphic.dynamicTextureCache.phases[index]; + const speed = graphic.dynamicTextureCache.speeds[index]; + const direction = graphic.dynamicTextureCache.directions[index]; + + // 计算到中心的距离 + const centerRow = rowCount / 2; + const centerCol = columnCount / 2; + const distance = Math.sqrt( + Math.pow((row - centerRow) / centerRow, 2) + Math.pow((column - centerCol) / centerCol, 2) + ); + const normalizedDistance = Math.min(distance, 1); + + // 扩散阶段:粒子随机运动 + const scatterRatio = (ratio - 0.4) / 0.6; + + // 使用相位和方向创建随机运动 + const movement = Math.sin(scatterRatio * speed * 8 * Math.PI + phase + direction * scatterRatio); + + // 添加距离影响 + const distanceEffect = Math.cos(normalizedDistance * Math.PI + scatterRatio * Math.PI); + + // 添加衰减效果 + const decay = Math.max(0, 1 - scatterRatio * 1.2); + + // 组合所有效果 + const _r = ((movement + 1) / 2) * decay * (0.3 + 0.7 * distanceEffect); + return Math.max(0, Math.min(1, _r)); +} diff --git a/packages/vrender/__tests__/browser/src/pages/dynamic-texture.ts b/packages/vrender/__tests__/browser/src/pages/dynamic-texture.ts new file mode 100644 index 000000000..2206cb362 --- /dev/null +++ b/packages/vrender/__tests__/browser/src/pages/dynamic-texture.ts @@ -0,0 +1,117 @@ +import { createStage, createRect, IGraphic, IContext2d } from '@visactor/vrender'; +import { + randomOpacity, + columnLeftToRight, + columnRightToLeft, + rowTopToBottom, + rowBottomToTop, + diagonalCenterToEdge, + diagonalTopLeftToBottomRight, + rotationScan, + rippleEffect, + snakeWave, + alternatingWave, + spiralEffect, + columnEdgeToCenter, + columnCenterToEdge, + rowEdgeToCenter, + rowCenterToEdge, + cornerToCenter, + centerToCorner, + pulseWave, + particleEffect +} from '@visactor/vrender-kits'; +import { addShapesToStage, colorPools } from '../utils'; + +// container.load(roughModule); +export const page = () => { + const graphics: IGraphic[] = []; + const effects = [ + randomOpacity, + columnLeftToRight, + columnRightToLeft, + rowTopToBottom, + rowBottomToTop, + diagonalCenterToEdge, + diagonalTopLeftToBottomRight, + rotationScan, + rippleEffect, + snakeWave, + alternatingWave, + spiralEffect, + columnEdgeToCenter, + columnCenterToEdge, + rowEdgeToCenter, + rowCenterToEdge, + cornerToCenter, + centerToCorner, + pulseWave, + particleEffect + ]; + const symbolTypeList = [ + 'circle', + 'cross', + 'diamond', + 'square', + 'arrow', + 'arrow2Left', + 'arrow2Right', + 'arrow2Up', + 'arrow2Down', + 'wedge', + 'thinTriangle', + 'triangle', + 'triangleUp', + 'triangleDown', + 'triangleRight', + 'triangleLeft', + 'star', + 'wye', + 'rect', + 'arrowLeft', + 'arrowRight', + 'rectRound' + ]; + effects.forEach((item, index) => { + // 4行5列 + graphics.push( + createRect({ + width: 200, + height: 200, + x: 200 * (index % 5), + y: 200 * Math.floor(index / 5), + fill: colorPools[index % colorPools.length], + texture: symbolTypeList[index % symbolTypeList.length], + textureSize: 20, + texturePadding: 2, + textureRatio: 0, + textureColor: 'orange', + textureOptions: { + dynamicTexture: ( + ctx: IContext2d, + row: number, + column: number, + rowCount: number, + columnCount: number, + ratio: number, + graphic: IGraphic + ) => { + const _r = effects[index](ctx, row, column, rowCount, columnCount, ratio, graphic); + ctx.globalAlpha = _r; + ctx.fill(); + } + } + }) + ); + }); + + const stage = createStage({ + canvas: 'main', + autoRender: true + }); + stage.render(); + graphics.forEach(g => { + stage.defaultLayer.add(g); + g.animate().to({ textureRatio: 1 }, 2000, 'linear').loop(Infinity); + }); +}; diff --git a/packages/vrender/__tests__/browser/src/pages/index.ts b/packages/vrender/__tests__/browser/src/pages/index.ts index 08a1dcf6b..df72142b3 100644 --- a/packages/vrender/__tests__/browser/src/pages/index.ts +++ b/packages/vrender/__tests__/browser/src/pages/index.ts @@ -183,6 +183,10 @@ export const pages = [ name: 'jsx', path: 'jsx' }, + { + name: 'dynamic-texture', + path: 'dynamic-texture' + }, { name: 'html元素', path: 'html' From db73fb2bce097f2b1db0a20bbafbc4dc50136983 Mon Sep 17 00:00:00 2001 From: zhouxinyu Date: Mon, 17 Feb 2025 17:24:10 +0800 Subject: [PATCH 2/2] feat: enhance the effect of dynamic texture --- .../src/tools/dynamicTexture/effect.ts | 205 +++++++++--------- .../browser/src/pages/dynamic-texture.ts | 6 +- .../__tests__/browser/src/pages/vchart.ts | 126 ++++------- 3 files changed, 158 insertions(+), 179 deletions(-) diff --git a/packages/vrender-kits/src/tools/dynamicTexture/effect.ts b/packages/vrender-kits/src/tools/dynamicTexture/effect.ts index 60ae92e1d..b2d3d5a30 100644 --- a/packages/vrender-kits/src/tools/dynamicTexture/effect.ts +++ b/packages/vrender-kits/src/tools/dynamicTexture/effect.ts @@ -7,16 +7,17 @@ export function randomOpacity( rowCount: number, columnCount: number, ratio: number, - graphic: IGraphic + graphic: IGraphic, + minRatio: number = 0, // 最小ratio值,默认为0 + amplitude: number = 1 // 变化幅度,默认为1 ): number { if (!graphic.dynamicTextureCache) { graphic.dynamicTextureCache = new Array(rowCount * columnCount).fill(0).map(item => Math.random() * 2 * Math.PI); } const targetRandomValue = graphic.dynamicTextureCache[row * columnCount + column]; - const _r = (Math.sin(ratio * 2 * Math.PI + targetRandomValue) + 1) / 2; - // ctx.globalAlpha = _r; - // ctx.fill(); - return _r; + // 调整sin函数的振幅,并将结果映射到[minRatio, minRatio + amplitude]范围 + const _r = minRatio + (amplitude * (Math.sin(ratio * 2 * Math.PI + targetRandomValue) + 1)) / 2; + return Math.min(1, Math.max(0, _r)); // 确保返回值在[0,1]范围内 } // 从左到右的列式渐变 @@ -27,15 +28,13 @@ export function columnLeftToRight( rowCount: number, columnCount: number, ratio: number, - graphic: IGraphic + graphic: IGraphic, + minRatio: number = 0, + amplitude: number = 1 ): number { - // 根据列号计算延迟 const delay = column / columnCount; - // 使用连续的sin函数,不需要max(0,ratio-delay)的截断 - const _r = (Math.sin(ratio * 2 * Math.PI - delay * 2 * Math.PI) + 1) / 2; - // ctx.globalAlpha = _r; - // ctx.fill(); - return _r; + const _r = minRatio + (amplitude * (Math.sin(ratio * 2 * Math.PI - delay * 2 * Math.PI) + 1)) / 2; + return Math.min(1, Math.max(0, _r)); } // 从右到左的列式渐变 @@ -46,13 +45,13 @@ export function columnRightToLeft( rowCount: number, columnCount: number, ratio: number, - graphic: IGraphic + graphic: IGraphic, + minRatio: number = 0, + amplitude: number = 1 ): number { const delay = (columnCount - 1 - column) / columnCount; - const _r = (Math.sin(ratio * 2 * Math.PI - delay * 2 * Math.PI) + 1) / 2; - // ctx.globalAlpha = _r; - // ctx.fill(); - return _r; + const _r = minRatio + (amplitude * (Math.sin(ratio * 2 * Math.PI - delay * 2 * Math.PI) + 1)) / 2; + return Math.min(1, Math.max(0, _r)); } // 从上到下的行式渐变 @@ -63,13 +62,13 @@ export function rowTopToBottom( rowCount: number, columnCount: number, ratio: number, - graphic: IGraphic + graphic: IGraphic, + minRatio: number = 0, + amplitude: number = 1 ): number { const delay = row / rowCount; - const _r = (Math.sin(ratio * 2 * Math.PI - delay * 2 * Math.PI) + 1) / 2; - // ctx.globalAlpha = _r; - // ctx.fill(); - return _r; + const _r = minRatio + (amplitude * (Math.sin(ratio * 2 * Math.PI - delay * 2 * Math.PI) + 1)) / 2; + return Math.min(1, Math.max(0, _r)); } // 从下到上的行式渐变 @@ -80,13 +79,13 @@ export function rowBottomToTop( rowCount: number, columnCount: number, ratio: number, - graphic: IGraphic + graphic: IGraphic, + minRatio: number = 0, + amplitude: number = 1 ): number { const delay = (rowCount - 1 - row) / rowCount; - const _r = (Math.sin(ratio * 2 * Math.PI - delay * 2 * Math.PI) + 1) / 2; - // ctx.globalAlpha = _r; - // ctx.fill(); - return _r; + const _r = minRatio + (amplitude * (Math.sin(ratio * 2 * Math.PI - delay * 2 * Math.PI) + 1)) / 2; + return Math.min(1, Math.max(0, _r)); } // 从中心向两边的对角线渐变 @@ -97,17 +96,17 @@ export function diagonalCenterToEdge( rowCount: number, columnCount: number, ratio: number, - graphic: IGraphic + graphic: IGraphic, + minRatio: number = 0, + amplitude: number = 1 ): number { const centerRow = rowCount / 2; const centerCol = columnCount / 2; const distance = Math.sqrt( Math.pow((row - centerRow) / rowCount, 2) + Math.pow((column - centerCol) / columnCount, 2) ); - const _r = (Math.sin(ratio * 2 * Math.PI - distance * 2 * Math.PI) + 1) / 2; - // ctx.globalAlpha = _r; - // ctx.fill(); - return _r; + const _r = minRatio + (amplitude * (Math.sin(ratio * 2 * Math.PI - distance * 2 * Math.PI) + 1)) / 2; + return Math.min(1, Math.max(0, _r)); } // 从左上角到右下角的对角线渐变 @@ -118,13 +117,13 @@ export function diagonalTopLeftToBottomRight( rowCount: number, columnCount: number, ratio: number, - graphic: IGraphic + graphic: IGraphic, + minRatio: number = 0, + amplitude: number = 1 ): number { const delay = (row / rowCount + column / columnCount) / 2; - const _r = (Math.sin(ratio * 2 * Math.PI - delay * 2 * Math.PI) + 1) / 2; - // ctx.globalAlpha = _r; - // ctx.fill(); - return _r; + const _r = minRatio + (amplitude * (Math.sin(ratio * 2 * Math.PI - delay * 2 * Math.PI) + 1)) / 2; + return Math.min(1, Math.max(0, _r)); } // 旋转扫描效果 @@ -135,7 +134,9 @@ export function rotationScan( rowCount: number, columnCount: number, ratio: number, - graphic: IGraphic + graphic: IGraphic, + minRatio: number = 0, + amplitude: number = 1 ): number { // 计算当前点相对于中心点的角度 const centerRow = rowCount / 2; @@ -145,10 +146,8 @@ export function rotationScan( const normalizedAngle = angle < 0 ? angle + 2 * Math.PI : angle; // 计算扫描延迟 const delay = normalizedAngle / (2 * Math.PI); - const _r = (Math.sin(ratio * 2 * Math.PI - delay * 2 * Math.PI) + 1) / 2; - // ctx.globalAlpha = _r; - // ctx.fill(); - return _r; + const _r = minRatio + (amplitude * (Math.sin(ratio * 2 * Math.PI - delay * 2 * Math.PI) + 1)) / 2; + return Math.min(1, Math.max(0, _r)); } // 波纹扩散效果 @@ -159,7 +158,9 @@ export function rippleEffect( rowCount: number, columnCount: number, ratio: number, - graphic: IGraphic + graphic: IGraphic, + minRatio: number = 0, + amplitude: number = 1 ): number { const centerRow = rowCount / 2; const centerCol = columnCount / 2; @@ -169,10 +170,9 @@ export function rippleEffect( const normalizedDistance = distance / Math.sqrt(Math.pow(rowCount / 2, 2) + Math.pow(columnCount / 2, 2)); // 创建多个波纹 const waves = 3; - const _r = (Math.sin(ratio * 2 * Math.PI * waves - normalizedDistance * 2 * Math.PI) + 1) / 2; - // ctx.globalAlpha = _r; - // ctx.fill(); - return _r; + const _r = + minRatio + (amplitude * (Math.sin(ratio * 2 * Math.PI * waves - normalizedDistance * 2 * Math.PI) + 1)) / 2; + return Math.min(1, Math.max(0, _r)); } // 蛇形波动效果 @@ -183,14 +183,14 @@ export function snakeWave( rowCount: number, columnCount: number, ratio: number, - graphic: IGraphic + graphic: IGraphic, + minRatio: number = 0, + amplitude: number = 1 ): number { // 使用行和列的位置创建蛇形路径 const delay = ((row + column) % (rowCount + columnCount)) / (rowCount + columnCount); - const _r = (Math.sin(ratio * 2 * Math.PI - delay * 4 * Math.PI) + 1) / 2; - // ctx.globalAlpha = _r; - // ctx.fill(); - return _r; + const _r = minRatio + (amplitude * (Math.sin(ratio * 2 * Math.PI - delay * 4 * Math.PI) + 1)) / 2; + return Math.min(1, Math.max(0, _r)); } // 交错波纹效果 @@ -201,18 +201,20 @@ export function alternatingWave( rowCount: number, columnCount: number, ratio: number, - graphic: IGraphic + graphic: IGraphic, + minRatio: number = 0, + amplitude: number = 1 ): number { // 行和列的交错波纹 const rowPhase = row / rowCount; const colPhase = column / columnCount; const _r = - (Math.sin(ratio * 2 * Math.PI - rowPhase * 2 * Math.PI) * Math.sin(ratio * 2 * Math.PI - colPhase * 2 * Math.PI) + - 1) / - 2; - // ctx.globalAlpha = _r; - // ctx.fill(); - return _r; + minRatio + + (amplitude * + (Math.sin(ratio * 2 * Math.PI - rowPhase * 2 * Math.PI) * Math.sin(ratio * 2 * Math.PI - colPhase * 2 * Math.PI) + + 1)) / + 2; + return Math.min(1, Math.max(0, _r)); } // 螺旋效果 @@ -223,21 +225,21 @@ export function spiralEffect( rowCount: number, columnCount: number, ratio: number, - graphic: IGraphic + graphic: IGraphic, + minRatio: number = 0, + amplitude: number = 1 ): number { + // 计算到中心的距离和角度 const centerRow = rowCount / 2; const centerCol = columnCount / 2; - // 计算到中心的距离和角度 const distance = Math.sqrt(Math.pow(row - centerRow, 2) + Math.pow(column - centerCol, 2)); const angle = Math.atan2(row - centerRow, column - centerCol); // 归一化距离 const normalizedDistance = distance / Math.sqrt(Math.pow(rowCount / 2, 2) + Math.pow(columnCount / 2, 2)); // 组合距离和角度创建螺旋效果 const delay = (normalizedDistance + angle / (2 * Math.PI)) / 2; - const _r = (Math.sin(ratio * 2 * Math.PI - delay * 4 * Math.PI) + 1) / 2; - // ctx.globalAlpha = _r; - // ctx.fill(); - return _r; + const _r = minRatio + (amplitude * (Math.sin(ratio * 2 * Math.PI - delay * 4 * Math.PI) + 1)) / 2; + return Math.min(1, Math.max(0, _r)); } // 从两边向中间的列式渐变 @@ -248,13 +250,15 @@ export function columnCenterToEdge( rowCount: number, columnCount: number, ratio: number, - graphic: IGraphic + graphic: IGraphic, + minRatio: number = 0, + amplitude: number = 1 ): number { const centerCol = columnCount / 2; // 计算到中心的距离,并归一化到[0,1]区间 const distance = Math.abs(column - centerCol) / centerCol; - const _r = (Math.sin(ratio * 2 * Math.PI - distance * 2 * Math.PI) + 1) / 2; - return _r; + const _r = minRatio + (amplitude * (Math.sin(ratio * 2 * Math.PI - distance * 2 * Math.PI) + 1)) / 2; + return Math.min(1, Math.max(0, _r)); } // 从中间向两边的列式渐变 @@ -265,13 +269,15 @@ export function columnEdgeToCenter( rowCount: number, columnCount: number, ratio: number, - graphic: IGraphic + graphic: IGraphic, + minRatio: number = 0, + amplitude: number = 1 ): number { const centerCol = columnCount / 2; // 计算到中心的距离,并归一化到[0,1]区间,然后用1减去它来反转延迟 const distance = 1 - Math.abs(column - centerCol) / centerCol; - const _r = (Math.sin(ratio * 2 * Math.PI - distance * 2 * Math.PI) + 1) / 2; - return _r; + const _r = minRatio + (amplitude * (Math.sin(ratio * 2 * Math.PI - distance * 2 * Math.PI) + 1)) / 2; + return Math.min(1, Math.max(0, _r)); } // 从上下向中间的行式渐变 @@ -282,13 +288,15 @@ export function rowCenterToEdge( rowCount: number, columnCount: number, ratio: number, - graphic: IGraphic + graphic: IGraphic, + minRatio: number = 0, + amplitude: number = 1 ): number { const centerRow = rowCount / 2; // 计算到中心的距离,并归一化到[0,1]区间 const distance = Math.abs(row - centerRow) / centerRow; - const _r = (Math.sin(ratio * 2 * Math.PI - distance * 2 * Math.PI) + 1) / 2; - return _r; + const _r = minRatio + (amplitude * (Math.sin(ratio * 2 * Math.PI - distance * 2 * Math.PI) + 1)) / 2; + return Math.min(1, Math.max(0, _r)); } // 从中间向上下的行式渐变 @@ -299,13 +307,15 @@ export function rowEdgeToCenter( rowCount: number, columnCount: number, ratio: number, - graphic: IGraphic + graphic: IGraphic, + minRatio: number = 0, + amplitude: number = 1 ): number { const centerRow = rowCount / 2; // 计算到中心的距离,并归一化到[0,1]区间,然后用1减去它来反转延迟 const distance = 1 - Math.abs(row - centerRow) / centerRow; - const _r = (Math.sin(ratio * 2 * Math.PI - distance * 2 * Math.PI) + 1) / 2; - return _r; + const _r = minRatio + (amplitude * (Math.sin(ratio * 2 * Math.PI - distance * 2 * Math.PI) + 1)) / 2; + return Math.min(1, Math.max(0, _r)); } // 从四个角向中心的渐变 @@ -316,7 +326,9 @@ export function cornerToCenter( rowCount: number, columnCount: number, ratio: number, - graphic: IGraphic + graphic: IGraphic, + minRatio: number = 0, + amplitude: number = 1 ): number { const centerRow = rowCount / 2; const centerCol = columnCount / 2; @@ -326,8 +338,8 @@ export function cornerToCenter( ); // 归一化到[0,1]区间 const normalizedDistance = Math.min(distance, 1); - const _r = (Math.sin(ratio * 2 * Math.PI - normalizedDistance * 2 * Math.PI) + 1) / 2; - return _r; + const _r = minRatio + (amplitude * (Math.sin(ratio * 2 * Math.PI - normalizedDistance * 2 * Math.PI) + 1)) / 2; + return Math.min(1, Math.max(0, _r)); } // 从中心向四个角的渐变 @@ -338,7 +350,9 @@ export function centerToCorner( rowCount: number, columnCount: number, ratio: number, - graphic: IGraphic + graphic: IGraphic, + minRatio: number = 0, + amplitude: number = 1 ): number { const centerRow = rowCount / 2; const centerCol = columnCount / 2; @@ -348,8 +362,8 @@ export function centerToCorner( ); // 归一化到[0,1]区间并反转 const normalizedDistance = 1 - Math.min(distance, 1); - const _r = (Math.sin(ratio * 2 * Math.PI - normalizedDistance * 2 * Math.PI) + 1) / 2; - return _r; + const _r = minRatio + (amplitude * (Math.sin(ratio * 2 * Math.PI - normalizedDistance * 2 * Math.PI) + 1)) / 2; + return Math.min(1, Math.max(0, _r)); } // 脉冲波纹效果 @@ -360,29 +374,26 @@ export function pulseWave( rowCount: number, columnCount: number, ratio: number, - graphic: IGraphic + graphic: IGraphic, + minRatio: number = 0, + amplitude: number = 1 ): number { const centerRow = rowCount / 2; const centerCol = columnCount / 2; - // 计算到中心的距离 const distance = Math.sqrt( Math.pow((row - centerRow) / centerRow, 2) + Math.pow((column - centerCol) / centerCol, 2) ); const normalizedDistance = Math.min(distance, 1); - // 创建多个波纹 const waves = 3; const wavePhase = ratio * 2 * Math.PI * waves; - // 添加距离衰减 const decay = Math.max(0, 1 - normalizedDistance); - // 组合波纹和衰减效果 const wave = Math.sin(wavePhase - normalizedDistance * 4 * Math.PI); - const _r = ((wave + 1) / 2) * (decay * 0.7 + 0.3); - - return _r; + const _r = minRatio + amplitude * ((wave + 1) / 2) * (decay * 0.7 + 0.3); + return Math.min(1, Math.max(0, _r)); } // 粒子动画效果 @@ -393,7 +404,9 @@ export function particleEffect( rowCount: number, columnCount: number, ratio: number, - graphic: IGraphic + graphic: IGraphic, + minRatio: number = 0, + amplitude: number = 1 ): number { // 初始化随机种子 if (!graphic.dynamicTextureCache) { @@ -419,17 +432,15 @@ export function particleEffect( // 扩散阶段:粒子随机运动 const scatterRatio = (ratio - 0.4) / 0.6; - // 使用相位和方向创建随机运动 const movement = Math.sin(scatterRatio * speed * 8 * Math.PI + phase + direction * scatterRatio); - // 添加距离影响 const distanceEffect = Math.cos(normalizedDistance * Math.PI + scatterRatio * Math.PI); - // 添加衰减效果 const decay = Math.max(0, 1 - scatterRatio * 1.2); // 组合所有效果 - const _r = ((movement + 1) / 2) * decay * (0.3 + 0.7 * distanceEffect); - return Math.max(0, Math.min(1, _r)); + const baseEffect = ((movement + 1) / 2) * decay * (0.3 + 0.7 * distanceEffect); + const _r = minRatio + amplitude * baseEffect; + return Math.min(1, Math.max(0, _r)); } diff --git a/packages/vrender/__tests__/browser/src/pages/dynamic-texture.ts b/packages/vrender/__tests__/browser/src/pages/dynamic-texture.ts index 2206cb362..ebde5ee21 100644 --- a/packages/vrender/__tests__/browser/src/pages/dynamic-texture.ts +++ b/packages/vrender/__tests__/browser/src/pages/dynamic-texture.ts @@ -82,8 +82,8 @@ export const page = () => { y: 200 * Math.floor(index / 5), fill: colorPools[index % colorPools.length], texture: symbolTypeList[index % symbolTypeList.length], - textureSize: 20, - texturePadding: 2, + textureSize: 6, + texturePadding: 0.5, textureRatio: 0, textureColor: 'orange', textureOptions: { @@ -96,7 +96,7 @@ export const page = () => { ratio: number, graphic: IGraphic ) => { - const _r = effects[index](ctx, row, column, rowCount, columnCount, ratio, graphic); + const _r = effects[index](ctx, row, column, rowCount, columnCount, ratio, graphic, 0.3, 1); ctx.globalAlpha = _r; ctx.fill(); } diff --git a/packages/vrender/__tests__/browser/src/pages/vchart.ts b/packages/vrender/__tests__/browser/src/pages/vchart.ts index 94941cdcf..4c1b0e99d 100644 --- a/packages/vrender/__tests__/browser/src/pages/vchart.ts +++ b/packages/vrender/__tests__/browser/src/pages/vchart.ts @@ -20,91 +20,59 @@ export const page = () => { let i = 0; const spec = { - type: 'area', - data: { - values: [ - { type: 'Nail polish', country: 'Africa', value: 4229 }, - { type: 'Nail polish', country: 'EU', value: 4376 }, - { type: 'Nail polish', country: 'China', value: 3054 }, - { type: 'Nail polish', country: 'USA', value: 12814 }, - { type: 'Eyebrow pencil', country: 'Africa', value: 3932 }, - { type: 'Eyebrow pencil', country: 'EU', value: 3987 }, - { type: 'Eyebrow pencil', country: 'China', value: 5067 }, - { type: 'Eyebrow pencil', country: 'USA', value: 13012 }, - { type: 'Rouge', country: 'Africa', value: 5221 }, - { type: 'Rouge', country: 'EU', value: 3574 }, - { type: 'Rouge', country: 'China', value: 7004 }, - { type: 'Rouge', country: 'USA', value: 11624 }, - { type: 'Lipstick', country: 'Africa', value: 9256 }, - { type: 'Lipstick', country: 'EU', value: 4376 }, - { type: 'Lipstick', country: 'China', value: 9054 }, - { type: 'Lipstick', country: 'USA', value: 8814 }, - { type: 'Eyeshadows', country: 'Africa', value: 3308 }, - { type: 'Eyeshadows', country: 'EU', value: 4572 }, - { type: 'Eyeshadows', country: 'China', value: 12043 }, - { type: 'Eyeshadows', country: 'USA', value: 12998 }, - { type: 'Eyeliner', country: 'Africa', value: 5432 }, - { type: 'Eyeliner', country: 'EU', value: 3417 }, - { type: 'Eyeliner', country: 'China', value: 15067 }, - { type: 'Eyeliner', country: 'USA', value: 12321 }, - { type: 'Foundation', country: 'Africa', value: 13701 }, - { type: 'Foundation', country: 'EU', value: 5231 }, - { type: 'Foundation', country: 'China', value: 10119 }, - { type: 'Foundation', country: 'USA', value: 10342 }, - { type: 'Lip gloss', country: 'Africa', value: 4008 }, - { type: 'Lip gloss', country: 'EU', value: 4572 }, - { type: 'Lip gloss', country: 'China', value: 12043 }, - { type: 'Lip gloss', country: 'USA', value: 22998 }, - { type: 'Mascara', country: 'Africa', value: 18712 }, - { type: 'Mascara', country: 'EU', value: 6134 }, - { type: 'Mascara', country: 'China', value: 10419 }, - { type: 'Mascara', country: 'USA', value: 11261 } - ] - }, - title: { - visible: true, - text: 'Unstacked area chart of cosmetic products sales' - }, - stack: false, - xField: 'type', - yField: 'value', - zField: 'country', - seriesField: 'country', - legends: [{ visible: true, position: 'middle', orient: 'bottom' }], - axes: [ - { - orient: 'bottom', - mode: '3d' - }, - { - orient: 'left', - mode: '3d' - }, + type: 'bar', + data: [ { - orient: 'z', - mode: '3d', - label: { visible: true }, - type: 'band', - grid: { visible: true }, - width: 600, - height: 200, - depth: 200 + id: 'barData', + values: [ + { month: 'Monday', sales: 22 }, + { month: 'Tuesday', sales: 13 }, + { month: 'Wednesday', sales: 25 }, + { month: 'Thursday', sales: 29 }, + { month: 'Friday', sales: 38 } + ] } ], - crosshair: { - xField: { visible: false } - } + bar: { + style: { + texture: 'square', + textureSize: 6, + texturePadding: 1, + textureRatio: 0, + textureColor: 'orange', + textureOptions: { + dynamicTexture: ( + ctx: any, + row: number, + column: number, + rowCount: number, + columnCount: number, + ratio: number, + graphic: any + ) => { + const _r = VRenderKits.randomOpacity(ctx, row, column, rowCount, columnCount, ratio, graphic, 0.5, 0.5); + ctx.globalAlpha = _r; + ctx.fill(); + } + } + } + }, + animationAppear: { + bar: { + channel: { + textureRatio: { from: 0, to: 1 } + }, + easing: 'linear', + duration: 3000, + loop: true + } + }, + xField: 'month', + yField: 'sales' }; const chartSpace = new window.ChartSpace.default(spec, { - dom: 'container', - enableHtmlAttribute: true, - animation: false, - options3d: { - enable: true, - enableView3dTranform: true, - center: { x: 500, y: 250 } - } + dom: 'container' }); // setTimeout(() => {