refactor: 渲染系统

This commit is contained in:
AncTe committed 2026-03-16 12:23:02 +08:00
1 parent 9ab0db9465
commit 191ba8d1db
74 files changed
+3776 -7748

No files matched your search

+20 -62
View File
@@ -1,44 +1,29 @@
import { MotaOffscreenCanvas2D } from './canvas2d';
import { ActionType, EventProgress, ActionEventMap } from './event';
import {
ERenderItemEvent,
IRenderChildable,
RenderItem,
RenderItemPosition
} from './item';
import { RenderItem } from './item';
import { Transform } from './transform';
import {
CustomContainerPropagateFn,
CustomContainerRenderFn,
IRenderItem
} from './types';
export interface EContainerEvent extends ERenderItemEvent {}
export class Container<E extends EContainerEvent = EContainerEvent>
extends RenderItem<E | EContainerEvent>
implements IRenderChildable
{
export class Container extends RenderItem {
sortedChildren: RenderItem[] = [];
private needSort: boolean = false;
/**
* 创建一个容器,容器中可以包含其他渲染对象
* @param type 渲染模式,absolute表示绝对位置,static表示跟随摄像机移动
* @param cache 是否启用缓存机制
*/
constructor(
type: RenderItemPosition = 'static',
cache: boolean = true,
fall: boolean = false
) {
super(type, cache, fall);
this.type = type;
}
protected render(
canvas: MotaOffscreenCanvas2D,
transform: Transform
_transform: Transform
): void {
if (this.needSort) {
this.sortChildren();
this.needSort = false;
}
this.sortedChildren.forEach(v => {
if (v.hidden) return;
v.renderContent(canvas, transform);
v.renderContent(canvas);
});
}
@@ -48,28 +33,22 @@ export class Container<E extends EContainerEvent = EContainerEvent>
}
requestSort() {
if (!this.needSort) {
this.needSort = true;
this.requestBeforeFrame(() => {
this.needSort = false;
this.sortChildren();
});
}
this.needSort = true;
}
/**
* 添加子元素到这个容器上,然后在下一个tick执行更新
* @param children 要添加的子元素
*/
appendChild(...children: RenderItem<any>[]) {
appendChild(...children: IRenderItem[]) {
children.forEach(v => {
v.appendTo(this);
});
this.requestSort();
this.update(this);
this.update();
}
removeChild(...child: RenderItem<any>[]): void {
removeChild(...child: IRenderItem[]): void {
let changed = false;
child.forEach(v => {
if (v.parent !== this) return;
@@ -79,7 +58,7 @@ export class Container<E extends EContainerEvent = EContainerEvent>
}
});
if (changed) this.requestSort();
this.update(this);
this.update();
}
appendTo(parent: RenderItem): void {
@@ -96,7 +75,7 @@ export class Container<E extends EContainerEvent = EContainerEvent>
* 遍历这个元素中的每个子元素,并执行传入的函数
* @param fn 对每个元素执行的函数
*/
forEachChild(fn: (ele: RenderItem) => void) {
protected forEachChild(fn: (ele: RenderItem) => void) {
const stack: RenderItem[] = [this];
while (stack.length > 0) {
const ele = stack.pop()!;
@@ -109,7 +88,6 @@ export class Container<E extends EContainerEvent = EContainerEvent>
this.sortedChildren = [...this.children]
.filter(v => !v.isComment)
.sort((a, b) => a.zIndex - b.zIndex);
this.update();
}
protected propagateEvent<T extends ActionType>(
@@ -145,27 +123,7 @@ export class Container<E extends EContainerEvent = EContainerEvent>
}
}
export type CustomContainerRenderFn = (
canvas: MotaOffscreenCanvas2D,
children: RenderItem[],
transform: Transform
) => void;
export type CustomContainerPropagateOrigin = <T extends ActionType>(
type: T,
progress: EventProgress,
event: ActionEventMap[T]
) => void;
export type CustomContainerPropagateFn = <T extends ActionType>(
type: T,
progress: EventProgress,
event: ActionEventMap[T],
container: ContainerCustom,
origin: CustomContainerPropagateOrigin
) => void;
export class ContainerCustom extends Container {
export class CustomContainer extends Container {
private renderFn?: CustomContainerRenderFn;
private propagateFn?: CustomContainerPropagateFn;
+60
View File
@@ -0,0 +1,60 @@
import { RenderItem } from './item';
import { MotaOffscreenCanvas2D } from './canvas2d';
import { CustomRenderFunction } from './types';
import { Ref } from 'vue';
export class CustomRenderItem extends RenderItem {
renderFn: CustomRenderFunction;
/**
* 创建一个精灵,可以自由在上面渲染内容
* @param type 渲染模式,absolute表示绝对位置,不会跟随自身的Transform改变
* @param cache 是否启用缓存机制
*/
constructor(cache: boolean = true) {
super(cache);
this.renderFn = () => {};
}
protected render(canvas: MotaOffscreenCanvas2D): void {
canvas.ctx.save();
this.renderFn(canvas);
canvas.ctx.restore();
}
setRenderFn(fn: CustomRenderFunction) {
this.renderFn = fn;
this.update(this);
}
protected handleProps(
key: string,
prevValue: any,
nextValue: any
): boolean {
switch (key) {
case 'render':
if (!this.assertType(nextValue, 'function', key)) return false;
this.setRenderFn(nextValue);
return true;
case 'bindings':
if (!this.assertType(nextValue, Array, key)) return false;
if (nextValue !== prevValue) {
this.update();
} else if (nextValue.length !== prevValue.length) {
this.update();
} else {
const arr: Ref<any>[] = nextValue as Ref<any>[];
const prev: Ref<any>[] = prevValue as Ref<any>[];
for (let i = 0; i < nextValue.length; i++) {
if (arr[i].value !== prev[i].value) {
this.update();
break;
}
}
}
return true;
}
return false;
}
}
+2 -2
View File
@@ -1,4 +1,4 @@
import type { RenderItem } from './item';
import { IRenderItem } from './types';
export const enum MouseType {
/** 没有按键按下 */
@@ -51,7 +51,7 @@ export const enum EventProgress {
export interface IActionEventBase {
/** 当前事件是监听的哪个元素 */
target: RenderItem;
target: IRenderItem;
/** 是触摸操作还是鼠标操作 */
touch: boolean;
/**
+47 -443
View File
@@ -1,15 +1,33 @@
import EventEmitter from 'eventemitter3';
import { logger } from '@motajs/common';
import { MotaOffscreenCanvas2D } from './canvas2d';
import { ERenderItemEvent, RenderItem, RenderItemPosition } from './item';
import { RenderItem } from './item';
import { Transform } from './transform';
import { isWebGL2Supported } from './utils';
import { SizedCanvasImageSource } from '../types';
export interface IGL2ProgramPrefix {
readonly VERTEX: string;
readonly FRAGMENT: string;
}
import {
AttribSetFn,
AttribType,
DrawArraysInstancedParam,
DrawArraysParam,
DrawElementsInstancedParam,
DrawElementsParam,
DrawParamsMap,
IGL2Program,
IGL2ProgramPrefix,
IShaderAttrib,
IShaderAttribArray,
IShaderIndices,
IShaderTexture2D,
IShaderUniform,
IShaderUniformBlock,
IShaderUniformMatrix,
IWebGL2RenderItem,
ProgramConstructor,
RenderMode,
UniformMatrix,
UniformSetFn,
UniformType
} from './types';
const GL2_PREFIX: IGL2ProgramPrefix = {
VERTEX: /* glsl */ `#version 300 es
@@ -25,78 +43,7 @@ interface CompiledShader {
fragment: WebGLShader;
}
const enum RenderMode {
Arrays,
Elements,
ArraysInstanced,
ElementsInstanced
}
export const enum UniformType {
Uniform1f,
Uniform1fv,
Uniform1i,
Uniform1iv,
Uniform1ui,
Uniform1uiv,
Uniform2f,
Uniform2fv,
Uniform2i,
Uniform2iv,
Uniform2ui,
Uniform2uiv,
Uniform3f,
Uniform3fv,
Uniform3i,
Uniform3iv,
Uniform3ui,
Uniform3uiv,
Uniform4f,
Uniform4fv,
Uniform4i,
Uniform4iv,
Uniform4ui,
Uniform4uiv
}
export const enum UniformMatrix {
UMatrix2x2,
UMatrix2x3,
UMatrix2x4,
UMatrix3x2,
UMatrix3x3,
UMatrix3x4,
UMatrix4x2,
UMatrix4x3,
UMatrix4x4
}
export const enum AttribType {
Attrib1f,
Attrib1fv,
Attrib2f,
Attrib2fv,
Attrib3f,
Attrib3fv,
Attrib4f,
Attrib4fv,
AttribI4i,
AttribI4iv,
AttribI4ui,
AttribI4uiv
}
export type ProgramConstructor<T extends GL2Program> = new (
gl2: GL2,
vs?: string,
fs?: string
) => T;
export interface EGL2Event extends ERenderItemEvent {}
export abstract class GL2<E extends EGL2Event = EGL2Event> extends RenderItem<
EGL2Event | E
> {
export abstract class GL2 extends RenderItem {
/** 是否支持此组件 */
static readonly support: boolean = isWebGL2Supported();
@@ -161,14 +108,14 @@ export abstract class GL2<E extends EGL2Event = EGL2Event> extends RenderItem<
gl: WebGL2RenderingContext;
/** webgl使用的程序 */
protected program: GL2Program | null = null;
protected program: IGL2Program | null = null;
/** 当前渲染实例的所有着色器程序 */
protected programs: Set<GL2Program> = new Set();
protected programs: Set<IGL2Program> = new Set();
/** framebuffer 映射 */
protected framebufferMap: Map<string, WebGLFramebuffer> = new Map();
constructor(type: RenderItemPosition = 'static') {
super(type, false);
constructor() {
super(false);
this.canvas = document.createElement('canvas');
const gl = this.canvas.getContext('webgl2')!;
@@ -258,7 +205,7 @@ export abstract class GL2<E extends EGL2Event = EGL2Event> extends RenderItem<
* @param gl 当前正在渲染的 gl2 画布
* @param program 当前元素正在使用的着色器程序
*/
draw(gl: WebGL2RenderingContext, program: GL2Program) {
protected draw(gl: WebGL2RenderingContext, program: IGL2Program) {
const indices = program.usingIndices;
const param = program.getDrawParams(program.renderMode);
if (!param) return;
@@ -367,7 +314,7 @@ export abstract class GL2<E extends EGL2Event = EGL2Event> extends RenderItem<
* 切换着色器程序
* @param program 着色器程序
*/
useProgram(program: GL2Program) {
useProgram(program: IGL2Program) {
if (!this.gl) return;
if (program.element !== this) {
logger.error(17);
@@ -386,11 +333,11 @@ export abstract class GL2<E extends EGL2Event = EGL2Event> extends RenderItem<
* @param vs 顶点着色器,可选
* @param fs 片元着色器,可选
*/
createProgram<T extends GL2Program>(
createProgram<T extends IGL2Program>(
Program: ProgramConstructor<T>,
vs?: string,
fs?: string
) {
): T {
const program = new Program(this, vs, fs);
this.programs.add(program);
return program;
@@ -400,7 +347,7 @@ export abstract class GL2<E extends EGL2Event = EGL2Event> extends RenderItem<
* 删除一个着色器程序
* @param program 要删除的着色器程序
*/
deleteProgram(program: GL2Program) {
deleteProgram(program: IGL2Program) {
if (program.element !== this) {
logger.error(18);
return;
@@ -411,319 +358,18 @@ export abstract class GL2<E extends EGL2Event = EGL2Event> extends RenderItem<
destroy(): void {
this.programs.forEach(v => v.destroy());
this.programs.clear();
this.canvas.remove();
super.destroy();
}
}
type _U1 = [x0: number];
type _U2 = [x0: number, x1: number];
type _U3 = [x0: number, x1: number, x2: number];
type _U4 = [x0: number, x1: number, x2: number, x3: number];
type _UV<T> = [data: T, srcOffset?: number, srcLength?: number];
type _A<T> = [data: T];
interface UniformSetFn {
[UniformType.Uniform1f]: _U1;
[UniformType.Uniform1fv]: _UV<Float32List>;
[UniformType.Uniform1i]: _U1;
[UniformType.Uniform1iv]: _UV<Int32List>;
[UniformType.Uniform1ui]: _U1;
[UniformType.Uniform1uiv]: _UV<Uint32List>;
[UniformType.Uniform2f]: _U2;
[UniformType.Uniform2fv]: _UV<Float32List>;
[UniformType.Uniform2i]: _U2;
[UniformType.Uniform2iv]: _UV<Int32List>;
[UniformType.Uniform2ui]: _U2;
[UniformType.Uniform2uiv]: _UV<Uint32List>;
[UniformType.Uniform3f]: _U3;
[UniformType.Uniform3fv]: _UV<Float32List>;
[UniformType.Uniform3i]: _U3;
[UniformType.Uniform3iv]: _UV<Int32List>;
[UniformType.Uniform3ui]: _U3;
[UniformType.Uniform3uiv]: _UV<Uint32List>;
[UniformType.Uniform4f]: _U4;
[UniformType.Uniform4fv]: _UV<Float32List>;
[UniformType.Uniform4i]: _U4;
[UniformType.Uniform4iv]: _UV<Int32List>;
[UniformType.Uniform4ui]: _U4;
[UniformType.Uniform4uiv]: _UV<Uint32List>;
}
interface AttribSetFn {
[AttribType.Attrib1f]: _U1;
[AttribType.Attrib1fv]: _A<Float32List>;
[AttribType.Attrib2f]: _U2;
[AttribType.Attrib2fv]: _A<Float32List>;
[AttribType.Attrib3f]: _U3;
[AttribType.Attrib3fv]: _A<Float32List>;
[AttribType.Attrib4f]: _U4;
[AttribType.Attrib4fv]: _A<Float32List>;
[AttribType.AttribI4i]: _U4;
[AttribType.AttribI4iv]: _A<Int32List>;
[AttribType.AttribI4ui]: _U4;
[AttribType.AttribI4uiv]: _A<Uint32List>;
}
export interface IShaderUniform<T extends UniformType> {
/** 这个 uniform 变量的内存位置 */
readonly location: WebGLUniformLocation;
/** 这个 uniform 变量的类型 */
readonly type: T;
/** 这个量所处的着色器程序 */
readonly program: GL2Program;
/**
* 设置这个 uniform 变量的值,
* 参考 https://developer.mozilla.org/zh-CN/docs/Web/API/WebGL2RenderingContext/uniform
* @param params 要传递的参数,例如 uniform2f 就要传递 x0 x1 两个参数等,可以参考 mdn 文档
*/
set(...params: UniformSetFn[T]): void;
}
export interface IShaderAttrib<T extends AttribType> {
/** 这个 attribute 常量的内存位置 */
readonly location: number;
/** 这个 attribute 常量的类型 */
readonly type: T;
/** 这个量所处的着色器程序 */
readonly program: GL2Program;
/**
* 设置这个 attribute 常量的值,
* 浮点数参考 https://developer.mozilla.org/zh-CN/docs/Web/API/WebGLRenderingContext/vertexAttrib
* 整数参考 https://developer.mozilla.org/en-US/docs/Web/API/WebGL2RenderingContext/vertexAttribI
* @param params 要传递的参数
*/
set(...params: AttribSetFn[T]): void;
}
export interface IShaderAttribArray {
/** 这个 attribute 常量的内存位置 */
readonly location: number;
/** 这个 attribute 所用的缓冲区信息 */
readonly data: WebGLBuffer;
/** 这个量所处的着色器程序 */
readonly program: GL2Program;
/**
* 修改缓冲区数据,会更改数据大小,重新分配内存,不更改数据大小的情况下建议使用 {@link sub} 代替。
* 参考 https://developer.mozilla.org/zh-CN/docs/Web/API/WebGLRenderingContext/bufferData
* @param data 数据
* @param usage 用途
*/
buffer(data: AllowSharedBufferSource | null, usage: GLenum): void;
/**
* 修改缓冲区数据,会更改数据大小,重新分配内存,不更改数据大小的情况下建议使用 {@link sub} 代替。
* 参考 https://developer.mozilla.org/zh-CN/docs/Web/API/WebGLRenderingContext/bufferData
* @param data 数据
* @param usage 用途
* @param srcOffset 数据偏移量
* @param length 数据长度
*/
buffer(
data: ArrayBufferView,
usage: GLenum,
srcOffset: number,
length?: number
): void;
/**
* 修改缓冲区数据,但是不修改数据大小,不重新分配内存。
* 参考 https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/bufferSubData
* @param dstByteOffset 数据修改的起始位置
* @param srcData 数据
*/
sub(dstByteOffset: GLintptr, srcData: AllowSharedBufferSource): void;
/**
* 修改缓冲区数据,但是不修改数据大小,不重新分配内存。
* 参考 https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/bufferSubData
* @param dstByteOffset 数据修改的起始位置
* @param srcData 数据
* @param srcOffset 数据偏移量
* @param length 数据长度
*/
sub(
dstByteOffset: GLintptr,
srcData: ArrayBufferView,
srcOffset: number,
length?: GLuint
): void;
/**
* 告诉 gpu 将读取此 attribute 数据
* 参考 https://developer.mozilla.org/zh-CN/docs/Web/API/WebGLRenderingContext/vertexAttribPointer
* @param size 单个数据大小
* @param type 数据类型
* @param normalized 是否要经过归一化处理
* @param stride 每一部分字节偏移量
* @param offset 第一部分字节偏移量
*/
pointer(
size: GLint,
type: GLenum,
normalized: GLboolean,
stride: GLsizei,
offset: GLintptr
): void;
/**
* 告诉 gpu 将由整数类型读取此 attribute 数据
* 参考 https://developer.mozilla.org/en-US/docs/Web/API/WebGL2RenderingContext/vertexAttribIPointer
* @param size 单个数据大小
* @param type 数据类型
* @param stride 每一部分字节偏移量
* @param offset 第一部分字节偏移量
*/
pointerI(
size: GLint,
type: GLenum,
stride: GLsizei,
offset: GLintptr
): void;
/**
* 设置顶点指针更新时刻。
* 参考 https://developer.mozilla.org/en-US/docs/Web/API/WebGL2RenderingContext/vertexAttribDivisor
* @param divisor 每多少个实例更新一次,0表示每个顶点都更新
*/
divisor(divisor: number): void;
/**
* 启用这个顶点数据
*/
enable(): void;
/**
* 禁用这个顶点数据
*/
disable(): void;
}
export interface IShaderIndices {
/** 这个顶点索引所用的缓冲区信息 */
readonly data: WebGLBuffer;
/** 这个量所处的着色器程序 */
readonly program: GL2Program;
/**
* 修改缓冲区数据,会更改数据大小,重新分配内存,不更改数据大小的情况下建议使用 {@link sub} 代替。
* 参考 https://developer.mozilla.org/zh-CN/docs/Web/API/WebGLRenderingContext/bufferData
* @param data 数据
* @param usage 用途
*/
buffer(data: AllowSharedBufferSource | null, usage: GLenum): void;
/**
* 修改缓冲区数据,会更改数据大小,重新分配内存,不更改数据大小的情况下建议使用 {@link sub} 代替。
* 参考 https://developer.mozilla.org/zh-CN/docs/Web/API/WebGLRenderingContext/bufferData
* @param data 数据
* @param usage 用途
* @param srcOffset 数据偏移量
* @param length 数据长度
*/
buffer(
data: ArrayBufferView,
usage: GLenum,
srcOffset: number,
length?: number
): void;
/**
* 修改缓冲区数据,但是不修改数据大小,不重新分配内存。
* 参考 https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/bufferSubData
* @param dstByteOffset 数据修改的起始位置
* @param srcData 数据
*/
sub(dstByteOffset: GLintptr, srcData: AllowSharedBufferSource): void;
/**
* 修改缓冲区数据,但是不修改数据大小,不重新分配内存。
* 参考 https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/bufferSubData
* @param dstByteOffset 数据修改的起始位置
* @param srcData 数据
* @param srcOffset 数据偏移量
* @param length 数据长度
*/
sub(
dstByteOffset: GLintptr,
srcData: ArrayBufferView,
srcOffset: number,
length?: GLuint
): void;
}
export interface IShaderUniformMatrix {
/** 矩阵的内存位置 */
readonly location: WebGLUniformLocation;
/** 矩阵类型 */
readonly type: UniformMatrix;
/** 这个量所处的着色器程序 */
readonly program: GL2Program;
/**
* 设置矩阵的值,参考 https://developer.mozilla.org/zh-CN/docs/Web/API/WebGL2RenderingContext/uniformMatrix
* @param transpose 是否转置矩阵
* @param data 矩阵数据,列主序
* @param srcOffset 数据偏移量
* @param srcLength 数据长度
*/
set(
transpose: GLboolean,
data: Float32List,
srcOffset?: number,
srcLength?: number
): void;
}
export interface IShaderUniformBlock {
/** 这个 uniform block 的内存地址 */
readonly location: GLuint;
/** 与这个 uniform block 所绑定的缓冲区 */
readonly buffer: WebGLBuffer;
/** 这个 uniform block 的大小 */
readonly size: number;
/** 这个量所处的着色器程序 */
readonly program: GL2Program;
/**
* 参考 https://developer.mozilla.org/zh-CN/docs/Web/API/WebGL2RenderingContext/bindBufferBase
* @param srcData 要设置为的值
*/
set(srcData: AllowSharedBufferSource | null): void;
/**
* 参考 https://developer.mozilla.org/zh-CN/docs/Web/API/WebGL2RenderingContext/bindBufferBase
* @param srcData 要设置为的值
* @param srcOffset 数据偏移量
* @param length 数据长度
*/
set(srcData: ArrayBufferView, srcOffset: number, length?: number): void;
}
export interface IShaderTexture2D {
/** 纹理对象 */
readonly texture: WebGLTexture;
/** 宽度 */
readonly width: number;
/** 高度 */
readonly height: number;
/** 纹理所属索引 */
readonly index: number;
/** 这个量所处的着色器程序 */
readonly program: GL2Program;
/**
* 设置这个纹理的图像,不建议使用,会修改宽高
* @param source 要设置成的图像源
*/
set(source: TexImageSource): void;
/**
* 设置纹理的一部分信息,不会修改宽高
* @param source 要设置的图像源
* @param x 要设置到的起始点横坐标
* @param y 要设置到的起始点纵坐标
* @param width 宽度
* @param height 高度
*/
sub(
source: TexImageSource,
x: number,
y: number,
width: number,
height: number
): void;
}
class ShaderUniform<T extends UniformType> implements IShaderUniform<T> {
constructor(
readonly type: T,
readonly location: WebGLUniformLocation,
readonly gl: WebGL2RenderingContext,
readonly program: GL2Program
readonly program: IGL2Program
) {}
set(...params: UniformSetFn[T]): void {
@@ -812,7 +458,7 @@ class ShaderAttrib<T extends AttribType> implements IShaderAttrib<T> {
readonly type: T,
readonly location: number,
readonly gl: WebGL2RenderingContext,
readonly program: GL2Program
readonly program: IGL2Program
) {}
set(...params: AttribSetFn[T]) {
@@ -869,7 +515,7 @@ class ShaderAttribArray implements IShaderAttribArray {
readonly data: WebGLBuffer,
readonly location: number,
readonly gl: WebGL2RenderingContext,
readonly program: GL2Program
readonly program: IGL2Program
) {}
buffer(data: AllowSharedBufferSource | null, usage: GLenum): void;
@@ -954,7 +600,7 @@ class ShaderIndices implements IShaderIndices {
constructor(
readonly data: WebGLBuffer,
readonly gl: WebGL2RenderingContext,
readonly program: GL2Program
readonly program: IGL2Program
) {}
buffer(data: AllowSharedBufferSource | null, usage: GLenum): void;
@@ -999,7 +645,7 @@ class ShaderUniformMatrix implements IShaderUniformMatrix {
readonly type: UniformMatrix,
readonly location: WebGLUniformLocation,
readonly gl: WebGL2RenderingContext,
readonly program: GL2Program
readonly program: IGL2Program
) {}
set(x2: GLboolean, x3: Float32List, x4?: number, x5?: number): void {
@@ -1043,7 +689,7 @@ class ShaderUniformBlock implements IShaderUniformBlock {
readonly buffer: WebGLBuffer,
readonly binding: number,
readonly gl: WebGL2RenderingContext,
readonly program: GL2Program
readonly program: IGL2Program
) {}
set(srcData: AllowSharedBufferSource | null): void;
@@ -1070,7 +716,7 @@ class ShaderTexture2D implements IShaderTexture2D {
readonly index: number,
readonly uniform: IShaderUniform<UniformType.Uniform1i>,
readonly gl: WebGL2RenderingContext,
readonly program: GL2Program,
readonly program: IGL2Program,
public width: number = 0,
public height: number = 0
) {
@@ -1136,47 +782,7 @@ class ShaderTexture2D implements IShaderTexture2D {
}
}
interface DrawArraysParam {
mode: GLenum;
first: number;
count: number;
}
interface DrawElementsParam {
mode: GLenum;
count: number;
type: GLenum;
offset: GLintptr;
}
interface DrawArraysInstancedParam {
mode: GLenum;
first: number;
count: number;
instanceCount: number;
}
interface DrawElementsInstancedParam {
mode: GLenum;
count: number;
type: GLenum;
offset: GLintptr;
instanceCount: number;
}
export interface DrawParamsMap {
[RenderMode.Arrays]: DrawArraysParam;
[RenderMode.ArraysInstanced]: DrawArraysInstancedParam;
[RenderMode.Elements]: DrawElementsParam;
[RenderMode.ElementsInstanced]: DrawElementsInstancedParam;
}
interface ShaderProgramEvent {
load: [];
unload: [];
}
export class GL2Program extends EventEmitter<ShaderProgramEvent> {
export class GL2Program implements IGL2Program {
/** 顶点着色器 */
private vertex: string = '';
/** 片元着色器 */
@@ -1184,7 +790,7 @@ export class GL2Program extends EventEmitter<ShaderProgramEvent> {
/** webgl2上下文 */
gl: WebGL2RenderingContext;
/** 当前着色器程序的着色器渲染元素 */
element: GL2;
element: IWebGL2RenderItem;
/** uniform存放地址 */
private uniform: Map<string, IShaderUniform<UniformType>> = new Map();
@@ -1220,8 +826,7 @@ export class GL2Program extends EventEmitter<ShaderProgramEvent> {
/** 着色器代码的前缀,会在设置时自动添加至代码前 */
protected readonly prefix: IGL2ProgramPrefix = GL2_PREFIX;
constructor(shader: GL2, vs?: string, fs?: string) {
super();
constructor(shader: IWebGL2RenderItem, vs?: string, fs?: string) {
if (vs) this.vs(vs);
if (fs) this.fs(fs);
this.element = shader;
@@ -1377,6 +982,7 @@ export class GL2Program extends EventEmitter<ShaderProgramEvent> {
/**
* 检查当前是否需要重新编译着色器,如果需要,则重新编译
* @param force 是否强制重新编译
* @returns 是否执行了编译操作
*/
requestCompile(force: boolean = false): boolean {
if (!force && !this.shaderDirty) return false;
@@ -1408,7 +1014,6 @@ export class GL2Program extends EventEmitter<ShaderProgramEvent> {
this.attribArray.forEach(v => {
v.disable();
});
this.emit('load');
}
/**
@@ -1418,7 +1023,6 @@ export class GL2Program extends EventEmitter<ShaderProgramEvent> {
this.attribArray.forEach(v => {
v.enable();
});
this.emit('unload');
}
/**
@@ -1,100 +1,23 @@
import {
Transform,
ERenderItemEvent,
RenderItem,
MotaOffscreenCanvas2D
} from '../core';
import { Transform, RenderItem, MotaOffscreenCanvas2D } from '.';
import { CanvasStyle } from '../types';
import { logger } from '@motajs/common';
import { clamp, isEqual, isNil } from 'lodash-es';
export type CircleParams = [
cx?: number,
cy?: number,
radius?: number,
start?: number,
end?: number
];
export type EllipseParams = [
cx?: number,
cy?: number,
radiusX?: number,
radiusY?: number,
start?: number,
end?: number
];
export type LineParams = [x1: number, y1: number, x2: number, y2: number];
export type BezierParams = [
sx: number,
sy: number,
cp1x: number,
cp1y: number,
cp2x: number,
cp2y: number,
ex: number,
ey: number
];
export type QuadParams = [
sx: number,
sy: number,
cpx: number,
cpy: number,
ex: number,
ey: number
];
export type RectRCircleParams = [
r1: number,
r2?: number,
r3?: number,
r4?: number
];
export type RectREllipseParams = [
rx1: number,
ry1: number,
rx2?: number,
ry2?: number,
rx3?: number,
ry3?: number,
rx4?: number,
ry4?: number
];
export interface ILineProperty {
/** 线宽 */
lineWidth: number;
/** 线的虚线设置 */
lineDash?: number[];
/** 虚线偏移量 */
lineDashOffset?: number;
/** 线的连接样式 */
lineJoin: CanvasLineJoin;
/** 线的顶端样式 */
lineCap: CanvasLineCap;
/** 线的斜接限制,当连接为miter类型时可填,默认为10 */
miterLimit: number;
}
export interface IGraphicProperty extends ILineProperty {
/** 渲染模式,参考 {@link GraphicMode} */
mode: GraphicMode;
/** 填充样式 */
fill: CanvasStyle;
/** 描边样式 */
stroke: CanvasStyle;
/** 填充算法 */
fillRule: CanvasFillRule;
}
export const enum GraphicMode {
/** 仅填充 */
Fill,
/** 仅描边 */
Stroke,
/** 先填充,然后描边 */
FillAndStroke,
/** 先描边,然后填充 */
StrokeAndFill
}
import {
ILineProperty,
GraphicMode,
IGraphicRenderItem,
IGraphicCircle,
IGraphicEllipse,
IGraphicLine,
IGraphicBezierCurve,
IGraphicQuadBezierCurve,
IGraphicPath,
RectRCorner,
CircleParams,
EllipseParams,
RectRCircleParams,
RectREllipseParams
} from './types';
const enum GraphicModeProp {
Fill,
@@ -102,11 +25,13 @@ const enum GraphicModeProp {
StrokeAndFill
}
export interface EGraphicItemEvent extends ERenderItemEvent {}
/** 用于点击检测的画布 */
const testCanvas = new MotaOffscreenCanvas2D(false);
testCanvas.size(1, 1);
export abstract class GraphicItemBase
extends RenderItem<EGraphicItemEvent>
implements Required<ILineProperty>
extends RenderItem
implements Required<ILineProperty>, IGraphicRenderItem
{
mode: GraphicMode = GraphicMode.Fill;
fill: CanvasStyle = '#ddd';
@@ -118,7 +43,6 @@ export abstract class GraphicItemBase
lineCap: CanvasLineCap = 'butt';
miterLimit: number = 10;
fillRule: CanvasFillRule = 'nonzero';
enableCache: boolean = false;
private propFill: boolean = true;
private propStroke: boolean = false;
@@ -129,6 +53,10 @@ export abstract class GraphicItemBase
private cachePath?: Path2D;
protected pathDirty: boolean = true;
constructor(enableCache: boolean = false) {
super(enableCache);
}
/**
* 获取这个元素的绘制路径
*/
@@ -166,7 +94,7 @@ export abstract class GraphicItemBase
}
protected isActionInElement(x: number, y: number): boolean {
const ctx = this.cache.ctx;
const ctx = testCanvas.ctx;
if (this.pathDirty) {
this.cachePath = this.getPath();
this.pathDirty = false;
@@ -387,7 +315,7 @@ export class Rect extends GraphicItemBase {
}
}
export class Circle extends GraphicItemBase {
export class Circle extends GraphicItemBase implements IGraphicCircle {
radius: number = 10;
start: number = 0;
end: number = Math.PI * 2;
@@ -462,7 +390,7 @@ export class Circle extends GraphicItemBase {
}
}
export class Ellipse extends GraphicItemBase {
export class Ellipse extends GraphicItemBase implements IGraphicEllipse {
radiusX: number = 10;
radiusY: number = 10;
start: number = 0;
@@ -552,7 +480,7 @@ export class Ellipse extends GraphicItemBase {
}
}
export class Line extends GraphicItemBase {
export class Line extends GraphicItemBase implements IGraphicLine {
x1: number = 0;
y1: number = 0;
x2: number = 0;
@@ -633,7 +561,10 @@ export class Line extends GraphicItemBase {
}
}
export class BezierCurve extends GraphicItemBase {
export class BezierCurve
extends GraphicItemBase
implements IGraphicBezierCurve
{
sx: number = 0;
sy: number = 0;
cp1x: number = 0;
@@ -700,10 +631,6 @@ export class BezierCurve extends GraphicItemBase {
this.update();
}
protected isActionInElement(x: number, y: number): boolean {
return x >= 0 && x < this.width && y >= 0 && y < this.height;
}
private fitRect() {
const left = Math.min(this.sx, this.cp1x, this.cp2x, this.ex);
const top = Math.min(this.sy, this.cp1y, this.cp2y, this.ey);
@@ -767,7 +694,10 @@ export class BezierCurve extends GraphicItemBase {
}
}
export class QuadraticCurve extends GraphicItemBase {
export class QuadraticCurve
extends GraphicItemBase
implements IGraphicQuadBezierCurve
{
sx: number = 0;
sy: number = 0;
cpx: number = 0;
@@ -842,10 +772,6 @@ export class QuadraticCurve extends GraphicItemBase {
this.pathDirty = true;
}
protected isActionInElement(x: number, y: number): boolean {
return x >= 0 && x < this.width && y >= 0 && y < this.height;
}
protected handleProps(
key: string,
prevValue: any,
@@ -890,7 +816,7 @@ export class QuadraticCurve extends GraphicItemBase {
}
}
export class Path extends GraphicItemBase {
export class Path extends GraphicItemBase implements IGraphicPath {
/** 路径 */
path: Path2D = new Path2D();
@@ -901,6 +827,14 @@ export class Path extends GraphicItemBase {
return this.path;
}
/**
* 重置此元素的路径
*/
resetPath() {
this.path = new Path2D();
this.update();
}
/**
* 为路径添加路径
* @param path 要添加的路径
@@ -911,10 +845,6 @@ export class Path extends GraphicItemBase {
this.update();
}
protected isActionInElement(x: number, y: number): boolean {
return x >= 0 && x < this.width && y >= 0 && y < this.height;
}
protected handleProps(
key: string,
prevValue: any,
@@ -932,13 +862,6 @@ export class Path extends GraphicItemBase {
}
}
export const enum RectRCorner {
TopLeft,
TopRight,
BottomRight,
BottomLeft
}
export class RectR extends GraphicItemBase {
/** 圆角属性,四元素数组,每个元素是一个二元素数组,表示这个角的半径,顺序为 左上,右上,右下,左下 */
readonly corner: [radiusX: number, radiusY: number][] = [
+4 -1
View File
@@ -1,11 +1,14 @@
export * from './adapter';
export * from './canvas2d';
export * from './container';
export * from './custom';
export * from './event';
export * from './gl2';
export * from './graphics';
export * from './item';
export * from './misc';
export * from './render';
export * from './shader';
export * from './sprite';
export * from './transform';
export * from './types';
export * from './utils';
File diff suppressed because it is too large. Load diff
@@ -1,21 +1,12 @@
import {
ERenderItemEvent,
RenderItem,
RenderItemPosition,
Transform,
MotaOffscreenCanvas2D
} from '../core';
import { RenderItem, Transform, MotaOffscreenCanvas2D } from '.';
import { CanvasStyle } from '../types';
import { Font } from '../style';
import { IRenderImage, IRenderText } from './types';
/** 文字的安全填充,会填充在文字的上侧和下侧,防止削顶和削底 */
const SAFE_PAD = 1;
export interface ETextEvent extends ERenderItemEvent {
setText: [text: string, width: number, height: number];
}
export class Text extends RenderItem<ETextEvent> {
export class Text extends RenderItem implements IRenderText {
text: string;
fillStyle?: CanvasStyle = '#fff';
@@ -28,14 +19,13 @@ export class Text extends RenderItem<ETextEvent> {
private static measureCanvas = new MotaOffscreenCanvas2D();
constructor(text: string = '', type: RenderItemPosition = 'static') {
super(type, false);
constructor(text: string = '', enableCache: boolean = false) {
super(enableCache);
this.text = text;
if (text.length > 0) {
this.requestBeforeFrame(() => {
this.calBox();
this.emit('setText', text, this.width, this.height);
});
}
}
@@ -79,8 +69,7 @@ export class Text extends RenderItem<ETextEvent> {
setText(text: string) {
this.text = text;
this.calBox();
this.update(this);
this.emit('setText', text, this.width, this.height);
this.update();
}
/**
@@ -157,13 +146,11 @@ export class Text extends RenderItem<ETextEvent> {
}
}
export interface EImageEvent extends ERenderItemEvent {}
export class Image extends RenderItem<EImageEvent> {
export class Image extends RenderItem implements IRenderImage {
image: CanvasImageSource;
constructor(image: CanvasImageSource, type: RenderItemPosition = 'static') {
super(type, false);
constructor(image: CanvasImageSource, enableCache: boolean = false) {
super(enableCache);
this.image = image;
if (image instanceof VideoFrame || image instanceof SVGElement) {
this.size(200, 200);
@@ -207,7 +194,7 @@ export class Comment extends RenderItem {
readonly isComment: boolean = true;
constructor(public text: string = '') {
super('static', false, false);
super(false);
this.hide();
}
@@ -219,8 +206,4 @@ export class Comment extends RenderItem {
_canvas: MotaOffscreenCanvas2D,
_transform: Transform
): void {}
protected handleProps(): boolean {
return false;
}
}
+361 -128
View File
@@ -1,6 +1,6 @@
import { logger } from '@motajs/common';
import { MotaOffscreenCanvas2D } from './canvas2d';
import { Container } from './container';
import { Container, CustomContainer } from './container';
import {
ActionType,
IActionEvent,
@@ -9,41 +9,68 @@ import {
MouseType,
WheelType
} from './event';
import { IRenderTreeRoot, RenderItem } from './item';
import { Transform } from './transform';
import {
IMotaRendererConfig,
RenderItemTags,
IRenderItem,
IRenderItemInstanceMap,
IRenderItemParameterMap,
IRenderTreeRoot,
RenderItemConstructor,
ExcitableDelegation
} from './types';
import { IExcitable, IExcitation, RafExcitation } from '@motajs/animate';
import { CustomRenderItem } from './custom';
import { Comment, Image, Text } from './misc';
import { Shader } from './shader';
import {
BezierCurve,
Circle,
Ellipse,
Line,
Path,
QuadraticCurve,
Rect,
RectR
} from './graphics';
interface TouchInfo {
/** 这次触摸在渲染系统的标识符 */
identifier: number;
readonly identifier: number;
/** 浏览器的 clientX,用于判断这个触点有没有移动 */
clientX: number;
readonly clientX: number;
/** 浏览器的 clientY,用于判断这个触点有没有移动 */
clientY: number;
readonly clientY: number;
/** 是否覆盖在了当前元素上 */
hovered: boolean;
readonly hovered: boolean;
}
interface MouseInfo {
/** 这个鼠标按键的标识符 */
identifier: number;
readonly identifier: number;
}
export interface MotaRendererConfig {
/** 要挂载到哪个画布上,可以填 css 选择器或画布元素本身 */
canvas: string | HTMLCanvasElement;
/** 画布的宽度,所有渲染操作会自行适配缩放 */
width: number;
/** 画布的高度,所有渲染操作会自行适配缩放 */
height: number;
/** 是否启用不透明度通道,默认启用 */
alpha?: boolean;
interface DelegatedExcitable extends IExcitable<number> {
/** 委托内容的 id */
readonly id: number;
/** 委托内容的原始对象 */
readonly obj: ExcitableDelegation;
/** 委托 excitable 是否永久执行 */
readonly forever: boolean;
/** 委托 excitable 的开始时刻 */
readonly startTime: number;
/** 委托 excitable 的持续时间 */
readonly time: number;
/** 委托结束时执行的函数 */
readonly end?: () => void;
}
export class MotaRenderer extends Container implements IRenderTreeRoot {
static list: Map<string, MotaRenderer> = new Map();
export class MotaRenderer
extends Container
implements IRenderTreeRoot, IExcitable<number>
{
/** 所有连接到此根元素的渲染元素的 id 到元素自身的映射 */
protected idMap: Map<string, RenderItem> = new Map();
protected readonly idMap: Map<string, IRenderItem> = new Map();
/** 最后一次按下的鼠标按键,用于处理鼠标移动 */
private lastMouse: MouseType = MouseType.None;
@@ -61,16 +88,40 @@ export class MotaRenderer extends Container implements IRenderTreeRoot {
/** 根据捕获行为判断光标样式 */
private targetCursor: string = 'auto';
/** 当前鼠标覆盖的元素 */
private hoveredElement: Set<RenderItem> = new Set();
private hoveredElement: Set<IRenderItem> = new Set();
/** 本次交互前鼠标覆盖的元素 */
private beforeHovered: Set<RenderItem> = new Set();
target!: MotaOffscreenCanvas2D;
private beforeHovered: Set<IRenderItem> = new Set();
/** 渲染至的目标画布 */
readonly target!: MotaOffscreenCanvas2D;
/** 当前元素是根元素 */
readonly isRoot = true;
constructor(config: MotaRendererConfig) {
super('static', false);
/** 当前元素的激励源 */
readonly excitation!: IExcitation<number>;
/** 下一帧之前需要执行的内容 */
private readonly beforeFrame: Set<() => void> = new Set();
/** 下一帧之后需要执行的内容 */
private readonly afterFrame: Set<() => void> = new Set();
/** 委托 excitables 的计数器 */
private delegationCounter: number = 0;
/** 委托执行的 excitables */
private readonly excitables: Map<number, DelegatedExcitable> = new Map();
/** 委托执行的 excitables 到其 id 的映射 */
private readonly excitablesMap: Map<
ExcitableDelegation,
DelegatedExcitable
> = new Map();
/** 执行完毕需要删除的 excitables */
private readonly toDeleteExcitables: Set<number> = new Set();
/** 标签注册信息 */
private readonly tagRegistry: Map<string, RenderItemConstructor> =
new Map();
constructor(config: IMotaRendererConfig) {
super(false);
const canvas = this.getMountCanvas(config.canvas);
if (!canvas) {
@@ -80,24 +131,132 @@ export class MotaRenderer extends Container implements IRenderTreeRoot {
this.target = new MotaOffscreenCanvas2D(config.alpha ?? true, canvas);
this.size(config.width, config.height);
this.target.setAntiAliasing(false);
if (config.excitaion) {
this.excitation = config.excitaion;
} else {
this.excitation = new RafExcitation();
}
this.setAnchor(0.5, 0.5);
MotaRenderer.list.set(canvas.id, this);
const update = () => {
this.requestRenderFrame(() => {
this.refresh();
update();
});
};
update();
this.listen();
this.setScale(1);
this.excited = this.excited.bind(this);
this.excitation.add(this);
this.registerIntrinsicTags();
}
//#region 渲染相关
excited(payload: number): void {
this.beforeFrame.forEach(v => v());
this.beforeFrame.clear();
this.refresh();
this.afterFrame.forEach(v => v());
this.afterFrame.clear();
this.excitables.forEach((ex, key) => {
if (!ex.forever && payload - ex.startTime >= ex.time) {
this.toDeleteExcitables.add(key);
ex.end?.();
} else {
ex.excited(payload);
}
});
this.toDeleteExcitables.forEach(key => this.excitables.delete(key));
}
private getMountCanvas(
canvas: string | HTMLCanvasElement
): HTMLCanvasElement | undefined {
if (typeof canvas === 'string') {
return document.querySelector(canvas) as HTMLCanvasElement;
} else {
return canvas;
}
}
update(_item: IRenderItem = this) {
this.cacheDirty = true;
}
protected refresh(): void {
if (!this.cacheDirty) return;
this.target.clear();
this.renderContent(this.target);
}
getCanvas(): HTMLCanvasElement {
return this.target.canvas;
}
//#endregion
//#region 标签元素
createElement<K extends RenderItemTags>(
tag: K,
...params: IRenderItemParameterMap[K]
): IRenderItemInstanceMap[K];
createElement<P extends any[], I extends IRenderItem>(
ele: new (...params: P) => I,
...params: P
): I;
createElement(
ele: RenderItemTags | RenderItemConstructor,
...params: any[]
): IRenderItem {
if (typeof ele === 'string') {
const Cons = this.tagRegistry.get(ele);
if (!Cons) {
logger.error(15, ele);
return new Container(false);
} else {
return new Cons(...params);
}
} else {
return new ele(...params);
}
}
registerElement(tag: string, cons: RenderItemConstructor): void {
if (this.tagRegistry.has(tag)) {
logger.error(14);
return;
} else {
this.tagRegistry.set(tag, cons);
}
}
hasTag(tag: string): boolean {
return this.tagRegistry.has(tag);
}
/**
* 注册内置渲染标签
*/
private registerIntrinsicTags() {
this.registerElement('container', Container);
this.registerElement('custom', CustomRenderItem);
this.registerElement('text', Text);
this.registerElement('image', Image);
this.registerElement('shader', Shader);
this.registerElement('comment', Comment);
this.registerElement('template', Container);
this.registerElement('custom-container', CustomContainer);
this.registerElement('g-rect', Rect);
this.registerElement('g-circle', Circle);
this.registerElement('g-ellipse', Ellipse);
this.registerElement('g-line', Line);
this.registerElement('g-bezier', BezierCurve);
this.registerElement('g-quad', QuadraticCurve);
this.registerElement('g-path', Path);
this.registerElement('g-rectr', RectR);
}
//#endregion
//#region 尺寸缩放
/**
* 设置这个渲染器的缩放比
* @param scale 缩放比
@@ -106,6 +265,10 @@ export class MotaRenderer extends Container implements IRenderTreeRoot {
this.onResize(scale);
}
getScale() {
return this.target.scale;
}
onResize(scale: number): void {
this.target.setScale(scale);
const width = this.target.width * scale;
@@ -115,23 +278,20 @@ export class MotaRenderer extends Container implements IRenderTreeRoot {
super.onResize(scale);
}
private getMountCanvas(canvas: string | HTMLCanvasElement) {
if (typeof canvas === 'string') {
return document.querySelector(canvas) as HTMLCanvasElement;
} else {
return canvas;
}
}
size(width: number, height: number): void {
super.size(width, height);
this.target.size(width, height);
this.transform.setTranslate(width / 2, height / 2);
}
//#endregion
//#region 事件处理
private listen() {
// 画布监听
const canvas = this.target.canvas;
canvas.addEventListener('mousedown', ev => {
const mouse = this.getMouseType(ev);
this.lastMouse = mouse;
@@ -178,47 +338,6 @@ export class MotaRenderer extends Container implements IRenderTreeRoot {
this.hoveredElement.clear();
this.beforeHovered.clear();
});
document.addEventListener('touchstart', ev => {
this.createTouchAction(ev, ActionType.Down).forEach(v => {
this.captureEvent(ActionType.Down, v);
});
});
document.addEventListener('touchend', ev => {
this.createTouchAction(ev, ActionType.Up).forEach(v => {
this.captureEvent(ActionType.Up, v);
this.captureEvent(ActionType.Click, v);
});
[...ev.touches].forEach(v => {
this.touchInfo.delete(v.identifier);
});
});
document.addEventListener('touchcancel', ev => {
this.createTouchAction(ev, ActionType.Up).forEach(v => {
this.captureEvent(ActionType.Up, v);
});
[...ev.touches].forEach(v => {
this.touchInfo.delete(v.identifier);
});
});
document.addEventListener('touchmove', ev => {
this.createTouchAction(ev, ActionType.Move).forEach(v => {
const list = this.touchInfo.values();
if (!list.some(vv => v.identifier === vv.identifier)) {
return;
}
const temp = this.beforeHovered;
temp.clear();
this.beforeHovered = this.hoveredElement;
this.hoveredElement = temp;
this.captureEvent(ActionType.Move, v);
this.checkTouchEnterLeave(
ev,
v,
this.beforeHovered,
this.hoveredElement
);
});
});
canvas.addEventListener('wheel', ev => {
this.captureEvent(
ActionType.Wheel,
@@ -240,6 +359,70 @@ export class MotaRenderer extends Container implements IRenderTreeRoot {
document.addEventListener('click', clear, { signal });
document.addEventListener('mouseenter', clear, { signal });
document.addEventListener('mouseleave', clear, { signal });
window.addEventListener(
'resize',
() => {
this.requestAfterFrame(() => this.refreshAllChildren());
},
{ signal }
);
document.addEventListener(
'touchstart',
ev => {
this.createTouchAction(ev, ActionType.Down).forEach(v => {
this.captureEvent(ActionType.Down, v);
});
},
{ signal }
);
document.addEventListener(
'touchend',
ev => {
this.createTouchAction(ev, ActionType.Up).forEach(v => {
this.captureEvent(ActionType.Up, v);
this.captureEvent(ActionType.Click, v);
});
[...ev.touches].forEach(v => {
this.touchInfo.delete(v.identifier);
});
},
{ signal }
);
document.addEventListener(
'touchcancel',
ev => {
this.createTouchAction(ev, ActionType.Up).forEach(v => {
this.captureEvent(ActionType.Up, v);
});
[...ev.touches].forEach(v => {
this.touchInfo.delete(v.identifier);
});
},
{ signal }
);
document.addEventListener(
'touchmove',
ev => {
this.createTouchAction(ev, ActionType.Move).forEach(v => {
const list = this.touchInfo.values();
if (!list.some(vv => v.identifier === vv.identifier)) {
return;
}
const temp = this.beforeHovered;
temp.clear();
this.beforeHovered = this.hoveredElement;
this.hoveredElement = temp;
this.captureEvent(ActionType.Move, v);
this.checkTouchEnterLeave(
ev,
v,
this.beforeHovered,
this.hoveredElement
);
});
},
{ signal }
);
}
private isTouchInCanvas(clientX: number, clientY: number) {
@@ -312,7 +495,7 @@ export class MotaRenderer extends Container implements IRenderTreeRoot {
private createMouseActionBase(
event: MouseEvent,
id: number,
target: RenderItem = this,
target: IRenderItem = this,
mouse: MouseType = this.getMouseType(event)
): IActionEventBase {
return {
@@ -331,7 +514,7 @@ export class MotaRenderer extends Container implements IRenderTreeRoot {
private createTouchActionBase(
event: TouchEvent,
id: number,
target: RenderItem
target: IRenderItem
): IActionEventBase {
return {
identifier: id,
@@ -488,8 +671,8 @@ export class MotaRenderer extends Container implements IRenderTreeRoot {
private checkMouseEnterLeave(
event: MouseEvent,
ev: IActionEvent,
before: Set<RenderItem>,
now: Set<RenderItem>
before: Set<IRenderItem>,
now: Set<IRenderItem>
) {
// 先 leave,再 enter
before.forEach(v => {
@@ -513,8 +696,8 @@ export class MotaRenderer extends Container implements IRenderTreeRoot {
private checkTouchEnterLeave(
event: TouchEvent,
ev: IActionEvent,
before: Set<RenderItem>,
now: Set<RenderItem>
before: Set<IRenderItem>,
now: Set<IRenderItem>
) {
// 先 leave,再 enter
before.forEach(v => {
@@ -535,22 +718,16 @@ export class MotaRenderer extends Container implements IRenderTreeRoot {
});
}
update(_item: RenderItem = this) {
this.cacheDirty = true;
}
//#endregion
protected refresh(): void {
if (!this.cacheDirty) return;
this.target.clear();
this.renderContent(this.target, Transform.identity);
}
//#region 元素处理
/**
* 根据渲染元素的id获取一个渲染元素
* @param id 要获取的渲染元素id
* @returns
*/
getElementById(id: string): RenderItem | null {
getElementById(id: string): IRenderItem | null {
if (id.length === 0) return null;
const item = this.idMap.get(id);
if (item) return item;
@@ -563,7 +740,7 @@ export class MotaRenderer extends Container implements IRenderTreeRoot {
}
}
private searchElement(ele: RenderItem, id: string): RenderItem | null {
private searchElement(ele: IRenderItem, id: string): IRenderItem | null {
for (const child of ele.children) {
if (child.id === id) return child;
else {
@@ -574,7 +751,7 @@ export class MotaRenderer extends Container implements IRenderTreeRoot {
return null;
}
connect(item: RenderItem): void {
connect(item: IRenderItem): void {
if (item.id.length === 0) return;
const existed = this.idMap.get(item.id);
if (existed) {
@@ -585,11 +762,11 @@ export class MotaRenderer extends Container implements IRenderTreeRoot {
}
}
disconnect(item: RenderItem): void {
disconnect(item: IRenderItem): void {
this.idMap.delete(item.id);
}
modifyId(item: RenderItem, previous: string, current: string): void {
modifyId(item: IRenderItem, previous: string, current: string): void {
this.idMap.delete(previous);
if (current.length !== 0) {
if (this.idMap.has(item.id)) {
@@ -600,24 +777,93 @@ export class MotaRenderer extends Container implements IRenderTreeRoot {
}
}
getCanvas(): HTMLCanvasElement {
return this.target.canvas;
}
hoverElement(element: RenderItem): void {
hoverElement(element: IRenderItem): void {
if (element.cursor !== 'inherit') {
this.targetCursor = element.cursor;
}
this.hoveredElement.add(element);
}
//#endregion
//#region 渲染绑定
requestBeforeFrame(fn: () => void): void {
this.beforeFrame.add(fn);
}
requestAfterFrame(fn: () => void): void {
this.afterFrame.add(fn);
}
delegateExcitable(
fn: ExcitableDelegation,
time?: number,
end?: () => void
): number {
const index = this.delegationCounter++;
const info: DelegatedExcitable = {
id: index,
obj: fn,
excited: typeof fn === 'function' ? fn : fn.excited,
startTime: this.excitation.payload(),
time: time ?? 0,
forever: time === void 0,
end
};
this.excitables.set(index, info);
this.excitablesMap.set(fn, info);
return index;
}
removeExcitable(id: number, callEnd: boolean = true): boolean {
const info = this.excitables.get(id);
if (!info) return false;
if (callEnd) {
info.end?.();
}
this.excitables.delete(id);
this.excitablesMap.delete(info.obj);
return true;
}
removeExcitableObject(
excitable: ExcitableDelegation,
callEnd: boolean = true
): boolean {
const info = this.excitablesMap.get(excitable);
if (!info) return false;
if (callEnd) {
info.end?.();
}
this.excitables.delete(info.id);
this.excitablesMap.delete(info.obj);
return true;
}
hasExcitable(id: number): boolean {
return this.excitables.has(id);
}
//#endregion
//#region 元素销毁
destroy() {
super.destroy();
MotaRenderer.list.delete(this.id);
this.excitation.destroy();
this.abort?.abort();
}
private toTagString(item: RenderItem, space: number, deep: number): string {
//#endregion
//#region 调试功能
private toTagString(
item: IRenderItem,
space: number,
deep: number
): string {
if (item.isComment) return '';
const name = item.constructor.name;
if (item.children.size === 0) {
@@ -647,18 +893,5 @@ export class MotaRenderer extends Container implements IRenderTreeRoot {
return this.toTagString(this, space, 0);
}
static get(id: string) {
return this.list.get(id);
}
//#endregion
}
window.addEventListener('resize', () => {
MotaRenderer.list.forEach(v =>
v.requestAfterFrame(() => v.refreshAllChildren())
);
});
// @ts-expect-error debug
window.logTagTree = () => {
console.log(MotaRenderer.get('render-main')?.toTagTree());
};
+4 -12
View File
@@ -1,6 +1,6 @@
import { MotaOffscreenCanvas2D } from './canvas2d';
import { EGL2Event, GL2, GL2Program, IGL2ProgramPrefix } from './gl2';
import { RenderItemPosition } from './item';
import { GL2, GL2Program } from './gl2';
import { IGL2ProgramPrefix, IWebGL2RenderItem } from './types';
const SHADER_PREFIX: IGL2ProgramPrefix = {
VERTEX: /* glsl */ `#version 300 es
@@ -34,15 +34,7 @@ void main() {
}
`;
export interface EShaderEvent extends EGL2Event {}
export class Shader<E extends EShaderEvent = EShaderEvent> extends GL2<
EShaderEvent | E
> {
constructor(type: RenderItemPosition = 'static') {
super(type);
}
export class Shader extends GL2 {
protected drawScene(
canvas: MotaOffscreenCanvas2D,
gl: WebGL2RenderingContext
@@ -57,7 +49,7 @@ export class Shader<E extends EShaderEvent = EShaderEvent> extends GL2<
export class ShaderProgram extends GL2Program {
protected readonly prefix: IGL2ProgramPrefix = SHADER_PREFIX;
constructor(gl2: GL2, vs?: string, fs?: string) {
constructor(gl2: IWebGL2RenderItem, vs?: string, fs?: string) {
super(gl2, vs, fs);
if (!vs) this.vs(DEFAULT_VS);
if (!fs) this.fs(DEFAULT_FS);
-59
View File
@@ -1,59 +0,0 @@
import {
ERenderItemEvent,
RenderFunction,
RenderItem,
RenderItemPosition
} from './item';
import { MotaOffscreenCanvas2D } from './canvas2d';
import { Transform } from './transform';
export interface ESpriteEvent extends ERenderItemEvent {}
export class Sprite<
E extends ESpriteEvent = ESpriteEvent
> extends RenderItem<E> {
renderFn: RenderFunction;
/**
* 创建一个精灵,可以自由在上面渲染内容
* @param type 渲染模式,absolute表示绝对位置,不会跟随自身的Transform改变
* @param cache 是否启用缓存机制
*/
constructor(
type: RenderItemPosition = 'static',
cache: boolean = true,
fall: boolean = false
) {
super(type, cache, fall);
this.type = type;
this.renderFn = () => {};
}
protected render(
canvas: MotaOffscreenCanvas2D,
transform: Transform
): void {
canvas.ctx.save();
this.renderFn(canvas, transform);
canvas.ctx.restore();
}
setRenderFn(fn: RenderFunction) {
this.renderFn = fn;
this.update(this);
}
protected handleProps(
key: string,
_prevValue: any,
nextValue: any
): boolean {
switch (key) {
case 'render':
if (!this.assertType(nextValue, 'function', key)) return false;
this.setRenderFn(nextValue);
return true;
}
return false;
}
}
File diff suppressed because it is too large. Load diff
-50
View File
@@ -1,34 +1,6 @@
import { TimingFn } from 'mutate-animate';
import { JSX } from 'vue/jsx-runtime';
import { DefineComponent, DefineSetupFnComponent } from 'vue';
import { MotaOffscreenCanvas2D } from './canvas2d';
import { Transform } from './transform';
export type Props<
T extends
| keyof JSX.IntrinsicElements
| DefineSetupFnComponent<any>
| DefineComponent
> = T extends keyof JSX.IntrinsicElements
? JSX.IntrinsicElements[T]
: T extends DefineSetupFnComponent<any>
? InstanceType<T>['$props'] & InstanceType<T>['$emits']
: T extends DefineComponent
? InstanceType<T>['$props'] & InstanceType<T>['$emits']
: unknown;
export type ElementLocator = [
x?: number,
y?: number,
width?: number,
height?: number,
anchorX?: number,
anchorY?: number
];
export type ElementAnchor = [x: number, y: number];
export type ElementScale = [x: number, y: number];
const { gl, gl2 } = checkSupport();
function checkSupport() {
@@ -47,28 +19,6 @@ export function isWebGL2Supported() {
return gl2;
}
/**
* 将两个缓动函数做加法
*/
export function addTiming(timing1: TimingFn, timing2: TimingFn): TimingFn {
return (p: number) => timing1(p) + timing2(p);
}
/**
* 将两个缓动函数做乘法
*/
export function multiplyTiming(timing1: TimingFn, timing2: TimingFn): TimingFn {
return (p: number) => timing1(p) * timing2(p);
}
/**
* 判断两个集合是否相等
*/
export function isSetEqual<T>(set1: Set<T>, set2: Set<T>) {
if (set1 === set2) return true;
else return set1.size === set2.size && set1.isSubsetOf(set2);
}
export function transformCanvas(
canvas: MotaOffscreenCanvas2D,
transform: Transform
-2
View File
@@ -1,2 +0,0 @@
export * from './graphics';
export * from './misc';
-1
View File
@@ -1,5 +1,4 @@
export * from './assets';
export * from './core';
export * from './elements';
export * from './style';
export * from './types';