From ecfc0426885eda3b6fc60814dcf3ecd0f7975f3d Mon Sep 17 00:00:00 2001 From: unanmed <1319491857@qq.com> Date: Fri, 4 Oct 2024 11:46:35 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20=E5=88=A0=E9=99=A4=E4=B8=80?= =?UTF-8?q?=E4=BA=9B=E6=B2=A1=E7=94=A8=E7=9A=84=E5=86=85=E5=AE=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 2 +- pnpm-lock.yaml | 8 +- src/plugin/game/fallback.ts | 2 +- src/plugin/particle/camera.ts | 258 ------------------------ src/plugin/particle/particle.ts | 334 -------------------------------- src/plugin/particle/render.ts | 288 --------------------------- src/plugin/webgl/canvas.ts | 125 ------------ src/plugin/webgl/matrix.ts | 165 ---------------- src/plugin/webgl/utils.ts | 260 ------------------------- src/ui/start.vue | 13 +- 10 files changed, 14 insertions(+), 1441 deletions(-) delete mode 100644 src/plugin/particle/camera.ts delete mode 100644 src/plugin/particle/particle.ts delete mode 100644 src/plugin/particle/render.ts delete mode 100644 src/plugin/webgl/canvas.ts delete mode 100644 src/plugin/webgl/matrix.ts delete mode 100644 src/plugin/webgl/utils.ts diff --git a/package.json b/package.json index de4c3bc..87b52da 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,7 @@ "jszip": "^3.10.1", "lodash-es": "^4.17.21", "lz-string": "^1.5.0", - "mutate-animate": "^1.4.0", + "mutate-animate": "^1.4.2", "vue": "^3.4.38" }, "devDependencies": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 62b3c25..476bfce 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -45,8 +45,8 @@ dependencies: specifier: ^1.5.0 version: 1.5.0 mutate-animate: - specifier: ^1.4.0 - version: 1.4.0 + specifier: ^1.4.2 + version: 1.4.2 vue: specifier: ^3.4.38 version: 3.4.38(typescript@5.5.4) @@ -4802,8 +4802,8 @@ packages: resolution: {integrity: sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==} dev: true - /mutate-animate@1.4.0: - resolution: {integrity: sha512-sMPX9xFYlN3iivVa1IwVrzDTjdISrLnU/IorUK3cFkIWZbk0RU6iEfMA98AtUqOGX3YDWh6HS8hWdPQ4nsbIQg==} + /mutate-animate@1.4.2: + resolution: {integrity: sha512-Ioy3fMZ+lW3ua+BKsbfM8S8dCpGxKucVnM+Llq+rCuhnVrv8rOPXDJ2qe/vk5FemFFp9lmtw1QPsFNx8sE0VAA==} dev: false /nan@2.20.0: diff --git a/src/plugin/game/fallback.ts b/src/plugin/game/fallback.ts index e34776c..e8d4ab3 100644 --- a/src/plugin/game/fallback.ts +++ b/src/plugin/game/fallback.ts @@ -588,7 +588,7 @@ export function init() { x: number, y: number, _moveMode: EaseMode, - time: number = 0, + time: number = 1, callback?: () => void ) { const main = Renderer.get('render-main'); diff --git a/src/plugin/particle/camera.ts b/src/plugin/particle/camera.ts deleted file mode 100644 index 636473a..0000000 --- a/src/plugin/particle/camera.ts +++ /dev/null @@ -1,258 +0,0 @@ -import { TimingFn } from 'mutate-animate'; -import { Matrix4 } from '../webgl/matrix'; -import { Renderer } from './render'; - -export type Position3D = [number, number, number]; - -type OneParameterAnimationType = 'x' | 'y' | 'z'; -type TwoParameterAnimationType = 'xy' | 'yx' | 'xz' | 'zx' | 'yz' | 'zy'; -type ThreeParameterAnimationType = - | 'xyz' - | 'xzy' - | 'yxz' - | 'yzx' - | 'zxy' - | 'zyx'; -type CameraAnimationTarget = 'eye' | 'at' | 'up'; - -interface CameraAnimationType { - 1: OneParameterAnimationType; - 2: TwoParameterAnimationType; - 3: ThreeParameterAnimationType; -} - -export class Camera { - /** 视图矩阵 */ - view!: Matrix4; - /** 投影矩阵 */ - projection!: Matrix4; - /** 绑定的渲染器 */ - renderer?: Renderer; - - constructor() { - this.reset(); - } - - /** - * 初始化视角矩阵 - */ - reset() { - this.view = new Matrix4(); - this.projection = new Matrix4(); - } - - /** - * 将该摄像机与一个渲染器绑定 - * @param renderer 渲染器 - */ - bind(renderer: Renderer) { - this.renderer = renderer; - } - - /** - * 取消与渲染器的绑定 - */ - unbind() { - this.renderer = void 0; - } - - /** - * 设置摄像机的观察方位 - * @param eye 视点位置 - * @param at 目标点位置 - * @param up 上方向 - */ - lookAt(eye: Position3D, at: Position3D, up: Position3D) { - this.view = this.calLookAt(eye, at, up); - } - - /** - * 变换摄像机的观察方位 - * @param eye 视点位置 - * @param at 目标点位置 - * @param up 上方向 - */ - transform(eye: Position3D, at: Position3D, up: Position3D) { - this.view.multipy(this.calLookAt(eye, at, up)); - } - - /** - * 设置透视投影矩阵 - * @param fov 垂直视角,即摄像机视锥体的上下平面夹角,单位角度 - * @param aspect 近裁剪面的长宽比,即视野的长宽比 - * @param near 近裁剪面的距离,即最近能看多远 - * @param far 远裁剪面的距离,即最远能看多远 - */ - setPerspective(fov: number, aspect: number, near: number, far: number) { - this.projection = this.calPerspective(fov, aspect, near, far); - } - - /** - * 设置正交投影矩阵 - * @param left 可视空间的左边界 - * @param right 可视空间的右边界 - * @param bottom 可视空间的下边界 - * @param top 可视空间的上边界 - * @param near 近裁剪面的距离,即最近能看多远 - * @param far 远裁剪面的距离,即最远能看多远 - */ - setOrthogonal( - left: number, - right: number, - bottom: number, - top: number, - near: number, - far: number - ) { - this.projection = this.calOrthogonal( - left, - right, - bottom, - top, - near, - far - ); - } - - /** - * 更新视角 - */ - update() { - this.renderer?.render(); - } - - applyAnimate( - target: CameraAnimationTarget, - type: CameraAnimationType[N], - time: number = 1000, - timing?: TimingFn, - relative: boolean = false - ) {} - - /** - * 计算摄像机变换矩阵 - * @see https://github.com/bad4iz/cuon-matrix/blob/main/src/Matrix4/Matrix4.ts - * @param eye 视点位置 - * @param at 目标点位置 - * @param up 上方向 - * @returns 转换矩阵 - */ - private calLookAt(eye: Position3D, at: Position3D, up: Position3D) { - const [eyeX, eyeY, eyeZ] = eye; - const [centerX, centerY, centerZ] = at; - const [upX, upY, upZ] = up; - - let fx = centerX - eyeX; - let fy = centerY - eyeY; - let fz = centerZ - eyeZ; - - const rlf = 1 / Math.sqrt(fx * fx + fy * fy + fz * fz); - fx *= rlf; - fy *= rlf; - fz *= rlf; - - let sx = fy * upZ - fz * upY; - let sy = fz * upX - fx * upZ; - let sz = fx * upY - fy * upX; - - const rls = 1 / Math.sqrt(sx * sx + sy * sy + sz * sz); - sx *= rls; - sy *= rls; - sz *= rls; - - const ux = sy * fz - sz * fy; - const uy = sz * fx - sx * fz; - const uz = sx * fy - sy * fx; - - const matrix = new Matrix4(); - - matrix[0] = [sx, sy, sz, 0]; - matrix[1] = [ux, uy, uz, 0]; - matrix[2] = [-fx, -fy, -fz, 0]; - matrix[3] = [0, 0, 0, 1]; - - matrix.translate(-eyeX, -eyeY, -eyeZ); - return matrix; - } - - /** - * 计算透视矩阵 - * @see https://github.com/bad4iz/cuon-matrix/blob/main/src/Matrix4/Matrix4.ts - * @param fovy 垂直视角,即摄像机视锥体的上下平面夹角 - * @param aspect 近裁剪面的长宽比,即视野的长宽比 - * @param near 近裁剪面的距离,即最近能看多远 - * @param far 远裁剪面的距离,即最远能看多远 - */ - private calPerspective( - fov: number, - aspect: number, - near: number, - far: number - ) { - if (near === far || aspect === 0) { - throw new Error( - `No sence can be set, because near === far or aspect === 0.` - ); - } - if (near <= 0 || far <= 0) { - throw new Error(`near and far must be positive.`); - } - - fov = (Math.PI * fov) / 180 / 2; - const s = Math.sin(fov); - if (s === 0) { - throw new Error( - `Cannot set perspectivity, because sin(fov) === 0.` - ); - } - - const rd = 1 / (far - near); - const ct = Math.cos(fov) / s; - - const matrix = new Matrix4(); - - matrix[0] = [ct / aspect, 0, 0, 0]; - matrix[1] = [0, ct, 0, 0]; - matrix[2] = [0, 0, -(far + near) * rd, -2 * near * far * rd]; - matrix[3] = [0, 0, -1, 0]; - - return matrix; - } - - /** - * 设置正交投影矩阵 - * @param left 可视空间的左边界 - * @param right 可视空间的右边界 - * @param bottom 可视空间的下边界 - * @param top 可视空间的上边界 - * @param near 近裁剪面的距离,即最近能看多远 - * @param far 远裁剪面的距离,即最远能看多远 - */ - private calOrthogonal( - left: number, - right: number, - bottom: number, - top: number, - near: number, - far: number - ) { - if (left === right || bottom === top || near === far) { - throw new Error( - `Cannot set Orthogonality, because left === right or top === bottom or near === far.` - ); - } - - const rw = 1 / (right - left); - const rh = 1 / (top - bottom); - const rd = 1 / (far - near); - - const matrix = new Matrix4(); - - matrix[0] = [2 * rw, 0, 0, -(right + left) * rw]; - matrix[1] = [0, 2 * rh, 0, -(top + bottom) * rh]; - matrix[2] = [0, 0, -2 * rd, -(far + near) * rd]; - matrix[3] = [0, 0, 0, 1]; - - return matrix; - } -} diff --git a/src/plugin/particle/particle.ts b/src/plugin/particle/particle.ts deleted file mode 100644 index 32171a4..0000000 --- a/src/plugin/particle/particle.ts +++ /dev/null @@ -1,334 +0,0 @@ -import { Ticker } from 'mutate-animate'; -import { has } from '../utils'; -import { Camera } from './camera'; -import { Renderer } from './render'; - -export type ParticleColor = [number, number, number, number]; - -interface ParticleThreshold { - radius: number; - color: number; - posX: number; - posY: number; - posZ: number; -} - -export interface ParticleOne { - x: number; - y: number; - z: number; - r: number; - color: ParticleColor; -} - -interface Loc3D extends Loc { - z: number; -} - -interface ParticleInfo { - pos: Loc3D; - density: number; - color: ParticleColor; - radius: number; - threshold: ParticleThreshold; -} - -export class Particle { - /** 绑定的摄像机 */ - camera?: Camera; - /** 粒子中心位置 */ - pos: Loc3D = { x: 0, y: 0, z: 0 }; - /** 粒子密度,即粒子总数 */ - density: number = 50; - /** 粒子颜色 */ - color: ParticleColor = [0, 0, 0, 0]; - /** 每个粒子的半径 */ - radius: number = 2; - /** 渲染器 */ - renderer?: Renderer; - - /** 需要渲染的粒子列表 */ - list: ParticleOne[] = []; - /** 是否需要更新缓冲区数据 */ - needUpdateBuffer: boolean = false; - /** 当前缓存信息 */ - cache?: Float32Array; - - /** 是否需要更新 */ - private needUpdate: boolean = false; - private ticker: Ticker = new Ticker(); - - /** 设置信息前的信息 */ - private originInfo: DeepPartial = {}; - - /** 各个属性的阈值 */ - threshold: ParticleThreshold = { - radius: 2, - color: 0.1, - posX: 0.1, - posY: 0.1, - posZ: 0.1 - }; - - constructor() { - this.ticker.add(() => { - this.updateParticleData.call(this); - }); - } - - /** - * 设置粒子中心的位置 - * @param x 横坐标 - * @param y 纵坐标 - */ - setPos(x?: number, y?: number, z?: number): Particle { - this.originInfo.pos ??= {}; - if (has(x)) { - this.pos.x = x; - this.originInfo.pos.x = x; - } - if (has(y)) { - this.pos.y = y; - this.originInfo.pos.y = y; - } - if (has(z)) { - this.pos.z = z; - this.originInfo.pos.z = z; - } - this.needUpdate = true; - return this; - } - - /** - * 设置粒子的密度,即粒子总数 - * @param density 密度 - */ - setDensity(density: number): Particle { - this.density = density; - this.originInfo.density = density; - this.needUpdate = true; - return this; - } - - /** - * 设置粒子的颜色 - * @param color 颜色 - */ - setColor(color: ParticleColor) { - this.color = color; - this.originInfo.color = color; - this.needUpdate = true; - return this; - } - - /** - * 设置粒子的半径 - * @param radius 半径 - */ - setRadius(radius: number) { - this.radius = radius; - this.originInfo.radius = radius; - this.needUpdate = true; - return this; - } - - /** - * 设置粒子的阈值信息 - * @param data 阈值信息 - */ - setThreshold(data: Partial): Particle { - this.originInfo.threshold ??= {}; - for (const [key, value] of Object.entries(data) as [ - keyof ParticleThreshold, - any - ][]) { - this.threshold[key] = value; - this.originInfo.threshold[key] = value; - } - this.needUpdate = true; - return this; - } - - /** - * 添加到一个渲染器上 - * @param renderer 渲染器 - */ - appendTo(renderer: Renderer) { - renderer.addParticle(this); - } - - /** - * 从当前渲染器上移除 - */ - remove() { - this.renderer?.removeParticle(this); - } - - /** - * 更新粒子信息 - */ - update() { - this.needUpdate = true; - } - - /** - * 生成粒子,注意该函数会删除当前的所有粒子,然后再重新生成 - */ - generate() { - const particles = this.generateNewParticles(this.density); - this.list = particles; - } - - /** - * 获取粒子的Float32Array信息 - */ - getArrayInfo() { - if (!this.cache || this.needUpdateBuffer) { - const array = this.list; - const particleArray = new Float32Array( - array - .map(v => { - const [r, g, b, a] = v.color; - return [v.x, v.y, v.z, r, g, b, a, v.r, 0]; - }) - .flat() - ); - this.cache = particleArray; - return particleArray; - } else { - return this.cache; - } - } - - /** - * 每帧执行的粒子更新器 - */ - private updateParticleData() { - if (!this.needUpdate || this.list.length === 0) return; - this.needUpdate = false; - - // check number - if (this.list.length > this.density) { - this.list.splice(this.density); - this.needUpdateBuffer = true; - } else if (this.list.length < this.density) { - this.list.push( - ...this.generateNewParticles(this.density - this.list.length) - ); - this.needUpdateBuffer = true; - } - - // check radius - if (has(this.originInfo.radius)) { - if (this.radius !== this.originInfo.radius) { - const delta = this.radius - this.originInfo.radius; - this.list.forEach(v => { - v.r += delta; - }); - this.needUpdateBuffer = true; - } - } - - // check color - if (has(this.originInfo.color)) { - if (!core.same(this.color, this.originInfo.color)) { - const r = this.color[0] - this.originInfo.color[0]!; - const g = this.color[1] - this.originInfo.color[1]!; - const b = this.color[2] - this.originInfo.color[2]!; - const a = this.color[3] - this.originInfo.color[3]!; - this.list.forEach(v => { - v.color[0] += r; - v.color[1] += g; - v.color[2] += b; - v.color[3] += a; - }); - this.needUpdateBuffer = true; - } - } - - // check position - if (has(this.originInfo.pos)) { - if (!core.same(this.pos, this.originInfo.pos)) { - const x = this.pos.x - this.originInfo.pos.x!; - const y = this.pos.y - this.originInfo.pos.y!; - const z = this.pos.z - this.originInfo.pos.z!; - this.list.forEach(v => { - v.x += x; - v.y += y; - v.z += z; - }); - this.needUpdateBuffer = true; - } - } - - // check threshold - if (has(this.originInfo.threshold)) { - for (const [key, v] of Object.entries(this.threshold) as [ - keyof ParticleThreshold, - any - ][]) { - const now = v; - const origin = this.originInfo.threshold[key]; - if (origin === now || !has(origin)) { - continue; - } - const ratio = now / origin; - if (key === 'posX') { - this.list.forEach(v => { - v.x = (v.x - this.pos.x) * ratio + this.pos.x; - }); - } else if (key === 'posY') { - this.list.forEach(v => { - v.y = (v.y - this.pos.y) * ratio + this.pos.y; - }); - } else if (key === 'posZ') { - this.list.forEach(v => { - v.z = (v.z - this.pos.z) * ratio + this.pos.z; - }); - } else if (key === 'radius') { - this.list.forEach(v => { - v.r = (v.r - this.radius) * ratio + this.radius; - }); - } else { - this.list.forEach(v => { - v.color = v.color.map((v, i) => { - return (v - this.color[i]) * ratio + this.color[i]; - }) as ParticleColor; - }); - } - this.needUpdateBuffer = true; - } - } - - this.render(); - } - - /** - * 生成指定数量的粒子 - * @param num 生成数量 - */ - private generateNewParticles(num: number): ParticleOne[] { - const res: ParticleOne[] = new Array(num); - const { posX, posY, posZ, radius, color } = this.threshold; - for (let i = 0; i < num; i++) { - const p: ParticleOne = { - x: this.pos.x + (Math.random() - 0.5) * 2 * posX, - y: this.pos.y + (Math.random() - 0.5) * 2 * posY, - z: this.pos.z + (Math.random() - 0.5) * 2 * posZ, - r: this.radius + (Math.random() - 0.5) * 2 * radius, - color: [0, 0, 0, 0].map( - (v, i) => this.color[i] + (Math.random() - 0.5) * 2 * color - ) as ParticleColor - }; - res[i] = p; - } - return res; - } - - /** - * 渲染这个粒子 - */ - private render() { - this.renderer?.render(); - } -} diff --git a/src/plugin/particle/render.ts b/src/plugin/particle/render.ts deleted file mode 100644 index 7094093..0000000 --- a/src/plugin/particle/render.ts +++ /dev/null @@ -1,288 +0,0 @@ -import { circle, sleep, Ticker } from 'mutate-animate'; -import { has } from '../utils'; -import { createProgram } from '../webgl/canvas'; -import { Matrix4 } from '../webgl/matrix'; -import { isWebGLSupported } from '../webgl/utils'; -import { Camera, Position3D } from './camera'; -import { Particle, ParticleColor } from './particle'; - -// 顶点着色器与片元着色器 -// 很像C对吧(但这不是C,是glsl -const vshader = ` - attribute vec4 position; - attribute vec4 color; - attribute vec2 radius; - uniform mat4 camera; - uniform mat4 projection; - varying vec4 vColor; - varying vec4 vPosition; - varying float vRadius; - - void main() { - vec4 p = projection * camera * position; - gl_Position = p; - vColor = color; - vPosition = p; - vRadius = radius.x; - gl_PointSize = vRadius; - } -`; -const fshader = ` - #ifdef GL_ES - precision mediump float; - #endif - - varying vec4 vColor; - varying vec4 vPosition; - varying float vRadius; - - void main() { - vec2 position = gl_PointCoord.xy; - if (distance(position, vec2(0.5)) > 0.5) { - discard; - } else { - gl_FragColor = vColor; - } - } -`; - -export class Renderer { - /** 粒子列表 */ - particleList: Particle[] = []; - /** 渲染画布 */ - canvas: HTMLCanvasElement = document.createElement('canvas'); - /** webgl绘制上下文 */ - gl: WebGLRenderingContext; - /** 绑定的摄像机 */ - camera?: Camera; - /** 缩放比例 */ - ratio: number = devicePixelRatio; - /** gl的程序对象 */ - program: WebGLProgram; - - /** 画布缓冲区 */ - private buffer: WebGLBuffer; - /** 各个attribute的内存地址 */ - private attribLocation: Record = {}; - /** 各个uniform的内存地址 */ - private uniformLocation: Record = {}; - - private static readonly attributes: string[] = [ - 'position', - 'color', - 'radius' - ]; - private static readonly uniforms: string[] = ['camera', 'projection']; - - constructor(width?: number, height?: number) { - if (!isWebGLSupported) { - throw new Error(`Your service or browser does not support webgl!`); - } - this.canvas.style.width = `${width}px`; - this.canvas.style.height = `${height}px`; - if (has(width)) { - this.canvas.width = width * devicePixelRatio; - } - if (has(height)) { - this.canvas.height = height * devicePixelRatio; - } - this.gl = this.canvas.getContext('webgl')!; - this.program = createProgram(this.gl, vshader, fshader); - this.gl.clearColor(0.0, 0.0, 0.0, 0.0); - this.buffer = this.bindBuffer(); - this.getGLVariblesLocation(); - this.gl.enable(this.gl.BLEND); - this.gl.enable(this.gl.DEPTH_TEST); - this.gl.blendFunc(this.gl.SRC_ALPHA, this.gl.ONE_MINUS_SRC_ALPHA); - } - - /** - * 初始化粒子画布 - * @param width 画布宽度 - * @param height 画布高度 - */ - initCanvas(width: number, height: number) { - const ratio = devicePixelRatio; - this.ratio = ratio; - this.canvas.width = width * ratio; - this.canvas.height = height * ratio; - } - - /** - * 绑定摄像机 - * @param camera 摄像机 - */ - bindCamera(camera: Camera) { - this.camera = camera; - } - - /** - * 取消绑定摄像机 - */ - unbindCamera() { - this.camera = void 0; - } - - /** - * 添加到一个html元素中 - * @param ele html元素 - */ - append(ele: HTMLElement) { - ele.appendChild(this.canvas); - } - - /** - * 从当前html元素中移除 - */ - remove() { - this.canvas.remove(); - } - - /** - * 添加一个粒子 - * @param particle 粒子 - */ - addParticle(particle: Particle) { - this.particleList.push(particle); - } - - /** - * 移除一个粒子 - * @param particle 粒子 - */ - removeParticle(particle: Particle) { - const index = this.particleList.findIndex(v => v === particle); - if (index === -1) return; - this.particleList.splice(index, 1); - } - - /** - * 设置画布的背景色 - * @param color 背景色 - */ - setBackground(color: ParticleColor) { - this.gl.clearColor(...color); - } - - /** - * 渲染所有或单个粒子 - */ - render(particle?: Particle | number) { - const { position, color } = this.attribLocation; - const { camera } = this.uniformLocation; - if (!has(position) || !has(color)) { - throw new Error(`Unexpected unset of attribute location`); - } - if (!has(camera)) { - throw new Error(`Unexpected unset of uniform location`); - } - this.gl.clear(this.gl.COLOR_BUFFER_BIT | this.gl.DEPTH_BUFFER_BIT); - if (!has(particle)) this.particleList.forEach(v => this.renderOne(v)); - else { - const p = - typeof particle === 'number' - ? this.particleList[particle] - : particle; - this.renderOne(p); - } - } - - /** - * 绑定画布的缓冲区 - * @returns 绑定的缓冲区 - */ - private bindBuffer() { - const buffer = this.gl.createBuffer(); - if (!buffer) throw this.notSupport(); - this.gl.bindBuffer(this.gl.ARRAY_BUFFER, buffer); - return buffer; - } - - /** - * 更新一个粒子的缓冲区数据 - * @param array 粒子的粒子元素数组 - */ - private updateOneParticleBufferData(particle: Particle) { - const particleArray = particle.getArrayInfo(); - this.gl.bufferData( - this.gl.ARRAY_BUFFER, - particleArray, - this.gl.DYNAMIC_DRAW - ); - return particleArray; - } - - /** - * 获取gl变量的内存地址 - */ - private getGLVariblesLocation() { - Renderer.attributes.forEach(v => { - this.attribLocation[v] = this.gl.getAttribLocation(this.program, v); - }); - Renderer.uniforms.forEach(v => { - const loc = this.gl.getUniformLocation(this.program, v); - if (!loc) { - throw new Error(`Cannot get the location of uniform '${v}'`); - } - this.uniformLocation[v] = loc; - }); - } - - /** - * 渲染某一个粒子 - * @param particle 要渲染的粒子 - */ - private renderOne(particle: Particle) { - const arr = this.updateOneParticleBufferData(particle); - const size = arr.BYTES_PER_ELEMENT; - - const { position, color, radius } = this.attribLocation; - const { camera, projection } = this.uniformLocation; - - // 给gl变量赋值 - this.gl.vertexAttribPointer( - position, - 3, - this.gl.FLOAT, - false, - size * 9, - 0 - ); - this.gl.vertexAttribPointer( - color, - 4, - this.gl.FLOAT, - false, - size * 9, - size * 3 - ); - this.gl.vertexAttribPointer( - radius, - 2, - this.gl.FLOAT, - false, - size * 9, - size * 7 - ); - this.gl.enableVertexAttribArray(position); - this.gl.enableVertexAttribArray(color); - this.gl.enableVertexAttribArray(radius); - const matrix = new Matrix4(); - const c = - this.camera?.view.toWebGLFloat32Array() ?? - matrix.toWebGLFloat32Array(); - const p = - this.camera?.projection.toWebGLFloat32Array() ?? - matrix.toWebGLFloat32Array(); - - this.gl.uniformMatrix4fv(camera, false, c); - this.gl.uniformMatrix4fv(projection, false, p); - - // 绘制 - this.gl.drawArrays(this.gl.POINTS, 0, particle.list.length); - } - - private notSupport() { - throw new Error(`Your service or browser does not support webgl!`); - } -} diff --git a/src/plugin/webgl/canvas.ts b/src/plugin/webgl/canvas.ts deleted file mode 100644 index 6f777c7..0000000 --- a/src/plugin/webgl/canvas.ts +++ /dev/null @@ -1,125 +0,0 @@ -const glMap: Record = {}; - -/** - * 创建一个以webgl为绘制上下文的画布 - * @param id 画布id - * @param x 横坐标 - * @param y 纵坐标 - * @param w 宽度 - * @param h 高度 - * @param z 纵深 - */ -export function createWebGLCanvas( - id: string, - x: number, - y: number, - w: number, - h: number, - z: number -) { - if (id in glMap) { - deleteWebGLCanvas(id); - } - const canvas = document.createElement('canvas'); - const gl = canvas.getContext('webgl')!; - const s = core.domStyle.scale; - canvas.style.left = `${x * s}px`; - canvas.style.top = `${y * s}px`; - canvas.style.width = `${w * s}px`; - canvas.style.height = `${h * s}px`; - canvas.style.zIndex = `${z}`; - canvas.width = w * s * devicePixelRatio; - canvas.height = h * s * devicePixelRatio; - core.dom.gameDraw.appendChild(canvas); - return gl; -} - -/** - * 删除一个webgl画布 - * @param id 画布id - */ -export function deleteWebGLCanvas(id: string) { - const gl = glMap[id]; - if (!gl) return; - const canvas = gl.canvas as HTMLCanvasElement; - canvas.remove(); - delete glMap[id]; -} - -/** - * 获取webgl画布上下文 - * @param id 画布id - */ -export function getWebGLCanvas(id: string): WebGLRenderingContext | null { - return glMap[id]; -} - -/** - * 创建webgl程序对象 - * @param gl 画布webgl上下文 - * @param vshader 顶点着色器 - * @param fshader 片元着色器 - */ -export function createProgram( - gl: WebGLRenderingContext, - vshader: string, - fshader: string -) { - // 创建着色器 - const vertexShader = loadShader(gl, gl.VERTEX_SHADER, vshader); - const fragmentShader = loadShader(gl, gl.FRAGMENT_SHADER, fshader); - - // 创建program - const program = gl.createProgram(); - if (!program) { - throw new Error(`Create webgl program fail!`); - } - - // 分配和连接program - gl.attachShader(program, vertexShader); - gl.attachShader(program, fragmentShader); - gl.linkProgram(program); - - // 检查连接是否成功 - const linked = gl.getProgramParameter(program, gl.LINK_STATUS); - if (!linked) { - const err = gl.getProgramInfoLog(program); - throw new Error(`Program link fail: ${err}`); - } - - gl.useProgram(program); - - return program; -} - -/** - * 加载着色器 - * @param gl 画布的webgl上下文 - * @param type 着色器类型,顶点着色器还是片元着色器 - * @param source 着色器源码 - */ -export function loadShader( - gl: WebGLRenderingContext, - type: number, - source: string -) { - // 创建着色器 - const shader = gl.createShader(type); - if (!shader) { - throw new ReferenceError( - `Your device or browser does not support webgl!` - ); - } - // 引入并编译着色器 - gl.shaderSource(shader, source); - gl.compileShader(shader); - - // 检查是否编译成功 - const compiled = gl.getShaderParameter(shader, gl.COMPILE_STATUS); - if (!compiled) { - const err = gl.getShaderInfoLog(shader); - throw new Error(`Shader compile fail: ${err}`); - } - - return shader; -} diff --git a/src/plugin/webgl/matrix.ts b/src/plugin/webgl/matrix.ts deleted file mode 100644 index de6c935..0000000 --- a/src/plugin/webgl/matrix.ts +++ /dev/null @@ -1,165 +0,0 @@ -import { has } from '../utils'; - -export class Matrix extends Array { - constructor(...n: number[][]) { - if (n.length !== n[0]?.length) { - throw new TypeError( - `The array delivered to Matrix must has the same length of its item and itself.` - ); - } - super(...n); - } - - /** - * 加上某个方阵 - * @param matrix 要加上的方阵 - */ - add(matrix: number[][]): Matrix { - if (matrix.length !== this.length) { - throw new TypeError( - `To add a martrix, the be-added-matrix's size must equal to the to-add-matrix's.` - ); - } - const length = matrix.length; - for (let i = 0; i < length; i++) { - for (let j = 0; j < length; j++) { - this[i][j] += matrix[i][j]; - } - } - return this; - } - - /** - * 让该方阵与另一个方阵相乘 - * @param matrix 要相乘的方阵 - */ - multipy(matrix: number[][]): Matrix { - if (matrix.length !== this.length) { - throw new TypeError( - `To multipy a martrix, the be-multipied-matrix's size must equal to the to-multipy-matrix's.` - ); - } - const n = this.length; - const arr = Array.from(this).map(v => v.slice()); - for (let i = 0; i < n; i++) { - for (let j = 0; j < n; j++) { - this[i][j] = 0; - for (let k = 0; k < n; k++) { - this[i][j] += arr[i][k] * matrix[k][j]; - } - } - } - - return this; - } -} - -export class Matrix4 extends Matrix { - constructor(...n: number[][]) { - if (n.length === 0) { - n = [ - [1, 0, 0, 0], - [0, 1, 0, 0], - [0, 0, 1, 0], - [0, 0, 0, 1] - ]; - } - - if (n.length !== 4) { - throw new TypeError(`The length of delivered array must be 4.`); - } - super(...n); - } - - /** - * 平移变换 - * @param x 平移横坐标 - * @param y 平移纵坐标 - * @param z 平移竖坐标 - */ - translate(x: number, y: number, z: number) { - this.multipy([ - [1, 0, 0, x], - [0, 1, 0, y], - [0, 0, 1, z], - [0, 0, 0, 1] - ]); - } - - /** - * 缩放变换 - * @param x 沿x轴的缩放比例 - * @param y 沿y轴的缩放比例 - * @param z 沿z轴的缩放比例 - */ - scale(x: number, y: number, z: number) { - this.multipy([ - [x, 0, 0, 0], - [0, y, 0, 0], - [0, 0, z, 0], - [0, 0, 0, 1] - ]); - } - - /** - * 旋转变换 - * @param x 绕x轴的旋转角度 - * @param y 绕y轴的旋转角度 - * @param z 绕z轴的旋转角度 - */ - rotate(x?: number, y?: number, z?: number): Matrix4 { - if (has(x) && x !== 0) { - const sin = Math.sin(x); - const cos = Math.cos(x); - this.multipy([ - [1, 0, 0, 0], - [0, cos, sin, 0], - [0, -sin, cos, 0], - [0, 0, 0, 1] - ]); - } - if (has(y) && y !== 0) { - const sin = Math.sin(y); - const cos = Math.cos(y); - this.multipy([ - [cos, 0, -sin, 0], - [0, 1, 0, 0], - [sin, 0, cos, 0], - [0, 0, 0, 1] - ]); - } - if (has(z) && z !== 0) { - const sin = Math.sin(z); - const cos = Math.cos(z); - this.multipy([ - [cos, sin, 0, 0], - [-sin, cos, 0, 0], - [0, 0, 1, 0], - [0, 0, 0, 1] - ]); - } - return this; - } - - /** - * 转置矩阵 - * @param target 转置目标,是赋给原矩阵还是新建一个矩阵 - */ - transpose(target: 'this' | 'new' = 'new'): Matrix4 { - const t = target === 'this' ? this : new Matrix4(); - const arr = Array.from(this).map(v => v.slice()); - for (let i = 0; i < 4; i++) { - for (let j = 0; j < 4; j++) { - t[i][j] = arr[j][i]; - } - } - return t; - } - - /** - * 转换成列主序的Float32Array,用于webgl - */ - toWebGLFloat32Array(): Float32Array { - return new Float32Array(Array.from(this.transpose()).flat()); - } -} diff --git a/src/plugin/webgl/utils.ts b/src/plugin/webgl/utils.ts deleted file mode 100644 index f72c360..0000000 --- a/src/plugin/webgl/utils.ts +++ /dev/null @@ -1,260 +0,0 @@ -import { has } from '../utils'; - -export default function init() { - return { isWebGLSupported }; -} - -export const isWebGLSupported = (function () { - const canvas = document.createElement('canvas'); - return !!canvas.getContext('webgl'); -})(); - -const cssColors = { - black: '#000000', - silver: '#c0c0c0', - gray: '#808080', - white: '#ffffff', - maroon: '#800000', - red: '#ff0000', - purple: '#800080', - fuchsia: '#ff00ff', - green: '#008000', - lime: '#00ff00', - olive: '#808000', - yellow: '#ffff00', - navy: '#000080', - blue: '#0000ff', - teal: '#008080', - aqua: '#00ffff', - orange: '#ffa500', - aliceblue: '#f0f8ff', - antiquewhite: '#faebd7', - aquamarine: '#7fffd4', - azure: '#f0ffff', - beige: '#f5f5dc', - bisque: '#ffe4c4', - blanchedalmond: '#ffebcd', - blueviolet: '#8a2be2', - brown: '#a52a2a', - burlywood: '#deb887', - cadetblue: '#5f9ea0', - chartreuse: '#7fff00', - chocolate: '#d2691e', - coral: '#ff7f50', - cornflowerblue: '#6495ed', - cornsilk: '#fff8dc', - crimson: '#dc143c', - cyan: '#00ffff', - darkblue: '#00008b', - darkcyan: '#008b8b', - darkgoldenrod: '#b8860b', - darkgray: '#a9a9a9', - darkgreen: '#006400', - darkgrey: '#a9a9a9', - darkkhaki: '#bdb76b', - darkmagenta: '#8b008b', - darkolivegreen: '#556b2f', - darkorange: '#ff8c00', - darkorchid: '#9932cc', - darkred: '#8b0000', - darksalmon: '#e9967a', - darkseagreen: '#8fbc8f', - darkslateblue: '#483d8b', - darkslategray: '#2f4f4f', - darkslategrey: '#2f4f4f', - darkturquoise: '#00ced1', - darkviolet: '#9400d3', - deeppink: '#ff1493', - deepskyblue: '#00bfff', - dimgray: '#696969', - dimgrey: '#696969', - dodgerblue: '#1e90ff', - firebrick: '#b22222', - floralwhite: '#fffaf0', - forestgreen: '#228b22', - gainsboro: '#dcdcdc', - ghostwhite: '#f8f8ff', - gold: '#ffd700', - goldenrod: '#daa520', - greenyellow: '#adff2f', - grey: '#808080', - honeydew: '#f0fff0', - hotpink: '#ff69b4', - indianred: '#cd5c5c', - indigo: '#4b0082', - ivory: '#fffff0', - khaki: '#f0e68c', - lavender: '#e6e6fa', - lavenderblush: '#fff0f5', - lawngreen: '#7cfc00', - lemonchiffon: '#fffacd', - lightblue: '#add8e6', - lightcoral: '#f08080', - lightcyan: '#e0ffff', - lightgoldenrodyellow: '#fafad2', - lightgray: '#d3d3d3', - lightgreen: '#90ee90', - lightgrey: '#d3d3d3', - lightpink: '#ffb6c1', - lightsalmon: '#ffa07a', - lightseagreen: '#20b2aa', - lightskyblue: '#87cefa', - lightslategray: '#778899', - lightslategrey: '#778899', - lightsteelblue: '#b0c4de', - lightyellow: '#ffffe0', - limegreen: '#32cd32', - linen: '#faf0e6', - magenta: '#ff00ff', - mediumaquamarine: '#66cdaa', - mediumblue: '#0000cd', - mediumorchid: '#ba55d3', - mediumpurple: '#9370db', - mediumseagreen: '#3cb371', - mediumslateblue: '#7b68ee', - mediumspringgreen: '#00fa9a', - mediumturquoise: '#48d1cc', - mediumvioletred: '#c71585', - midnightblue: '#191970', - mintcream: '#f5fffa', - mistyrose: '#ffe4e1', - moccasin: '#ffe4b5', - navajowhite: '#ffdead', - oldlace: '#fdf5e6', - olivedrab: '#6b8e23', - orangered: '#ff4500', - orchid: '#da70d6', - palegoldenrod: '#eee8aa', - palegreen: '#98fb98', - paleturquoise: '#afeeee', - palevioletred: '#db7093', - papayawhip: '#ffefd5', - peachpuff: '#ffdab9', - peru: '#cd853f', - pink: '#ffc0cb', - plum: '#dda0dd', - powderblue: '#b0e0e6', - rosybrown: '#bc8f8f', - royalblue: '#4169e1', - saddlebrown: '#8b4513', - salmon: '#fa8072', - sandybrown: '#f4a460', - seagreen: '#2e8b57', - seashell: '#fff5ee', - sienna: '#a0522d', - skyblue: '#87ceeb', - slateblue: '#6a5acd', - slategray: '#708090', - slategrey: '#708090', - snow: '#fffafa', - springgreen: '#00ff7f', - steelblue: '#4682b4', - tan: '#d2b48c', - thistle: '#d8bfd8', - tomato: '#ff6347', - turquoise: '#40e0d0', - violet: '#ee82ee', - wheat: '#f5deb3', - whitesmoke: '#f5f5f5', - yellowgreen: '#9acd32', - transparent: '#0000' -}; - -/** - * 颜色字符串转rgb数组 - * @param color 颜色字符串 - */ -export function parseColor(color: string): RGBArray { - if (color.startsWith('rgb')) { - // rgb - const match = color.match(/rgba?\([\d\,\s\.%]+\)/); - if (!has(match)) throw new Error(`Invalid color is delivered!`); - const l = color.includes('a'); - return match[0] - .slice(l ? 5 : 4, -1) - .split(',') - .map((v, i) => { - const vv = v.trim(); - if (vv.endsWith('%')) { - if (i === 3) { - return parseInt(vv) / 100; - } else { - return (parseInt(vv) * 255) / 100; - } - } else return parseFloat(vv); - }) - .slice(0, l ? 4 : 3) as RGBArray; - } else if (color.startsWith('#')) { - // 十六进制 - const content = color.slice(1); - if (![3, 4, 6, 8].includes(content.length)) { - throw new Error(`Invalid color is delivered!`); - } - - if (content.length <= 4) { - const res = content - .split('') - .map(v => Number(`0x${v}${v}`)) as RGBArray; - if (res.length === 4) res[3]! /= 255; - return res; - } else { - const res = Array(content.length / 2) - .fill(1) - .map((v, i) => - Number(`0x${content[i * 2]}${content[i * 2 + 1]}`) - ) as RGBArray; - if (res.length === 4) res[3]! /= 255; - return res; - } - } else if (color.startsWith('hsl')) { - // hsl,转成rgb后输出 - const match = color.match(/hsla?\([\d\,\s\.%]+\)/); - if (!has(match)) throw new Error(`Invalid color is delivered!`); - const l = color.includes('a'); - const hsl = match[0] - .slice(l ? 5 : 4, -1) - .split(',') - .map(v => { - const vv = v.trim(); - if (vv.endsWith('%')) return parseInt(vv) / 100; - else return parseFloat(vv); - }); - const rgb = hslToRgb(hsl[0], hsl[1], hsl[2]); - return (l ? rgb.concat([hsl[3]]) : rgb) as RGBArray; - } else { - // 单词 - const rgb = cssColors[color as keyof typeof cssColors]; - if (!has(rgb)) { - throw new Error(`Invalid color is delivered!`); - } - return parseColor(rgb); - } -} - -/** - * hsl转rgb - * @param h 色相 - * @param s 饱和度 - * @param l 亮度 - */ -export function hslToRgb(h: number, s: number, l: number) { - if (s == 0) { - return [0, 0, 0]; - } else { - const hue2rgb = (p: number, q: number, t: number) => { - if (t < 0) t += 1; - if (t > 1) t -= 1; - if (t < 1 / 6) return p + (q - p) * 6 * t; - if (t < 1 / 2) return q; - if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6; - return p; - }; - - const q = l < 0.5 ? l * (1 + s) : l + s - l * s; - const p = 2 * l - q; - const r = hue2rgb(p, q, h + 1 / 3); - const g = hue2rgb(p, q, h); - const b = hue2rgb(p, q, h - 1 / 3); - return [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255)]; - } -} diff --git a/src/ui/start.vue b/src/ui/start.vue index 9230204..2d70107 100644 --- a/src/ui/start.vue +++ b/src/ui/start.vue @@ -64,7 +64,6 @@ import { FullscreenExitOutlined } from '@ant-design/icons-vue'; import { sleep } from 'mutate-animate'; -import { Matrix4 } from '../plugin/webgl/matrix'; import { doByInterval, keycode } from '../plugin/utils'; import { triggerFullscreen } from '../plugin/utils'; import { isMobile } from '../plugin/use'; @@ -74,6 +73,7 @@ import { mainUi } from '@/core/main/init/ui'; import { CustomToolbar } from '@/core/main/custom/toolbar'; import { mainSetting } from '@/core/main/setting'; import { bgm as mainBgm } from '@/core/audio/bgm'; +import { mat4 } from 'gl-matrix'; const props = defineProps<{ num: number; @@ -107,6 +107,8 @@ const toshow = reactive([]); const selected = ref('start-game'); +const perspective = mat4.create(); + function resize() { if (!window.core) return; const scale = core.domStyle.scale; @@ -175,11 +177,12 @@ function onmove(e: MouseEvent) { const dx = (offsetX - cx) / cx; const dy = (offsetY - cy) / cy; - const matrix = new Matrix4(); + const matrix = mat4.identity(perspective); + mat4.scale(matrix, matrix, [1.2, 1.2, 1]); + mat4.rotateX(matrix, matrix, -(dy * 10 * Math.PI) / 180); + mat4.rotateY(matrix, matrix, (dx * 10 * Math.PI) / 180); - matrix.scale(1.2, 1.2, 1); - matrix.rotate((dy * 10 * Math.PI) / 180, -(dx * 10 * Math.PI) / 180); - const end = Array.from(matrix.transpose()).flat().join(','); + const end = matrix.join(','); background.style.transform = `perspective(${ 1000 * core.domStyle.scale }px)matrix3d(${end})`;