粒子初步

This commit is contained in:
AncTe committed 2023-02-10 16:39:53 +08:00
1 parent 4d45553c84
commit 40e20d9b4f
11 files changed
+316 -191

No files matched your search

+100
View File
@@ -0,0 +1,100 @@
import { Matrix4 } from '../webgl/martrix';
import { Renderer } from './render';
type Position3D = [number, number, number];
export class Camera {
/** 视图矩阵 */
matrix!: Matrix4;
/** 绑定的渲染器 */
renderer?: Renderer;
constructor() {
this.reset();
}
reset() {
this.matrix = 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.matrix = this.calLookAt(eye, at, up);
}
/**
* 变换摄像机的观察方位
* @param eye 视点位置
* @param at 目标点位置
* @param up 上方向
*/
transform(eye: Position3D, at: Position3D, up: Position3D) {
this.matrix.multipy(this.calLookAt(eye, at, up));
}
/**
* 计算摄像机变换矩阵
* @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[4] = [0, 0, 0, 1];
matrix.translate(-eyeX, -eyeY, -eyeZ);
return matrix;
}
}
+112
View File
@@ -0,0 +1,112 @@
import { Ticker } from 'mutate-animate';
import { has } from '../utils';
import { Camera } from './camera';
import { Renderer } from './render';
interface ParticleThreshold {
radius: number;
color: number;
position: number;
}
interface ParticleOne {
x: number;
y: number;
z: number;
r: number;
}
interface Loc3D extends Loc {
z: number;
}
export class Particle {
/** 绑定的摄像机 */
camera?: Camera;
/** 粒子中心位置 */
pos: Loc3D;
/** 粒子密度 */
density: number;
/** 渲染器 */
renderer?: Renderer;
/** 需要渲染的粒子列表 */
list: ParticleOne[] = [];
/** 是否需要更新 */
private needUpdate: boolean = false;
private ticker: Ticker = new Ticker();
/** 各个属性的阈值 */
threshold: ParticleThreshold = {
radius: 2,
color: 16,
position: 50
};
constructor(density: number, x: number, y: number, z: number) {
this.pos = { x, y, z };
this.density = density;
this.ticker.add(this.updateParticleData);
}
/**
* 设置粒子中心的位置
* @param x 横坐标
* @param y 纵坐标
*/
setPos(x?: number, y?: number): Particle {
has(x) && (this.pos.x = x);
has(y) && (this.pos.y = y);
this.needUpdate = true;
return this;
}
/**
* 设置粒子的阈值信息
* @param data 阈值信息
*/
setThreshold(data: Partial<ParticleThreshold>): 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.needUpdate = true;
return this;
}
/**
* 生成粒子
*/
generate() {}
/**
* 添加到一个渲染器上
* @param renderer 渲染器
*/
appendTo(renderer: Renderer) {
renderer.addParticle(this);
}
/**
* 从当前渲染器上移除
*/
remove() {
this.renderer?.removeParticle(this);
}
/**
* 更新粒子信息
*/
update() {
this.needUpdate = true;
}
/**
* 每帧执行的粒子更新器
*/
private updateParticleData() {
if (!this.needUpdate) return;
this.needUpdate = false;
}
}
+83
View File
@@ -0,0 +1,83 @@
import { isWebGLSupported } from '../webgl/utils';
import { Camera } from './camera';
import { Particle } from './particle';
export class Renderer {
/** 粒子列表 */
particleList: Particle[] = [];
/** 渲染画布 */
canvas: HTMLCanvasElement = document.createElement('canvas');
/** webgl绘制上下文 */
gl: WebGLRenderingContext;
/** 绑定的摄像机 */
camera?: Camera;
/** 缩放比例 */
ratio: number = devicePixelRatio;
constructor() {
if (!isWebGLSupported) {
throw new Error(`Your service or browser does not support webgl!`);
}
this.gl = this.canvas.getContext('webgl')!;
}
/**
* 初始化粒子画布
* @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);
}
}