mirror of
https://github.com/motajs/template.git
synced 2026-11-06 01:14:31 +08:00
refactor: 渲染系统
This commit is contained in:
1 parent
9ab0db9465
commit
191ba8d1db
74 files changed
+3776
-7748
No files matched your search
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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][] = [
|
||||
@@ -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';
|
||||
+312
-515
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
@@ -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());
|
||||
};
|
||||
@@ -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);
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
export * from './graphics';
|
||||
export * from './misc';
|
||||
@@ -1,5 +1,4 @@
|
||||
export * from './assets';
|
||||
export * from './core';
|
||||
export * from './elements';
|
||||
export * from './style';
|
||||
export * from './types';
|
||||
Reference in new issue
Block a user