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'