mirror of
https://github.com/unanmed/HumanBreak.git
synced 2025-02-28 09:27:07 +08:00
refactor: 删除一些没用的内容
This commit is contained in:
parent
06adb72bc3
commit
ecfc042688
@ -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": {
|
||||
|
@ -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:
|
||||
|
@ -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');
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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!`);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换成列主序的Float32Array,用于webgl
|
||||
*/
|
||||
toWebGLFloat32Array(): Float32Array {
|
||||
return new Float32Array(Array.from(this.transpose()).flat());
|
||||
}
|
||||
}
|
@ -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)];
|
||||
}
|
||||
}
|
@ -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<string[]>([]);
|
||||
|
||||
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})`;
|
||||
|
Loading…
Reference in New Issue
Block a user