refactor: 删除一些没用的内容

This commit is contained in:
unanmed 2024-10-04 11:46:35 +08:00
parent 06adb72bc3
commit ecfc042688
10 changed files with 14 additions and 1441 deletions

View File

@ -27,7 +27,7 @@
"jszip": "^3.10.1", "jszip": "^3.10.1",
"lodash-es": "^4.17.21", "lodash-es": "^4.17.21",
"lz-string": "^1.5.0", "lz-string": "^1.5.0",
"mutate-animate": "^1.4.0", "mutate-animate": "^1.4.2",
"vue": "^3.4.38" "vue": "^3.4.38"
}, },
"devDependencies": { "devDependencies": {

View File

@ -45,8 +45,8 @@ dependencies:
specifier: ^1.5.0 specifier: ^1.5.0
version: 1.5.0 version: 1.5.0
mutate-animate: mutate-animate:
specifier: ^1.4.0 specifier: ^1.4.2
version: 1.4.0 version: 1.4.2
vue: vue:
specifier: ^3.4.38 specifier: ^3.4.38
version: 3.4.38(typescript@5.5.4) version: 3.4.38(typescript@5.5.4)
@ -4802,8 +4802,8 @@ packages:
resolution: {integrity: sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==} resolution: {integrity: sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==}
dev: true dev: true
/mutate-animate@1.4.0: /mutate-animate@1.4.2:
resolution: {integrity: sha512-sMPX9xFYlN3iivVa1IwVrzDTjdISrLnU/IorUK3cFkIWZbk0RU6iEfMA98AtUqOGX3YDWh6HS8hWdPQ4nsbIQg==} resolution: {integrity: sha512-Ioy3fMZ+lW3ua+BKsbfM8S8dCpGxKucVnM+Llq+rCuhnVrv8rOPXDJ2qe/vk5FemFFp9lmtw1QPsFNx8sE0VAA==}
dev: false dev: false
/nan@2.20.0: /nan@2.20.0:

View File

@ -588,7 +588,7 @@ export function init() {
x: number, x: number,
y: number, y: number,
_moveMode: EaseMode, _moveMode: EaseMode,
time: number = 0, time: number = 1,
callback?: () => void callback?: () => void
) { ) {
const main = Renderer.get('render-main'); const main = Renderer.get('render-main');

View File

@ -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<N extends keyof CameraAnimationType = 1>(
target: CameraAnimationTarget,
type: CameraAnimationType[N],
time: number = 1000,
timing?: TimingFn<N>,
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;
}
}

View File

@ -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<ParticleInfo> = {};
/** 各个属性的阈值 */
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<ParticleThreshold>): 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();
}
}

View File

@ -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<string, number> = {};
/** 各个uniform的内存地址 */
private uniformLocation: Record<string, WebGLUniformLocation> = {};
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!`);
}
}

View File

@ -1,125 +0,0 @@
const glMap: Record<string, WebGLRenderingContext> = {};
/**
* 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;
}

View File

@ -1,165 +0,0 @@
import { has } from '../utils';
export class Matrix extends Array<number[]> {
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;
}
/**
* Float32Arraywebgl
*/
toWebGLFloat32Array(): Float32Array {
return new Float32Array(Array.from(this.transpose()).flat());
}
}

View File

@ -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)];
}
}

View File

@ -64,7 +64,6 @@ import {
FullscreenExitOutlined FullscreenExitOutlined
} from '@ant-design/icons-vue'; } from '@ant-design/icons-vue';
import { sleep } from 'mutate-animate'; import { sleep } from 'mutate-animate';
import { Matrix4 } from '../plugin/webgl/matrix';
import { doByInterval, keycode } from '../plugin/utils'; import { doByInterval, keycode } from '../plugin/utils';
import { triggerFullscreen } from '../plugin/utils'; import { triggerFullscreen } from '../plugin/utils';
import { isMobile } from '../plugin/use'; import { isMobile } from '../plugin/use';
@ -74,6 +73,7 @@ import { mainUi } from '@/core/main/init/ui';
import { CustomToolbar } from '@/core/main/custom/toolbar'; import { CustomToolbar } from '@/core/main/custom/toolbar';
import { mainSetting } from '@/core/main/setting'; import { mainSetting } from '@/core/main/setting';
import { bgm as mainBgm } from '@/core/audio/bgm'; import { bgm as mainBgm } from '@/core/audio/bgm';
import { mat4 } from 'gl-matrix';
const props = defineProps<{ const props = defineProps<{
num: number; num: number;
@ -107,6 +107,8 @@ const toshow = reactive<string[]>([]);
const selected = ref('start-game'); const selected = ref('start-game');
const perspective = mat4.create();
function resize() { function resize() {
if (!window.core) return; if (!window.core) return;
const scale = core.domStyle.scale; const scale = core.domStyle.scale;
@ -175,11 +177,12 @@ function onmove(e: MouseEvent) {
const dx = (offsetX - cx) / cx; const dx = (offsetX - cx) / cx;
const dy = (offsetY - cy) / cy; 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); const end = matrix.join(',');
matrix.rotate((dy * 10 * Math.PI) / 180, -(dx * 10 * Math.PI) / 180);
const end = Array.from(matrix.transpose()).flat().join(',');
background.style.transform = `perspective(${ background.style.transform = `perspective(${
1000 * core.domStyle.scale 1000 * core.domStyle.scale
}px)matrix3d(${end})`; }px)matrix3d(${end})`;