mirror of
https://github.com/unanmed/HumanBreak.git
synced 2025-10-20 13:02:58 +08:00
refactor: 素材管理
This commit is contained in:
parent
fb8236d127
commit
a10625013d
@ -29,6 +29,7 @@
|
||||
"jszip": "^3.10.1",
|
||||
"lodash-es": "^4.17.21",
|
||||
"lz-string": "^1.5.0",
|
||||
"maxrects-packer": "^2.7.3",
|
||||
"mutate-animate": "^1.4.2",
|
||||
"ogg-opus-decoder": "^1.6.14",
|
||||
"opus-decoder": "^0.7.7",
|
||||
|
@ -4,6 +4,7 @@
|
||||
"@motajs/client-base": "workspace:*",
|
||||
"@motajs/common": "workspace:*",
|
||||
"@motajs/render": "workspace:*",
|
||||
"@motajs/render-assets": "workspace:*",
|
||||
"@motajs/render-core": "workspace:*",
|
||||
"@motajs/legacy-common": "workspace:*",
|
||||
"@motajs/legacy-ui": "workspace:*",
|
||||
|
@ -1,8 +1,6 @@
|
||||
import { logger } from '@motajs/common';
|
||||
import {
|
||||
MotaOffscreenCanvas2D,
|
||||
SizedCanvasImageSource
|
||||
} from '@motajs/render-core';
|
||||
import { MotaOffscreenCanvas2D } from '@motajs/render-core';
|
||||
import { SizedCanvasImageSource } from '@motajs/render-assets';
|
||||
|
||||
// 经过测试(https://www.measurethat.net/Benchmarks/Show/30741/1/drawimage-img-vs-canvas-vs-bitmap-cropping-fix-loading)
|
||||
// 得出结论,ImageBitmap和Canvas的绘制性能不如Image,于是直接画Image就行,所以缓存基本上就是存Image
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { RenderAdapter, SizedCanvasImageSource } from '@motajs/render-core';
|
||||
import { RenderAdapter } from '@motajs/render-core';
|
||||
import { SizedCanvasImageSource } from '@motajs/render-assets';
|
||||
import { logger } from '@motajs/common';
|
||||
import { ILayerRenderExtends, Layer, LayerMovingRenderable } from './layer';
|
||||
import EventEmitter from 'eventemitter3';
|
||||
|
@ -4,9 +4,9 @@ import {
|
||||
RenderItem,
|
||||
RenderItemPosition,
|
||||
MotaOffscreenCanvas2D,
|
||||
Transform,
|
||||
SizedCanvasImageSource
|
||||
Transform
|
||||
} from '@motajs/render-core';
|
||||
import { SizedCanvasImageSource } from '@motajs/render-assets';
|
||||
import { isNil } from 'lodash-es';
|
||||
import { RenderableData, AutotileRenderable, texture } from './cache';
|
||||
import { IAnimateFrame, renderEmits } from './frame';
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { BaseProps, TagDefine } from '@motajs/render-vue';
|
||||
import { CanvasStyle, Transform } from '@motajs/render-core';
|
||||
import { Transform } from '@motajs/render-core';
|
||||
import { CanvasStyle } from '@motajs/render-assets';
|
||||
import {
|
||||
ILayerGroupRenderExtends,
|
||||
FloorLayer,
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { CloudLike } from './cloudLike';
|
||||
import { SizedCanvasImageSource } from '@motajs/render-core';
|
||||
import { SizedCanvasImageSource } from '@motajs/render-assets';
|
||||
|
||||
export class CloudWeather extends CloudLike {
|
||||
getImage(): SizedCanvasImageSource | null {
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { MotaOffscreenCanvas2D, Sprite } from '@motajs/render-core';
|
||||
import { Weather } from '../weather';
|
||||
import { SizedCanvasImageSource } from '@motajs/render-core';
|
||||
import { SizedCanvasImageSource } from '@motajs/render-assets';
|
||||
|
||||
export abstract class CloudLike extends Weather<Sprite> {
|
||||
/** 不透明度 */
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { MotaOffscreenCanvas2D } from '@motajs/render-core';
|
||||
import { CloudLike } from './cloudLike';
|
||||
import { SizedCanvasImageSource } from '@motajs/render-core';
|
||||
import { SizedCanvasImageSource } from '@motajs/render-assets';
|
||||
|
||||
export class FogWeather extends CloudLike {
|
||||
/** 雾天气的图像比较小,因此将四个进行合并 */
|
||||
|
@ -97,6 +97,14 @@
|
||||
"63": "Uncaught promise error in waiting box component. Error reason: $1",
|
||||
"64": "Text node type and block type mismatch: '$1' vs '$2'",
|
||||
"65": "Cannot bind a weather controller twice.",
|
||||
"66": "Texture identifier repeated: $1",
|
||||
"67": "Cannot set alias '$1' for texture $2, since there is already an alias '$3' for it.",
|
||||
"68": "Cannot set alias '$1' for texture $2, since '$1' is already an alias for $3.",
|
||||
"69": "Clip totally exceeds texture's dimensions, no operation will be alppied.",
|
||||
"70": "An TextureAnimater can only be created once.",
|
||||
"71": "Cannot start animate when animater not created on texture.",
|
||||
"72": "Frame count delivered to frame-based animater need to exactly divide texture's height.",
|
||||
"73": "Consistent size is needed when using WebGL2 composer.",
|
||||
"1001": "Item-detail extension needs 'floor-binder' and 'floor-damage' extension as dependency.",
|
||||
"1101": "Cannot add new effect to point effect instance, for there's no more reserve space for it. Please increase the max count of the instance."
|
||||
}
|
||||
|
3
packages/render-assets/package.json
Normal file
3
packages/render-assets/package.json
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"name": "@motajs/render-assets"
|
||||
}
|
189
packages/render-assets/src/animater.ts
Normal file
189
packages/render-assets/src/animater.ts
Normal file
@ -0,0 +1,189 @@
|
||||
import { logger } from '@motajs/common';
|
||||
import {
|
||||
IRect,
|
||||
ITexture,
|
||||
ITextureAnimater,
|
||||
ITextureListedRenderable
|
||||
} from './types';
|
||||
|
||||
/**
|
||||
* 基于帧的动画控制器,创建时传入的参数代表帧数,生成动画时传入的参数自定义
|
||||
*/
|
||||
export abstract class FrameBasedAnimater<T>
|
||||
implements ITextureAnimater<number, T>
|
||||
{
|
||||
texture: ITexture<number, T> | null = null;
|
||||
|
||||
/** 动画的总帧数 */
|
||||
protected frames: number = 0;
|
||||
|
||||
create(texture: ITexture, data: number): void {
|
||||
if (this.texture) {
|
||||
logger.warn(70);
|
||||
return;
|
||||
}
|
||||
this.texture = texture;
|
||||
this.frames = data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断当前动画控制器是否已经初始化完毕并开始生成动画
|
||||
*/
|
||||
protected check(): boolean {
|
||||
if (!this.texture || this.frames === 0) {
|
||||
logger.warn(71);
|
||||
return false;
|
||||
}
|
||||
if (this.texture.height % this.frames !== 0) {
|
||||
logger.warn(72);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
abstract open(init: T): Generator<ITextureListedRenderable> | null;
|
||||
|
||||
abstract cycled(init: T): Generator<ITextureListedRenderable> | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 行动画控制器,将贴图按照从上到下的顺序依次组成帧动画,创建时传入的参数代表帧数
|
||||
*/
|
||||
export class TextureRowAnimater extends FrameBasedAnimater<void> {
|
||||
*open(): Generator<ITextureListedRenderable> | null {
|
||||
if (!this.check()) return null;
|
||||
const renderable = this.texture!.static();
|
||||
const { x: ox, y: oy } = renderable.rect;
|
||||
const { width: w, height } = this.texture!;
|
||||
const h = height / this.frames;
|
||||
for (let i = 0; i < this.frames; i++) {
|
||||
const renderable: ITextureListedRenderable = {
|
||||
source: this.texture!.source,
|
||||
rect: [{ x: ox, y: i * h + oy, w, h }]
|
||||
};
|
||||
yield renderable;
|
||||
}
|
||||
}
|
||||
|
||||
*cycled(): Generator<ITextureListedRenderable> | null {
|
||||
if (!this.check()) return null;
|
||||
const renderable = this.texture!.static();
|
||||
const { x: ox, y: oy } = renderable.rect;
|
||||
const { width: w, height } = this.texture!;
|
||||
const h = height / this.frames;
|
||||
let i = 0;
|
||||
while (true) {
|
||||
if (i === this.frames) i = 0;
|
||||
const renderable: ITextureListedRenderable = {
|
||||
source: this.texture!.source,
|
||||
rect: [{ x: ox, y: i * h + oy, w, h }]
|
||||
};
|
||||
yield renderable;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 列动画控制器,将贴图按照从左到右的顺序依次组成帧动画,创建时传入的参数代表帧数
|
||||
*/
|
||||
export class TextureColumnAnimater extends FrameBasedAnimater<void> {
|
||||
*open(): Generator<ITextureListedRenderable> | null {
|
||||
if (!this.check()) return null;
|
||||
const renderable = this.texture!.static();
|
||||
const { x: ox, y: oy } = renderable.rect;
|
||||
const { width, height: h } = this.texture!;
|
||||
const w = width / this.frames;
|
||||
for (let i = 0; i < this.frames; i++) {
|
||||
const renderable: ITextureListedRenderable = {
|
||||
source: this.texture!.source,
|
||||
rect: [{ x: i * width + ox, y: oy, w, h }]
|
||||
};
|
||||
yield renderable;
|
||||
}
|
||||
}
|
||||
|
||||
*cycled(): Generator<ITextureListedRenderable> | null {
|
||||
if (!this.check()) return null;
|
||||
const renderable = this.texture!.static();
|
||||
const { x: ox, y: oy } = renderable.rect;
|
||||
const { width, height: h } = this.texture!;
|
||||
const w = width / this.frames;
|
||||
let i = 0;
|
||||
while (true) {
|
||||
if (i === this.frames) i = 0;
|
||||
const renderable: ITextureListedRenderable = {
|
||||
source: this.texture!.source,
|
||||
rect: [{ x: i * w + ox, y: oy, w, h }]
|
||||
};
|
||||
yield renderable;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export interface IAnimaterTranslatedInit<T> {
|
||||
/** 以此矩形作为参考矩形 */
|
||||
readonly rect: Readonly<IRect>;
|
||||
/** 传递给原先的动画控制器的参数 */
|
||||
readonly data: T;
|
||||
/** 原先的动画控制器 */
|
||||
readonly animate: ITextureAnimater<unknown, T>;
|
||||
}
|
||||
|
||||
type AdderImplements<T> = ITextureAnimater<void, IAnimaterTranslatedInit<T>>;
|
||||
|
||||
type AdderTexture<T> = ITexture<void, IAnimaterTranslatedInit<T>>;
|
||||
|
||||
/**
|
||||
* 对一个动画控制器执行偏移操作的控制器,一般用于图集上。
|
||||
* 创建时传入的参数代表要执行偏移操作的动画控制器,动画参数包含两部分,一个是参考矩形,一个是传递给原先动画控制器的参数
|
||||
*/
|
||||
export class TextureAnimaterTranslated<T> implements AdderImplements<T> {
|
||||
texture: AdderTexture<T> | null = null;
|
||||
|
||||
create(texture: ITexture): void {
|
||||
this.texture = texture;
|
||||
}
|
||||
|
||||
*output(
|
||||
ani: Generator<ITextureListedRenderable>,
|
||||
origin: Readonly<IRect>,
|
||||
rect: Readonly<IRect>
|
||||
) {
|
||||
const { x: ox, y: oy } = origin;
|
||||
const { x: nx, y: ny } = rect;
|
||||
const source = this.texture!.source;
|
||||
|
||||
while (true) {
|
||||
const next = ani.next();
|
||||
if (next.done) break;
|
||||
const renderable = next.value;
|
||||
const list: IRect[] = [];
|
||||
renderable.rect.forEach(({ x, y, w, h }) => {
|
||||
list.push({ x: x - ox + nx, y: y - oy + ny, w, h });
|
||||
});
|
||||
const res: ITextureListedRenderable = {
|
||||
source,
|
||||
rect: list
|
||||
};
|
||||
yield res;
|
||||
}
|
||||
}
|
||||
|
||||
open(
|
||||
init: IAnimaterTranslatedInit<T>
|
||||
): Generator<ITextureListedRenderable> | null {
|
||||
const ani = init.animate.open(init.data);
|
||||
const origin = init.animate.texture?.static().rect;
|
||||
if (!ani || !origin) return null;
|
||||
return this.output(ani, origin, init.rect);
|
||||
}
|
||||
|
||||
cycled(
|
||||
init: IAnimaterTranslatedInit<T>
|
||||
): Generator<ITextureListedRenderable> | null {
|
||||
const ani = init.animate.cycled(init.data);
|
||||
const origin = init.animate.texture?.static().rect;
|
||||
if (!ani || !origin) return null;
|
||||
return this.output(ani, origin, init.rect);
|
||||
}
|
||||
}
|
488
packages/render-assets/src/composer.ts
Normal file
488
packages/render-assets/src/composer.ts
Normal file
@ -0,0 +1,488 @@
|
||||
import {
|
||||
IOption,
|
||||
IRectangle,
|
||||
MaxRectsPacker,
|
||||
Rectangle
|
||||
} from 'maxrects-packer';
|
||||
import { IAnimaterTranslatedInit, TextureAnimaterTranslated } from './animater';
|
||||
import { Texture } from './texture';
|
||||
import {
|
||||
IRect,
|
||||
ITexture,
|
||||
ITextureComposedData,
|
||||
ITextureComposer,
|
||||
SizedCanvasImageSource
|
||||
} from './types';
|
||||
import vert from './shader/pack.vert?raw';
|
||||
import frag from './shader/pack.frag?raw';
|
||||
import { compileGLWith } from './utils';
|
||||
import { logger } from '@motajs/common';
|
||||
import { isNil } from 'lodash-es';
|
||||
|
||||
interface IndexMarkedComposedData {
|
||||
/** 组合数据 */
|
||||
readonly data: ITextureComposedData;
|
||||
/** 组合时最后一个用到的贴图的索引 */
|
||||
readonly index: number;
|
||||
}
|
||||
|
||||
type TranslatedComposer<D, T> = ITextureComposer<
|
||||
D,
|
||||
void,
|
||||
IAnimaterTranslatedInit<T>
|
||||
>;
|
||||
|
||||
export interface IGridComposerData {
|
||||
/** 单张图集的最大宽度 */
|
||||
readonly maxWidth: number;
|
||||
/** 单张图集的最大高度 */
|
||||
readonly maxHeight: number;
|
||||
/** 单个贴图的宽度,与之不同的贴图将会被剔除并警告 */
|
||||
readonly width: number;
|
||||
/** 单个贴图的宽度,与之不同的贴图将会被剔除并警告 */
|
||||
readonly height: number;
|
||||
}
|
||||
|
||||
interface GridNeededSize {
|
||||
/** 图集宽度 */
|
||||
readonly width: number;
|
||||
/** 图集高度 */
|
||||
readonly height: number;
|
||||
/** 一共有多少行 */
|
||||
readonly rows: number;
|
||||
/** 一共有多少列 */
|
||||
readonly cols: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 网格组合器,将等大小的贴图组合成图集,要求每个贴图的尺寸一致
|
||||
*/
|
||||
export class TextureGridComposer<T>
|
||||
implements TranslatedComposer<IGridComposerData, T>
|
||||
{
|
||||
private getNeededSize(
|
||||
tex: ITexture[],
|
||||
start: number,
|
||||
data: IGridComposerData
|
||||
): GridNeededSize {
|
||||
const maxRows = Math.floor(data.maxWidth / data.width);
|
||||
const maxCols = Math.floor(data.maxHeight / data.height);
|
||||
const rest = tex.length - start;
|
||||
if (rest >= maxRows * maxCols) {
|
||||
const size: GridNeededSize = {
|
||||
width: data.maxWidth,
|
||||
height: data.maxHeight,
|
||||
rows: maxRows,
|
||||
cols: maxCols
|
||||
};
|
||||
return size;
|
||||
} else {
|
||||
const aspect = data.height / data.width;
|
||||
const cols = Math.ceil(Math.sqrt(rest * aspect));
|
||||
const rows = Math.ceil(rest / cols);
|
||||
const size: GridNeededSize = {
|
||||
width: cols * data.width,
|
||||
height: rows * data.height,
|
||||
rows,
|
||||
cols
|
||||
};
|
||||
return size;
|
||||
}
|
||||
}
|
||||
|
||||
private nextAsset(
|
||||
tex: ITexture[],
|
||||
start: number,
|
||||
data: IGridComposerData
|
||||
): IndexMarkedComposedData {
|
||||
const size = this.getNeededSize(tex, start, data);
|
||||
const { width, height, rows, cols } = size;
|
||||
const canvas = document.createElement('canvas');
|
||||
const ctx = canvas.getContext('2d')!;
|
||||
canvas.width = width;
|
||||
canvas.height = height;
|
||||
|
||||
const count = Math.min(rows * cols, tex.length - start);
|
||||
const map = new Map<ITexture, IRect>();
|
||||
|
||||
let x = 0;
|
||||
let y = 0;
|
||||
for (let i = 0; i < count; i++) {
|
||||
const dx = x * data.width;
|
||||
const dy = y * data.height;
|
||||
const texture = tex[i + start];
|
||||
const renderable = texture.static();
|
||||
const { x: sx, y: sy, w: sw, h: sh } = renderable.rect;
|
||||
ctx.drawImage(renderable.source, sx, sy, sw, sh, dx, dy, sw, sh);
|
||||
map.set(texture, { x: dx, y: dy, w: sw, h: sh });
|
||||
x++;
|
||||
if (x === cols) {
|
||||
y++;
|
||||
x = 0;
|
||||
}
|
||||
}
|
||||
|
||||
const texture = new Texture<void, IAnimaterTranslatedInit<T>>(canvas);
|
||||
texture.animated(new TextureAnimaterTranslated(), void 0);
|
||||
|
||||
const composed: ITextureComposedData = {
|
||||
texture,
|
||||
assetMap: map
|
||||
};
|
||||
|
||||
return { data: composed, index: start + count };
|
||||
}
|
||||
|
||||
*compose(
|
||||
input: Iterable<ITexture>,
|
||||
data: IGridComposerData
|
||||
): Generator<ITextureComposedData> {
|
||||
const arr = [...input];
|
||||
|
||||
let i = 0;
|
||||
|
||||
while (i < arr.length) {
|
||||
const { data: asset, index } = this.nextAsset(arr, i, data);
|
||||
i = index + 1;
|
||||
yield asset;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export interface IMaxRectsComposerData extends IOption {
|
||||
/** 贴图之间的间距 */
|
||||
readonly padding: number;
|
||||
}
|
||||
|
||||
interface MaxRectsRectangle extends IRectangle {
|
||||
/** 这个矩形对应的贴图对象 */
|
||||
readonly data: ITexture;
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用 Max Rects 算法执行贴图整合,输入数据参考 {@link IMaxRectsComposerData},
|
||||
* 输出的纹理的图像源将会是不同的画布,注意与 {@link TextureMaxRectsWebGL2Composer} 区分
|
||||
*/
|
||||
export class TextureMaxRectsComposer<T>
|
||||
implements TranslatedComposer<IMaxRectsComposerData, T>
|
||||
{
|
||||
constructor(
|
||||
public readonly maxWidth: number,
|
||||
public readonly maxHeight: number
|
||||
) {}
|
||||
|
||||
*compose(
|
||||
input: Iterable<ITexture>,
|
||||
data: IMaxRectsComposerData
|
||||
): Generator<ITextureComposedData<void, IAnimaterTranslatedInit<T>>> {
|
||||
const packer = new MaxRectsPacker<MaxRectsRectangle>(
|
||||
this.maxWidth,
|
||||
this.maxHeight,
|
||||
data.padding,
|
||||
data
|
||||
);
|
||||
const arr = [...input];
|
||||
const rects = arr.map<MaxRectsRectangle>(v => {
|
||||
const rect = v.static().rect;
|
||||
const toPack = new Rectangle(rect.w, rect.h);
|
||||
toPack.data = v;
|
||||
return toPack;
|
||||
});
|
||||
packer.addArray(rects);
|
||||
|
||||
for (const bin of packer.bins) {
|
||||
const map = new Map<ITexture, IRect>();
|
||||
const canvas = document.createElement('canvas');
|
||||
const ctx = canvas.getContext('2d')!;
|
||||
canvas.width = bin.width;
|
||||
canvas.height = bin.height;
|
||||
ctx.imageSmoothingEnabled = false;
|
||||
bin.rects.forEach(v => {
|
||||
const rect: IRect = { x: v.x, y: v.y, w: v.width, h: v.height };
|
||||
map.set(v.data, rect);
|
||||
const renderable = v.data.static();
|
||||
const { x, y, w, h } = renderable.rect;
|
||||
const source = renderable.source;
|
||||
ctx.drawImage(source, x, y, w, h, v.x, v.y, v.width, v.height);
|
||||
});
|
||||
const texture = new Texture<void, IAnimaterTranslatedInit<T>>(
|
||||
canvas
|
||||
);
|
||||
texture.animated(new TextureAnimaterTranslated(), void 0);
|
||||
const data: ITextureComposedData = {
|
||||
texture,
|
||||
assetMap: map
|
||||
};
|
||||
yield data;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface RectProcessed {
|
||||
/** 贴图位置映射 */
|
||||
readonly texMap: Map<ITexture, Readonly<IRect>>;
|
||||
/** 顶点数组 */
|
||||
readonly attrib: Float32Array;
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用 Max Rects 算法执行贴图整合,输入数据参考 {@link IMaxRectsComposerData},
|
||||
* 要求每个贴图的图像源尺寸一致,贴图大小可以不同。
|
||||
* 注意,本组合器同时只能处理一个组合操作,上一个没执行完的时候再次调用 `compose` 会出现问题。
|
||||
* 所有输出的内容中,贴图对象的图像源都是同一个画布,因此获取后要么直接使用,要么立刻调用 `toBitmap`
|
||||
*/
|
||||
export class TextureMaxRectsWebGL2Composer<T>
|
||||
implements TranslatedComposer<IMaxRectsComposerData, T>
|
||||
{
|
||||
/** 使用的画布 */
|
||||
readonly canvas: HTMLCanvasElement;
|
||||
/** 画布上下文 */
|
||||
readonly gl: WebGL2RenderingContext;
|
||||
/** WebGL2 程序 */
|
||||
readonly program: WebGLProgram;
|
||||
/** 顶点数组缓冲区 */
|
||||
readonly vertBuffer: WebGLBuffer;
|
||||
/** 纹理数组对象 */
|
||||
readonly texArray: WebGLTexture;
|
||||
/** 纹理数组对象的位置 */
|
||||
readonly texArrayPos: WebGLUniformLocation;
|
||||
/** `a_position` 的索引 */
|
||||
readonly posPos: number;
|
||||
/** `a_texCoord` 的索引 */
|
||||
readonly texPos: number;
|
||||
|
||||
/** 本次处理的贴图宽度 */
|
||||
private opWidth: number = 0;
|
||||
/** 本次处理的贴图高度 */
|
||||
private opHeight: number = 0;
|
||||
|
||||
constructor(
|
||||
public readonly maxWidth: number,
|
||||
public readonly maxHeight: number
|
||||
) {
|
||||
this.canvas = document.createElement('canvas');
|
||||
this.canvas.width = maxWidth;
|
||||
this.canvas.height = maxHeight;
|
||||
this.gl = this.canvas.getContext('webgl2')!;
|
||||
const program = compileGLWith(this.gl, vert, frag)!;
|
||||
this.program = program;
|
||||
|
||||
// 初始化画布数据
|
||||
|
||||
const texture = this.gl.createTexture();
|
||||
this.texArray = texture;
|
||||
const location = this.gl.getUniformLocation(program, 'u_textArray')!;
|
||||
this.texArrayPos = location;
|
||||
|
||||
this.posPos = this.gl.getAttribLocation(program, 'a_position');
|
||||
this.texPos = this.gl.getAttribLocation(program, 'a_texCoord');
|
||||
|
||||
this.vertBuffer = this.gl.createBuffer();
|
||||
|
||||
this.gl.useProgram(program);
|
||||
}
|
||||
|
||||
/**
|
||||
* 对贴图进行索引
|
||||
* @param textures 贴图数组
|
||||
*/
|
||||
private mapTextures(
|
||||
textures: ITexture[]
|
||||
): Map<SizedCanvasImageSource, number> {
|
||||
const map = new Map<SizedCanvasImageSource, number>();
|
||||
const { width, height } = textures[0].source;
|
||||
|
||||
textures.forEach(v => {
|
||||
const source = v.source;
|
||||
if (map.has(source)) return;
|
||||
if (source.width !== width || source.height !== height) {
|
||||
logger.warn(73);
|
||||
return;
|
||||
}
|
||||
map.set(source, map.size);
|
||||
});
|
||||
|
||||
this.opWidth = width;
|
||||
this.opHeight = height;
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
/**
|
||||
* 传递贴图
|
||||
* @param texMap 纹理映射
|
||||
*/
|
||||
private setTexture(texMap: Map<SizedCanvasImageSource, number>) {
|
||||
const gl = this.gl;
|
||||
gl.bindTexture(gl.TEXTURE_2D_ARRAY, this.texArray);
|
||||
|
||||
gl.texStorage3D(
|
||||
gl.TEXTURE_2D_ARRAY,
|
||||
1,
|
||||
gl.RGBA8,
|
||||
this.opWidth,
|
||||
this.opHeight,
|
||||
texMap.size
|
||||
);
|
||||
texMap.forEach((index, source) => {
|
||||
gl.texSubImage3D(
|
||||
gl.TEXTURE_2D_ARRAY,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
index,
|
||||
this.opWidth,
|
||||
this.opHeight,
|
||||
1,
|
||||
gl.RGBA,
|
||||
gl.UNSIGNED_BYTE,
|
||||
source
|
||||
);
|
||||
});
|
||||
|
||||
gl.texParameteri(
|
||||
gl.TEXTURE_2D_ARRAY,
|
||||
gl.TEXTURE_MAG_FILTER,
|
||||
gl.NEAREST
|
||||
);
|
||||
gl.texParameteri(
|
||||
gl.TEXTURE_2D_ARRAY,
|
||||
gl.TEXTURE_MIN_FILTER,
|
||||
gl.NEAREST
|
||||
);
|
||||
gl.texParameteri(
|
||||
gl.TEXTURE_2D_ARRAY,
|
||||
gl.TEXTURE_WRAP_S,
|
||||
gl.CLAMP_TO_EDGE
|
||||
);
|
||||
gl.texParameteri(
|
||||
gl.TEXTURE_2D_ARRAY,
|
||||
gl.TEXTURE_WRAP_T,
|
||||
gl.CLAMP_TO_EDGE
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理矩形数组,生成 WebGL2 顶点数据
|
||||
* @param rects 要处理的矩形数组
|
||||
* @param texMap 贴图到贴图数组索引的映射
|
||||
*/
|
||||
private processRects(
|
||||
rects: MaxRectsRectangle[],
|
||||
texMap: Map<SizedCanvasImageSource, number>
|
||||
): RectProcessed {
|
||||
const { width: ow, height: oh } = this.canvas;
|
||||
const map = new Map<ITexture, IRect>();
|
||||
const attrib = new Float32Array(rects.length * 5 * 6);
|
||||
rects.forEach((v, i) => {
|
||||
const rect: IRect = { x: v.x, y: v.y, w: v.width, h: v.height };
|
||||
map.set(v.data, rect);
|
||||
const renderable = v.data.static();
|
||||
const { width: tw, height: th } = v.data.source;
|
||||
const { x, y, w, h } = renderable.rect;
|
||||
// 画到目标画布上的位置
|
||||
const ol = (v.x / ow) * 2 - 1;
|
||||
const ob = (v.y / oh) * 2 - 1;
|
||||
const or = ((v.x + v.width) / ow) * 2 - 1;
|
||||
const ot = ((v.y + v.height) / oh) * 2 - 1;
|
||||
// 原始贴图位置
|
||||
const tl = x / tw;
|
||||
const tt = y / tw;
|
||||
const tr = (x + w) / tw;
|
||||
const tb = (y + h) / th;
|
||||
// 贴图索引
|
||||
const ti = texMap.get(v.data.source);
|
||||
|
||||
if (isNil(ti)) return;
|
||||
|
||||
// Benchmark https://www.measurethat.net/Benchmarks/Show/35246/2/different-method-to-write-a-typedarray
|
||||
|
||||
// prettier-ignore
|
||||
const data = [
|
||||
// x y u v i
|
||||
ol, -ot, tl, tt, ti, // 左上角
|
||||
ol, -ob, tl, tb, ti, // 左下角
|
||||
or, -ot, tr, tt, ti, // 右上角
|
||||
or, -ot, tr, tt, ti, // 右上角
|
||||
ol, -ob, tl, tb, ti, // 左下角
|
||||
or, -ob, tr, tb, ti // 右下角
|
||||
];
|
||||
|
||||
attrib.set(data, i * 30);
|
||||
});
|
||||
|
||||
const data: RectProcessed = {
|
||||
texMap: map,
|
||||
attrib
|
||||
};
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行渲染操作
|
||||
* @param attrib 顶点数组
|
||||
*/
|
||||
private renderAtlas(attrib: Float32Array) {
|
||||
const gl = this.gl;
|
||||
|
||||
gl.clearColor(0, 0, 0, 0);
|
||||
gl.clearDepth(1);
|
||||
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
|
||||
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, this.vertBuffer);
|
||||
gl.bufferData(gl.ARRAY_BUFFER, attrib, gl.DYNAMIC_DRAW);
|
||||
|
||||
gl.vertexAttribPointer(this.posPos, 2, gl.FLOAT, false, 5 * 4, 0);
|
||||
gl.vertexAttribPointer(this.texPos, 3, gl.FLOAT, false, 5 * 4, 2 * 4);
|
||||
gl.enableVertexAttribArray(this.posPos);
|
||||
gl.enableVertexAttribArray(this.texPos);
|
||||
|
||||
gl.uniform1i(this.texArrayPos, 0);
|
||||
|
||||
gl.drawArrays(gl.TRIANGLES, 0, attrib.length / 5);
|
||||
}
|
||||
|
||||
*compose(
|
||||
input: Iterable<ITexture>,
|
||||
data: IMaxRectsComposerData
|
||||
): Generator<ITextureComposedData<void, IAnimaterTranslatedInit<T>>> {
|
||||
this.opWidth = 0;
|
||||
this.opHeight = 0;
|
||||
|
||||
const packer = new MaxRectsPacker<MaxRectsRectangle>(
|
||||
this.maxWidth,
|
||||
this.maxHeight,
|
||||
data.padding,
|
||||
data
|
||||
);
|
||||
const arr = [...input];
|
||||
const rects = arr.map<MaxRectsRectangle>(v => {
|
||||
const rect = v.static().rect;
|
||||
const toPack = new Rectangle(rect.w, rect.h);
|
||||
toPack.data = v;
|
||||
return toPack;
|
||||
});
|
||||
packer.addArray(rects);
|
||||
|
||||
const indexMap = this.mapTextures(arr);
|
||||
this.setTexture(indexMap);
|
||||
|
||||
for (const bin of packer.bins) {
|
||||
const { texMap, attrib } = this.processRects(bin.rects, indexMap);
|
||||
this.renderAtlas(attrib);
|
||||
const texture = new Texture<void, IAnimaterTranslatedInit<T>>(
|
||||
this.canvas
|
||||
);
|
||||
texture.animated(new TextureAnimaterTranslated(), void 0);
|
||||
const data: ITextureComposedData = {
|
||||
texture,
|
||||
assetMap: texMap
|
||||
};
|
||||
|
||||
yield data;
|
||||
}
|
||||
|
||||
this.gl.disableVertexAttribArray(this.posPos);
|
||||
this.gl.disableVertexAttribArray(this.texPos);
|
||||
}
|
||||
}
|
7
packages/render-assets/src/index.ts
Normal file
7
packages/render-assets/src/index.ts
Normal file
@ -0,0 +1,7 @@
|
||||
export * from './animater';
|
||||
export * from './composer';
|
||||
export * from './splitter';
|
||||
export * from './store';
|
||||
export * from './texture';
|
||||
export * from './types';
|
||||
export * from './utils';
|
12
packages/render-assets/src/shader/pack.frag
Normal file
12
packages/render-assets/src/shader/pack.frag
Normal file
@ -0,0 +1,12 @@
|
||||
#version 300 es
|
||||
precision highp float;
|
||||
precision highp sampler2DArray;
|
||||
|
||||
in vec3 v_texCoord;
|
||||
out vec4 outColor;
|
||||
|
||||
uniform sampler2DArray u_texArray;
|
||||
|
||||
void main() {
|
||||
outColor = texture(u_texArray, v_texCoord);
|
||||
}
|
12
packages/render-assets/src/shader/pack.vert
Normal file
12
packages/render-assets/src/shader/pack.vert
Normal file
@ -0,0 +1,12 @@
|
||||
#version 300 es
|
||||
precision highp float;
|
||||
|
||||
in vec2 a_position;
|
||||
in vec3 a_texCoord;
|
||||
|
||||
out vec3 v_texCoord;
|
||||
|
||||
void main() {
|
||||
v_texCoord = a_texCoord;
|
||||
gl_Position = vec4(a_position, 0.0, 1.0);
|
||||
}
|
65
packages/render-assets/src/splitter.ts
Normal file
65
packages/render-assets/src/splitter.ts
Normal file
@ -0,0 +1,65 @@
|
||||
import { Texture } from './texture';
|
||||
import { ITexture, ITextureSplitter, IRect } from './types';
|
||||
|
||||
/**
|
||||
* 按行分割贴图,即分割成一行行的贴图,按从上到下的顺序输出
|
||||
* 输入参数代表每一行的高度
|
||||
*/
|
||||
export class TextureRowSplitter implements ITextureSplitter<number> {
|
||||
*split(texture: ITexture, data: number): Generator<ITexture> {
|
||||
const lines = Math.ceil(texture.height / data);
|
||||
for (let i = 0; i < lines; i++) {
|
||||
const tex = new Texture(texture.source);
|
||||
tex.clip(0, i * data, texture.width, data);
|
||||
yield tex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 按列分割贴图,即分割成一列列的贴图,按从左到右的顺序输出
|
||||
* 输入参数代表每一列的宽度
|
||||
*/
|
||||
export class TextureColumnSplitter implements ITextureSplitter<number> {
|
||||
*split(texture: ITexture, data: number): Generator<ITexture> {
|
||||
const lines = Math.ceil(texture.width / data);
|
||||
for (let i = 0; i < lines; i++) {
|
||||
const tex = new Texture(texture.source);
|
||||
tex.clip(i * data, 0, data, texture.height);
|
||||
yield tex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 按照网格分割贴图,按照先从左到右,再从上到下的顺序输出
|
||||
* 输入参数代表每一列的宽度和高度
|
||||
*/
|
||||
export class TextureGridSplitter implements ITextureSplitter<[number, number]> {
|
||||
*split(texture: ITexture, data: [number, number]): Generator<ITexture> {
|
||||
const [w, h] = data;
|
||||
const rows = Math.ceil(texture.width / w);
|
||||
const lines = Math.ceil(texture.height / h);
|
||||
for (let y = 0; y < lines; y++) {
|
||||
for (let x = 0; x < rows; x++) {
|
||||
const tex = new Texture(texture.source);
|
||||
tex.clip(x * w, y * h, w, h);
|
||||
yield tex;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据图集信息分割贴图,按照传入的矩形数组的顺序输出
|
||||
* 输入参数代表每个贴图对应到图集上的矩形位置
|
||||
*/
|
||||
export class TextureAssetSplitter implements ITextureSplitter<IRect[]> {
|
||||
*split(texture: ITexture, data: IRect[]): Generator<ITexture> {
|
||||
for (const { x, y, w, h } of data) {
|
||||
const tex = new Texture(texture.source);
|
||||
tex.clip(x, y, w, h);
|
||||
yield tex;
|
||||
}
|
||||
}
|
||||
}
|
102
packages/render-assets/src/store.ts
Normal file
102
packages/render-assets/src/store.ts
Normal file
@ -0,0 +1,102 @@
|
||||
import { isNil } from 'lodash-es';
|
||||
import { Texture } from './texture';
|
||||
import { ITexture, ITextureStore, SizedCanvasImageSource } from './types';
|
||||
import { logger } from '@motajs/common';
|
||||
|
||||
export class TextureStore implements ITextureStore {
|
||||
private readonly texMap: Map<number, ITexture> = new Map();
|
||||
private readonly invMap: Map<ITexture, number> = new Map();
|
||||
private readonly aliasMap: Map<string, number> = new Map();
|
||||
private readonly aliasInvMap: Map<number, string> = new Map();
|
||||
|
||||
[Symbol.iterator](): Iterator<[key: number, tex: ITexture]> {
|
||||
return this.texMap.entries();
|
||||
}
|
||||
|
||||
entries(): Iterable<[key: number, tex: ITexture]> {
|
||||
return this.texMap.entries();
|
||||
}
|
||||
|
||||
keys(): Iterable<number> {
|
||||
return this.texMap.keys();
|
||||
}
|
||||
|
||||
values(): Iterable<ITexture> {
|
||||
return this.texMap.values();
|
||||
}
|
||||
|
||||
createTexture(source: SizedCanvasImageSource): ITexture {
|
||||
return new Texture(source);
|
||||
}
|
||||
|
||||
addTexture(identifier: number, texture: ITexture): void {
|
||||
if (this.texMap.has(identifier)) {
|
||||
logger.warn(66, identifier.toString());
|
||||
return;
|
||||
}
|
||||
this.texMap.set(identifier, texture);
|
||||
this.invMap.set(texture, identifier);
|
||||
}
|
||||
|
||||
private removeBy(id: number, tex: ITexture, alias?: string) {
|
||||
this.texMap.delete(id);
|
||||
this.invMap.delete(tex);
|
||||
if (alias) {
|
||||
this.aliasMap.delete(alias);
|
||||
this.aliasInvMap.delete(id);
|
||||
}
|
||||
}
|
||||
|
||||
removeTexture(identifier: number | string | ITexture): void {
|
||||
if (typeof identifier === 'string') {
|
||||
const id = this.aliasMap.get(identifier);
|
||||
if (isNil(id)) return;
|
||||
const tex = this.texMap.get(id);
|
||||
if (isNil(tex)) return;
|
||||
this.removeBy(id, tex, identifier);
|
||||
} else if (typeof identifier === 'number') {
|
||||
const tex = this.texMap.get(identifier);
|
||||
if (isNil(tex)) return;
|
||||
const alias = this.aliasInvMap.get(identifier);
|
||||
this.removeBy(identifier, tex, alias);
|
||||
} else {
|
||||
const id = this.invMap.get(identifier);
|
||||
if (isNil(id)) return;
|
||||
const alias = this.aliasInvMap.get(id);
|
||||
this.removeBy(id, identifier, alias);
|
||||
}
|
||||
}
|
||||
|
||||
getTexture(identifier: number): ITexture | null {
|
||||
return this.texMap.get(identifier) ?? null;
|
||||
}
|
||||
|
||||
alias(identifier: number, alias: string): void {
|
||||
const id = this.aliasMap.get(alias);
|
||||
const al = this.aliasInvMap.get(identifier);
|
||||
if (!isNil(al)) {
|
||||
logger.warn(67, alias, identifier.toString(), al);
|
||||
return;
|
||||
}
|
||||
if (!isNil(id)) {
|
||||
logger.warn(68, alias, identifier.toString(), id.toString());
|
||||
return;
|
||||
}
|
||||
this.aliasMap.set(alias, identifier);
|
||||
this.aliasInvMap.set(identifier, alias);
|
||||
}
|
||||
|
||||
fromAlias(alias: string): ITexture | null {
|
||||
const id = this.aliasMap.get(alias);
|
||||
if (isNil(id)) return null;
|
||||
return this.texMap.get(id) ?? null;
|
||||
}
|
||||
|
||||
idOf(texture: ITexture): number | undefined {
|
||||
return this.invMap.get(texture);
|
||||
}
|
||||
|
||||
aliasOf(identifier: number): string | undefined {
|
||||
return this.aliasInvMap.get(identifier);
|
||||
}
|
||||
}
|
90
packages/render-assets/src/texture.ts
Normal file
90
packages/render-assets/src/texture.ts
Normal file
@ -0,0 +1,90 @@
|
||||
import { logger } from '@motajs/common';
|
||||
import {
|
||||
ITexture,
|
||||
ITextureAnimater,
|
||||
ITextureListedRenderable,
|
||||
ITextureSingleRenderable,
|
||||
ITextureSplitter,
|
||||
SizedCanvasImageSource
|
||||
} from './types';
|
||||
|
||||
export class Texture<T = unknown, A = unknown> implements ITexture<T, A> {
|
||||
source: SizedCanvasImageSource;
|
||||
animater: ITextureAnimater<T, A> | null = null;
|
||||
width: number;
|
||||
height: number;
|
||||
|
||||
private cx: number;
|
||||
private cy: number;
|
||||
|
||||
constructor(source: SizedCanvasImageSource) {
|
||||
this.source = source;
|
||||
this.width = source.width;
|
||||
this.height = source.height;
|
||||
this.cx = 0;
|
||||
this.cy = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 对纹理进行裁剪操作,不会改变图像源
|
||||
* @param x 裁剪左上角横坐标
|
||||
* @param y 裁剪左上角纵坐标
|
||||
* @param w 裁剪宽度
|
||||
* @param h 裁剪高度
|
||||
*/
|
||||
clip(x: number, y: number, w: number, h: number) {
|
||||
const r = x + w;
|
||||
const b = y + h;
|
||||
if (x > this.width || y > this.height || r < 0 || b < 0) {
|
||||
logger.warn(69);
|
||||
return;
|
||||
}
|
||||
const left = Math.max(0, x);
|
||||
const top = Math.max(0, y);
|
||||
const right = Math.min(this.width, r);
|
||||
const bottom = Math.min(this.height, b);
|
||||
this.cx = left;
|
||||
this.cy = top;
|
||||
this.width = right - left;
|
||||
this.height = bottom - top;
|
||||
}
|
||||
|
||||
async toBitmap(): Promise<void> {
|
||||
if (this.source instanceof ImageBitmap) return;
|
||||
this.source = await createImageBitmap(this.source as any);
|
||||
}
|
||||
|
||||
split<U>(splitter: ITextureSplitter<U>, data: U): Generator<ITexture> {
|
||||
return splitter.split(this, data);
|
||||
}
|
||||
|
||||
animated(animater: ITextureAnimater<T, A>, data: T): void {
|
||||
this.animater = animater;
|
||||
animater.create(this, data);
|
||||
}
|
||||
|
||||
static(): ITextureSingleRenderable {
|
||||
const renderable: ITextureSingleRenderable = {
|
||||
source: this.source,
|
||||
rect: { x: this.cx, y: this.cy, w: this.width, h: this.height }
|
||||
};
|
||||
return renderable;
|
||||
}
|
||||
|
||||
dynamic(data: A): Generator<ITextureListedRenderable> | null {
|
||||
if (!this.animater) return null;
|
||||
return this.animater.open(data);
|
||||
}
|
||||
|
||||
cycled(data: A): Generator<ITextureListedRenderable> | null {
|
||||
if (!this.animater) return null;
|
||||
return this.animater.cycled(data);
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
if (this.source instanceof ImageBitmap) {
|
||||
this.source.close();
|
||||
}
|
||||
this.animater = null;
|
||||
}
|
||||
}
|
201
packages/render-assets/src/types.ts
Normal file
201
packages/render-assets/src/types.ts
Normal file
@ -0,0 +1,201 @@
|
||||
export type SizedCanvasImageSource = Exclude<
|
||||
CanvasImageSource,
|
||||
VideoFrame | SVGElement
|
||||
>;
|
||||
|
||||
export type CanvasStyle = string | CanvasGradient | CanvasPattern;
|
||||
|
||||
export interface IRect {
|
||||
x: number;
|
||||
y: number;
|
||||
w: number;
|
||||
h: number;
|
||||
}
|
||||
|
||||
export interface ITextureSingleRenderable {
|
||||
/** 可渲染贴图对象的图像源 */
|
||||
readonly source: SizedCanvasImageSource;
|
||||
/** 贴图裁剪区域 */
|
||||
readonly rect: Readonly<IRect>;
|
||||
}
|
||||
|
||||
export interface ITextureListedRenderable {
|
||||
/** 可渲染贴图对象的图像源 */
|
||||
readonly source: SizedCanvasImageSource;
|
||||
/** 贴图裁剪区域 */
|
||||
readonly rect: Readonly<IRect>[];
|
||||
}
|
||||
|
||||
export interface ITextureComposedData<T = unknown, A = unknown> {
|
||||
/** 这个纹理图集的贴图对象 */
|
||||
readonly texture: ITexture<T, A>;
|
||||
/** 每个参与组合的贴图对应到图集对象的矩形范围 */
|
||||
readonly assetMap: Map<ITexture, Readonly<IRect>>;
|
||||
}
|
||||
|
||||
export interface ITextureComposer<T, C, I> {
|
||||
/**
|
||||
* 将一系列纹理组合成为一系列纹理图集
|
||||
* @param input 输入纹理
|
||||
* @param data 输入给组合器的参数
|
||||
*/
|
||||
compose(
|
||||
input: Iterable<ITexture>,
|
||||
data: T
|
||||
): Generator<ITextureComposedData<C, I>>;
|
||||
}
|
||||
|
||||
export interface ITextureSplitter<T> {
|
||||
/**
|
||||
* 对一个贴图对象执行拆分操作
|
||||
* @param texture 要拆分的贴图
|
||||
* @param data 传给拆分器的参数
|
||||
*/
|
||||
split(texture: ITexture, data: T): Generator<ITexture>;
|
||||
}
|
||||
|
||||
export interface ITextureAnimater<T, I> {
|
||||
/** 此动画控制器所控制的贴图 */
|
||||
readonly texture: ITexture<T, I> | null;
|
||||
|
||||
/**
|
||||
* 对一个贴图对象创建动画控制器
|
||||
* @param texture 要绑定的贴图对象
|
||||
* @param data 传递给动画控制器的参数
|
||||
*/
|
||||
create(texture: ITexture, data: T): void;
|
||||
|
||||
/**
|
||||
* 开始动画序列
|
||||
* @param init 动画初始化参数
|
||||
*/
|
||||
open(init: I): Generator<ITextureListedRenderable> | null;
|
||||
|
||||
/**
|
||||
* 开始循环动画序列
|
||||
* @param init 动画初始化参数
|
||||
*/
|
||||
cycled(init: I): Generator<ITextureListedRenderable> | null;
|
||||
}
|
||||
|
||||
export interface ITexture<T = unknown, A = unknown> {
|
||||
/** 贴图的图像源 */
|
||||
readonly source: SizedCanvasImageSource;
|
||||
/** 此贴图使用的动画控制器 */
|
||||
readonly animater: ITextureAnimater<T, A> | null;
|
||||
/** 贴图宽度 */
|
||||
readonly width: number;
|
||||
/** 贴图高度 */
|
||||
readonly height: number;
|
||||
|
||||
/**
|
||||
* 将此贴图转换为 bitmap 图像,图像源也会转变成 ImageBitmap
|
||||
*/
|
||||
toBitmap(): Promise<void>;
|
||||
|
||||
/**
|
||||
* 使用指定贴图切分器切分贴图
|
||||
* @param splitter 贴图切分器
|
||||
* @param data 传递给切分器的参数
|
||||
* @returns 切分出的贴图所组成的可迭代对象
|
||||
*/
|
||||
split<T>(splitter: ITextureSplitter<T>, data: T): Generator<ITexture>;
|
||||
|
||||
/**
|
||||
* 将此贴图标记为可动画贴图,使用传入的动画控制器描述动画。每个贴图只能绑定一个动画控制器,反之同理
|
||||
* @param animater 动画控制器
|
||||
* @param data 传递给动画控制器的参数
|
||||
*/
|
||||
animated(animater: ITextureAnimater<T, A>, data: T): void;
|
||||
|
||||
/**
|
||||
* 获取整张图的可渲染对象
|
||||
*/
|
||||
static(): ITextureSingleRenderable;
|
||||
|
||||
/**
|
||||
* 获取一系列动画可渲染对象,不循环,按帧数依次排列
|
||||
* @param data 传递给动画控制器的初始化参数
|
||||
*/
|
||||
dynamic(data: A): Generator<ITextureListedRenderable> | null;
|
||||
|
||||
/**
|
||||
* 获取无限循环的动画可渲染对象
|
||||
* @param data 传递给动画控制器的初始化参数
|
||||
*/
|
||||
cycled(data: A): Generator<ITextureListedRenderable> | null;
|
||||
|
||||
/**
|
||||
* 释放此贴图的资源,将不能再被使用
|
||||
*/
|
||||
dispose(): void;
|
||||
}
|
||||
|
||||
export interface ITextureStore {
|
||||
[Symbol.iterator](): Iterator<[key: number, tex: ITexture]>;
|
||||
|
||||
/**
|
||||
* 获取纹理对象键值对的可迭代对象
|
||||
*/
|
||||
entries(): Iterable<[key: number, tex: ITexture]>;
|
||||
|
||||
/**
|
||||
* 获取纹理对象的键的可迭代对象
|
||||
*/
|
||||
keys(): Iterable<number>;
|
||||
|
||||
/**
|
||||
* 获取纹理对象的值的可迭代对象
|
||||
*/
|
||||
values(): Iterable<ITexture>;
|
||||
|
||||
/**
|
||||
* 通过图像源创建贴图对象
|
||||
* @param source 贴图使用的图像源
|
||||
*/
|
||||
createTexture(source: SizedCanvasImageSource): ITexture;
|
||||
|
||||
/**
|
||||
* 添加一个贴图
|
||||
* @param identifier 贴图 id
|
||||
* @param texture 贴图对象
|
||||
*/
|
||||
addTexture(identifier: number, texture: ITexture): void;
|
||||
|
||||
/**
|
||||
* 移除一个贴图
|
||||
* @param identifier 要移除的贴图对象 id 或 别名 或 贴图对象
|
||||
*/
|
||||
removeTexture(identifier: number | string | ITexture): void;
|
||||
|
||||
/**
|
||||
* 根据贴图对象 id 获取贴图
|
||||
* @param identifier 贴图对象 id
|
||||
*/
|
||||
getTexture(identifier: number): ITexture | null;
|
||||
|
||||
/**
|
||||
* 给贴图对象命名一个别名
|
||||
* @param identifier 贴图对象 id
|
||||
* @param alias 要命名的别名
|
||||
*/
|
||||
alias(identifier: number, alias: string): void;
|
||||
|
||||
/**
|
||||
* 根据贴图对象别名获取贴图
|
||||
* @param alias 贴图对象别名
|
||||
*/
|
||||
fromAlias(alias: string): ITexture | null;
|
||||
|
||||
/**
|
||||
* 根据贴图对象获取此贴图对象在此控制器中的 id,如果贴图不在此控制器,返回 `undefined`
|
||||
* @param texture 贴图对象
|
||||
*/
|
||||
idOf(texture: ITexture): number | undefined;
|
||||
|
||||
/**
|
||||
* 根据贴图对象获取此贴图对象在此控制器中的别名,如果不存在此 id,返回 `undefined`
|
||||
* @param identifier 贴图 id
|
||||
*/
|
||||
aliasOf(identifier: number): string | undefined;
|
||||
}
|
46
packages/render-assets/src/utils.ts
Normal file
46
packages/render-assets/src/utils.ts
Normal file
@ -0,0 +1,46 @@
|
||||
import { logger } from '@motajs/common';
|
||||
|
||||
export function compileGLWith(
|
||||
gl: WebGL2RenderingContext,
|
||||
vert: string,
|
||||
frag: string
|
||||
): WebGLProgram | null {
|
||||
const vsShader = compileShader(gl, gl.VERTEX_SHADER, vert);
|
||||
const fsShader = compileShader(gl, gl.FRAGMENT_SHADER, frag);
|
||||
|
||||
if (!vsShader || !fsShader) return null;
|
||||
|
||||
const program = gl.createProgram();
|
||||
gl.attachShader(program, vsShader);
|
||||
gl.attachShader(program, fsShader);
|
||||
gl.linkProgram(program);
|
||||
|
||||
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
|
||||
const info = gl.getProgramInfoLog(program);
|
||||
logger.error(9, info ?? '');
|
||||
return null;
|
||||
}
|
||||
|
||||
return program;
|
||||
}
|
||||
|
||||
function compileShader(
|
||||
gl: WebGL2RenderingContext,
|
||||
type: number,
|
||||
source: string
|
||||
): WebGLShader | null {
|
||||
const shader = gl.createShader(type);
|
||||
if (!shader) return null;
|
||||
gl.shaderSource(shader, source);
|
||||
gl.compileShader(shader);
|
||||
|
||||
// 如果编译失败
|
||||
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
|
||||
const info = gl.getShaderInfoLog(shader);
|
||||
const typeStr = type === gl.VERTEX_SHADER ? 'vertex' : 'fragment';
|
||||
logger.error(10, typeStr, info ?? '');
|
||||
return null;
|
||||
}
|
||||
|
||||
return shader;
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
{
|
||||
"name": "@motajs/render-core",
|
||||
"dependencies": {
|
||||
"@motajs/common": "workspace:*"
|
||||
"@motajs/common": "workspace:*",
|
||||
"@motajs/render-assets": "workspace:*"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4,7 +4,7 @@ import { MotaOffscreenCanvas2D } from './canvas2d';
|
||||
import { ERenderItemEvent, RenderItem, RenderItemPosition } from './item';
|
||||
import { Transform } from './transform';
|
||||
import { isWebGL2Supported } from './utils';
|
||||
import { SizedCanvasImageSource } from './types';
|
||||
import { SizedCanvasImageSource } from '@motajs/render-assets';
|
||||
|
||||
export interface IGL2ProgramPrefix {
|
||||
readonly VERTEX: string;
|
||||
|
@ -8,5 +8,4 @@ export * from './render';
|
||||
export * from './shader';
|
||||
export * from './sprite';
|
||||
export * from './transform';
|
||||
export * from './types';
|
||||
export * from './utils';
|
||||
|
@ -1,6 +0,0 @@
|
||||
export type SizedCanvasImageSource = Exclude<
|
||||
CanvasImageSource,
|
||||
VideoFrame | SVGElement
|
||||
>;
|
||||
|
||||
export type CanvasStyle = string | CanvasGradient | CanvasPattern;
|
@ -2,9 +2,9 @@ import {
|
||||
Transform,
|
||||
ERenderItemEvent,
|
||||
RenderItem,
|
||||
MotaOffscreenCanvas2D,
|
||||
CanvasStyle
|
||||
MotaOffscreenCanvas2D
|
||||
} from '@motajs/render-core';
|
||||
import { CanvasStyle } from '@motajs/render-assets';
|
||||
import { logger } from '@motajs/common';
|
||||
import { clamp, isEqual, isNil } from 'lodash-es';
|
||||
|
||||
|
@ -5,13 +5,12 @@ import {
|
||||
Transform,
|
||||
MotaOffscreenCanvas2D
|
||||
} from '@motajs/render-core';
|
||||
import { CanvasStyle } from '@motajs/render-assets';
|
||||
import { Font } from '@motajs/render-style';
|
||||
|
||||
/** 文字的安全填充,会填充在文字的上侧和下侧,防止削顶和削底 */
|
||||
const SAFE_PAD = 1;
|
||||
|
||||
type CanvasStyle = string | CanvasGradient | CanvasPattern;
|
||||
|
||||
export interface ETextEvent extends ERenderItemEvent {
|
||||
setText: [text: string, width: number, height: number];
|
||||
}
|
||||
|
@ -7,9 +7,9 @@ import {
|
||||
ElementLocator,
|
||||
ElementScale,
|
||||
CustomContainerRenderFn,
|
||||
CustomContainerPropagateFn,
|
||||
CanvasStyle
|
||||
CustomContainerPropagateFn
|
||||
} from '@motajs/render-core';
|
||||
import { CanvasStyle } from '@motajs/render-assets';
|
||||
import {
|
||||
BezierParams,
|
||||
CircleParams,
|
||||
|
@ -1,3 +1,4 @@
|
||||
export * from '@motajs/render-assets';
|
||||
export * from '@motajs/render-core';
|
||||
export * from '@motajs/render-elements';
|
||||
export * from '@motajs/render-style';
|
||||
|
@ -44,6 +44,9 @@ importers:
|
||||
lz-string:
|
||||
specifier: ^1.5.0
|
||||
version: 1.5.0
|
||||
maxrects-packer:
|
||||
specifier: ^2.7.3
|
||||
version: 2.7.3
|
||||
mutate-animate:
|
||||
specifier: ^1.4.2
|
||||
version: 1.4.2
|
||||
@ -480,11 +483,16 @@ importers:
|
||||
specifier: ^3.5.13
|
||||
version: 3.5.20(typescript@5.9.2)
|
||||
|
||||
packages/render-assets: {}
|
||||
|
||||
packages/render-core:
|
||||
dependencies:
|
||||
'@motajs/common':
|
||||
specifier: workspace:*
|
||||
version: link:../common
|
||||
'@motajs/render-assets':
|
||||
specifier: workspace:*
|
||||
version: link:../render-assets
|
||||
|
||||
packages/render-elements:
|
||||
dependencies:
|
||||
@ -4513,6 +4521,9 @@ packages:
|
||||
mathjax-full@3.2.2:
|
||||
resolution: {integrity: sha512-+LfG9Fik+OuI8SLwsiR02IVdjcnRCy5MufYLi0C3TdMT56L/pjB0alMVGgoWJF8pN9Rc7FESycZB9BMNWIid5w==}
|
||||
|
||||
maxrects-packer@2.7.3:
|
||||
resolution: {integrity: sha512-bG6qXujJ1QgttZVIH4WDanhoJtvbud/xP/XPyf6A69C9RdA61BM4TomFALCq2nrTa+tARRIBB4LuIFsnUQU2wA==}
|
||||
|
||||
mdast-util-to-hast@13.2.0:
|
||||
resolution: {integrity: sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA==}
|
||||
|
||||
@ -10722,6 +10733,8 @@ snapshots:
|
||||
mj-context-menu: 0.6.1
|
||||
speech-rule-engine: 4.1.2
|
||||
|
||||
maxrects-packer@2.7.3: {}
|
||||
|
||||
mdast-util-to-hast@13.2.0:
|
||||
dependencies:
|
||||
'@types/hast': 3.0.4
|
||||
|
Loading…
Reference in New Issue
Block a user