diff --git a/src/data/desc.json b/src/data/desc.json
index 1fa349f..638778d 100644
--- a/src/data/desc.json
+++ b/src/data/desc.json
@@ -54,7 +54,7 @@
"
",
"游戏作者:古祠",
"
",
- "本塔遵循MIT开源协议,你可随意使用本塔的任何代码,不需要作者授权,也可以随意用于商业用途。",
+ "本塔遵循MIT开源协议。查看开源协议",
"
",
"BGM来源:网易云音乐等",
"
",
diff --git a/src/main.ts b/src/main.ts
index 7cc4045..90be7f5 100644
--- a/src/main.ts
+++ b/src/main.ts
@@ -3,6 +3,7 @@ import App from './App.vue';
import App2 from './App2.vue';
import './styles.less';
import 'ant-design-vue/dist/antd.dark.css';
+import './plugin/particle/render';
createApp(App).mount('#root');
createApp(App2).mount('#root2');
diff --git a/src/plugin/particle/camera.ts b/src/plugin/particle/camera.ts
index 42ed59b..636473a 100644
--- a/src/plugin/particle/camera.ts
+++ b/src/plugin/particle/camera.ts
@@ -1,11 +1,31 @@
-import { Matrix4 } from '../webgl/martrix';
+import { TimingFn } from 'mutate-animate';
+import { Matrix4 } from '../webgl/matrix';
import { Renderer } from './render';
-type Position3D = [number, number, number];
+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 {
/** 视图矩阵 */
- matrix!: Matrix4;
+ view!: Matrix4;
+ /** 投影矩阵 */
+ projection!: Matrix4;
/** 绑定的渲染器 */
renderer?: Renderer;
@@ -13,8 +33,12 @@ export class Camera {
this.reset();
}
+ /**
+ * 初始化视角矩阵
+ */
reset() {
- this.matrix = new Matrix4();
+ this.view = new Matrix4();
+ this.projection = new Matrix4();
}
/**
@@ -39,7 +63,7 @@ export class Camera {
* @param up 上方向
*/
lookAt(eye: Position3D, at: Position3D, up: Position3D) {
- this.matrix = this.calLookAt(eye, at, up);
+ this.view = this.calLookAt(eye, at, up);
}
/**
@@ -49,9 +73,62 @@ export class Camera {
* @param up 上方向
*/
transform(eye: Position3D, at: Position3D, up: Position3D) {
- this.matrix.multipy(this.calLookAt(eye, at, up));
+ 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
@@ -92,9 +169,90 @@ export class Camera {
matrix[0] = [sx, sy, sz, 0];
matrix[1] = [ux, uy, uz, 0];
matrix[2] = [-fx, -fy, -fz, 0];
- matrix[4] = [0, 0, 0, 1];
+ 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
index 481136f..6f3643c 100644
--- a/src/plugin/particle/particle.ts
+++ b/src/plugin/particle/particle.ts
@@ -3,30 +3,47 @@ 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;
- position: number;
+ posX: number;
+ posY: number;
+ posZ: number;
}
-interface ParticleOne {
+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;
- /** 粒子密度 */
- density: number;
+ pos: Loc3D = { x: 0, y: 0, z: 0 };
+ /** 粒子密度,即粒子总数 */
+ density: number = 50;
+ /** 粒子颜色 */
+ color: ParticleColor = [0, 0, 0, 0];
+ /** 每个粒子的半径 */
+ radius: number = 2;
/** 渲染器 */
renderer?: Renderer;
@@ -37,17 +54,22 @@ export class Particle {
private needUpdate: boolean = false;
private ticker: Ticker = new Ticker();
+ /** 设置信息前的信息 */
+ private originInfo: DeepPartial = {};
+
/** 各个属性的阈值 */
threshold: ParticleThreshold = {
radius: 2,
- color: 16,
- position: 50
+ color: 0.1,
+ posX: 0.1,
+ posY: 0.1,
+ posZ: 0.1
};
- constructor(density: number, x: number, y: number, z: number) {
- this.pos = { x, y, z };
- this.density = density;
- this.ticker.add(this.updateParticleData);
+ constructor() {
+ this.ticker.add(() => {
+ this.updateParticleData.call(this);
+ });
}
/**
@@ -55,9 +77,53 @@ export class Particle {
* @param x 横坐标
* @param y 纵坐标
*/
- setPos(x?: number, y?: number): Particle {
- has(x) && (this.pos.x = x);
- has(y) && (this.pos.y = 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;
}
@@ -67,19 +133,18 @@ export class Particle {
* @param data 阈值信息
*/
setThreshold(data: Partial): Particle {
- const { radius, color, position } = data;
- has(radius) && (this.threshold.radius = radius);
- has(color) && (this.threshold.radius = color);
- has(position) && (this.threshold.radius = position);
+ 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;
}
- /**
- * 生成粒子
- */
- generate() {}
-
/**
* 添加到一个渲染器上
* @param renderer 渲染器
@@ -102,11 +167,137 @@ export class Particle {
this.needUpdate = true;
}
+ /**
+ * 生成粒子,注意该函数会删除当前的所有粒子,然后再重新生成
+ */
+ generate() {
+ const particles = this.generateNewParticles(this.density);
+ this.list = particles;
+ }
+
/**
* 每帧执行的粒子更新器
*/
private updateParticleData() {
- if (!this.needUpdate) return;
+ if (!this.needUpdate || this.list.length === 0) return;
this.needUpdate = false;
+
+ // check number
+ if (this.list.length > this.density) {
+ this.list.splice(this.density);
+ } else if (this.list.length < this.density) {
+ this.list.push(
+ ...this.generateNewParticles(this.density - this.list.length)
+ );
+ }
+
+ // 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;
+ });
+ }
+ }
+
+ // 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;
+ });
+ }
+ }
+
+ // 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;
+ });
+ }
+ }
+
+ // 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.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(this);
}
}
diff --git a/src/plugin/particle/render.ts b/src/plugin/particle/render.ts
index c4398bc..5fed02c 100644
--- a/src/plugin/particle/render.ts
+++ b/src/plugin/particle/render.ts
@@ -1,6 +1,50 @@
+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 } from './camera';
-import { Particle } from './particle';
+import { Camera, Position3D } from './camera';
+import { Particle, ParticleColor, ParticleOne } 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 {
/** 粒子列表 */
@@ -13,12 +57,43 @@ export class Renderer {
camera?: Camera;
/** 缩放比例 */
ratio: number = devicePixelRatio;
+ /** gl的程序对象 */
+ program: WebGLProgram;
- constructor() {
+ /** 画布缓冲区 */
+ 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);
}
/**
@@ -80,4 +155,187 @@ export class Renderer {
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(array: ParticleOne[]) {
+ 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.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.list);
+ 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!`);
+ }
}
+
+window.addEventListener('load', async () => {
+ const renderer = new Renderer(
+ 480 * core.domStyle.scale,
+ 480 * core.domStyle.scale
+ );
+ const particle = new Particle();
+ const camera = new Camera();
+ renderer.bindCamera(camera);
+ particle.appendTo(renderer);
+ renderer.append(core.dom.gameDraw);
+ camera.lookAt([1, 1, 5], [0, 0, 0], [0, 1, 0]);
+ camera.setPerspective(20, 1, 1, 100);
+
+ console.log(camera.view, camera.projection);
+
+ particle.setColor([0.3, 0.6, 0.7, 0.7]);
+ particle.setRadius(3);
+ particle.setDensity(1000);
+ particle.setThreshold({
+ posX: 0.2,
+ posY: 0.2,
+ posZ: 10,
+ radius: 0,
+ color: 0
+ });
+ particle.generate();
+
+ renderer.canvas.style.position = 'absolute';
+ renderer.canvas.style.zIndex = '160';
+
+ renderer.render();
+
+ await sleep(5000);
+ const now: Position3D = [1, 1, 5];
+ const path = circle(1, 1000, [0, 0]);
+ let f = 0;
+ new Ticker().add(() => {
+ camera.lookAt(now, [0, 0, 0], [0, 1, 0]);
+ const [x, y] = path(f / 1000 / 2000);
+ f++;
+ now[0] = x;
+ now[1] = y;
+ renderer.render();
+ });
+});
diff --git a/src/plugin/webgl/canvas.ts b/src/plugin/webgl/canvas.ts
index b4a70c2..6f777c7 100644
--- a/src/plugin/webgl/canvas.ts
+++ b/src/plugin/webgl/canvas.ts
@@ -87,6 +87,8 @@ export function createProgram(
throw new Error(`Program link fail: ${err}`);
}
+ gl.useProgram(program);
+
return program;
}
@@ -113,7 +115,7 @@ export function loadShader(
gl.compileShader(shader);
// 检查是否编译成功
- const compiled = gl.getShaderParameter(gl, gl.COMPILE_STATUS);
+ const compiled = gl.getShaderParameter(shader, gl.COMPILE_STATUS);
if (!compiled) {
const err = gl.getShaderInfoLog(shader);
throw new Error(`Shader compile fail: ${err}`);
diff --git a/src/plugin/webgl/martrix.ts b/src/plugin/webgl/matrix.ts
similarity index 88%
rename from src/plugin/webgl/martrix.ts
rename to src/plugin/webgl/matrix.ts
index ec6692d..450ba03 100644
--- a/src/plugin/webgl/martrix.ts
+++ b/src/plugin/webgl/matrix.ts
@@ -1,3 +1,4 @@
+import { cloneDeep } from 'lodash';
import { has } from '../utils';
export class Matrix extends Array {
@@ -40,26 +41,31 @@ export class Matrix extends Array {
);
}
const n = this.length;
- const arr = this.map(v => v.slice());
+ 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];
+ this[i][j] += arr[i][k] * matrix[k][j];
}
}
}
+
return this;
}
}
export class Matrix4 extends Matrix {
constructor(...n: number[][]) {
- n ??= [
- [1, 0, 0, 0],
- [0, 1, 0, 0],
- [0, 0, 1, 0],
- [0, 0, 0, 1]
- ];
+ 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.`);
}
@@ -142,7 +148,7 @@ export class Matrix4 extends Matrix {
*/
transpose(target: 'this' | 'new' = 'new'): Matrix4 {
const t = target === 'this' ? this : new Matrix4();
- const arr = this.map(v => v.slice());
+ 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];
@@ -155,6 +161,6 @@ export class Matrix4 extends Matrix {
* 转换成列主序的Float32Array,用于webgl
*/
toWebGLFloat32Array(): Float32Array {
- return new Float32Array(this.transpose().flat());
+ return new Float32Array(Array.from(this.transpose()).flat());
}
}
diff --git a/src/types/util.d.ts b/src/types/util.d.ts
index f3a7697..e6d0611 100644
--- a/src/types/util.d.ts
+++ b/src/types/util.d.ts
@@ -822,12 +822,12 @@ type DeepReadonly = {
};
/**
- * 深度可选一个对象,使其所有属性都
+ * 深度可选一个对象,使其所有属性都可选
*/
type DeepPartial = {
[P in keyof T]?: T[P] extends number | string | boolean
? T[P]
- : DeepReadonly;
+ : DeepPartial;
};
/**
@@ -836,7 +836,7 @@ type DeepPartial = {
type DeepRequired = {
[P in keyof T]-?: T[P] extends number | string | boolean
? T[P]
- : DeepReadonly;
+ : DeepRequired;
};
/**
@@ -852,7 +852,7 @@ type Writable = {
type DeepWritable = {
-readonly [P in keyof T]: T[P] extends number | string | boolean
? T[P]
- : DeepReadonly;
+ : DeepWritable;
};
/**