| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321 |
- import * as THREE from 'three';
- import gsap from 'gsap';
- // import * as dat from 'dat.gui';
- // const gui = new dat.GUI();
- export default class SmokePath {
- smokeTexturePath;
- pointsMesh;
- points = [];
- opacity = 0;
- size = 15;
- scale = 0.8;
- color;
- range = 30; // 半径
- progress = 0;
- frameId;
- startSpeed = 100;
- speed = 0;
- speedFactor = 2; // 速度因素
- scaleFactor = 0.1; // 缩放比率, 范围0-1
- opacityFactor = 1; // 透明度因素, 范围0-1
- section1Num = 0; // 默认direction为true时后面一段
- section2Num = 25; // 默认direction为true时前面一段 仅用来测试 18
- spreadSection = 32; // 默认direction为true时前面一段 扩散部分
- flyPointArr = new Map();
- geometry;
- direction = false; // true 从口到里面
- isStop = false;
- constructor(smokeTexturePath, color, range, opacityFactor, scaleFactor, life, path) {
- this.smokeTexturePath = smokeTexturePath;
- this.color = color;
- this.opacityFactor = opacityFactor;
- this.scaleFactor = scaleFactor;
- this.range = range;
- // this.life = life;
- path.getSpacedPoints(100).forEach((point) => {
- const { x, y, z } = point;
- this.points.push([x, y, z]);
- });
- //
- //
- // gui.add(this, 'speedFactor', 0, 100);
- // gui.add(this, 'scaleFactor', 0, 1);
- // gui.add(this, 'opacityFactor', 0, 10);
- }
- setPoints() {
- const textureLoader = new THREE.TextureLoader();
- // 先创建一个空的缓冲几何体
- const geometry = new THREE.BufferGeometry();
- geometry.setAttribute('position', new THREE.BufferAttribute(new Float32Array([]), 3)); // 一个顶点由3个坐标构成
- geometry.setAttribute('a_opacity', new THREE.BufferAttribute(new Float32Array([]), 1)); // 点的透明度,用1个浮点数表示
- geometry.setAttribute('a_size', new THREE.BufferAttribute(new Float32Array([]), 1)); // 点的初始大小,用1个浮点数表示
- geometry.setAttribute('a_scale', new THREE.BufferAttribute(new Float32Array([]), 1)); // 点的放大量,用1个浮点数表示
- geometry.boundingBox = new THREE.Box3(new THREE.Vector3(10000, -10000, 10000), new THREE.Vector3(-10000, 10000, -10000));
- geometry.boundingSphere = new THREE.Sphere(new THREE.Vector3(0, 0, 0), 10000);
- // 创建材质
- const material = new THREE.PointsMaterial({
- color: this.color,
- map: textureLoader.load(this.smokeTexturePath), // 纹理图
- transparent: true, // 开启透明度
- depthWrite: false, // 禁止深度写入
- sizeAttenuation: true,
- side: THREE.DoubleSide,
- });
- // 修正着色器
- material.onBeforeCompile = function (shader) {
- const vertexShader_attribute = `
- attribute float a_opacity;
- attribute float a_size;
- attribute float a_scale;
- varying float v_opacity;
- void main() {
- v_opacity = a_opacity;
- `;
- const vertexShader_size = `
- gl_PointSize = a_size * a_scale;
- `;
- shader.vertexShader = shader.vertexShader.replace('void main() {', vertexShader_attribute);
- shader.vertexShader = shader.vertexShader.replace('gl_PointSize = size;', vertexShader_size);
- const fragmentShader_varying = `
- varying float v_opacity;
- void main() {
- `;
- const fragmentShader_opacity = `
- vec4 diffuseColor = vec4( diffuse, opacity * v_opacity );
- `;
- shader.fragmentShader = shader.fragmentShader.replace('void main() {', fragmentShader_varying);
- shader.fragmentShader = shader.fragmentShader.replace('vec4 diffuseColor = vec4( diffuse, opacity );', fragmentShader_opacity);
- };
- // 创建点,并添加进场景
- this.pointsMesh = new THREE.Points(geometry, material);
- geometry.dispose();
- material.dispose();
- }
- updateFly() {
- type attributeType = {
- index: number;
- point: number[];
- scale: number;
- size: number;
- opacity: number;
- oldOpacity: number;
- };
- const total = this.points.length - this.section1Num - Math.floor(this.section2Num);
- let startIndex = this.direction ? this.section1Num : this.points.length - Math.floor(this.section2Num);
- if (this.flyPointArr.size < total - 1) {
- let index;
- if (this.flyPointArr.size == 0) {
- // 起始计算
- index = this.direction ? ++startIndex : --startIndex;
- } else {
- // 根据flyPointArr 已有数量时根据自己的数量结合起点时的数,判断方向, 但是要注意最大、最小的临界值
- if (this.direction) {
- index = this.flyPointArr.size + startIndex + 1;
- } else {
- index = startIndex - this.flyPointArr.size - 1;
- }
- }
- if (index != undefined) {
- let opacity;
- opacity = (this.flyPointArr.size / total) * 0.2 * this.opacityFactor + 0.02;
- const attribute: attributeType = {
- index: index,
- point: this.points[index],
- scale: 0.5 + Math.random() * 0.5,
- size: this.size,
- opacity: opacity,
- oldOpacity: opacity,
- };
- // 判断点是否已经存在
- if (!this.flyPointArr.has(index)) {
- this.flyPointArr.set(index, attribute);
- }
- }
- } else {
- // 变短时候要移除点
- }
- if (this.flyPointArr.size > 0) {
- // 计算新数据
- const posArr = [];
- const scaleArr: number[] = [];
- const sizeArr: number[] = [];
- let opacityArr: number[] = [];
- const flyPointArr = this.flyPointArr.entries();
- for (let i = 0; i < this.flyPointArr.size; i++) {
- const attribute = flyPointArr.next().value[1];
- let scale,
- point = attribute.point,
- index = attribute.index;
- const num = this.points.length - index;
- const factor = 1 - num / this.spreadSection; // 扩散点对scale的影响
- if (index > this.points.length - 1 - this.spreadSection) {
- // 扩散气体部分
- attribute.scale = 1.1 + Math.random() * factor * 3;
- scale = (attribute.scale / 4) * 3 + (Math.random() * attribute.scale) / 4;
- point = [attribute.point[0] + Math.random() * 1, attribute.point[1] + Math.random() * 7, attribute.point[2] + Math.random() * 1];
- } else {
- if (this.flyPointArr.size < total - 1 && !this.isStop) {
- // 逐渐增加粒子时
- scale = 0.6 + Math.random() * 0.3;
- } else {
- // 粒子数全部加载后
- scale = this.scaleFactor + Math.random() * (1 - this.scaleFactor);
- }
- }
- let opacity;
- if (this.isStop) {
- if (index > this.points.length - 1 - this.spreadSection) {
- point = [attribute.point[0] - Math.random() * 1, attribute.point[1] - Math.random() * 7, attribute.point[2] - Math.random() * 1];
- }
- attribute.opacity = attribute.oldOpacity - (1 - this.speed / this.startSpeed) * attribute.oldOpacity;
- attribute.scale = (attribute.scale * this.speed) / this.startSpeed;
- }
- opacity = this.opacityFactor * attribute.opacity;
- posArr.push(...point);
- sizeArr.push(attribute.size);
- scaleArr.push(scale);
- opacityArr.push(opacity);
- }
- if (this.direction) {
- opacityArr = opacityArr.reverse();
- }
- // 更新几何体
- this.pointsMesh.geometry.setAttribute('position', new THREE.BufferAttribute(new Float32Array(posArr), 3));
- this.pointsMesh.geometry.setAttribute('a_scale', new THREE.BufferAttribute(new Float32Array(scaleArr), 1));
- this.pointsMesh.geometry.setAttribute('a_opacity', new THREE.BufferAttribute(new Float32Array(opacityArr), 1));
- this.pointsMesh.geometry.setAttribute('a_size', new THREE.BufferAttribute(new Float32Array(sizeArr), 1));
- }
- }
- // 移动
- move(duration = 10) {
- if (this.frameId) return;
- const _this = this;
- gsap.to(this, {
- opacity: 0.2,
- duration: duration,
- ease: 'easeInCirc',
- overwrite: true,
- });
- gsap.to(this, {
- speed: this.startSpeed,
- duration: duration,
- ease: 'easeInCirc',
- overwrite: true,
- });
- let startTime = new Date().getTime();
- const h = () => {
- if (_this.frameId) {
- _this.frameId = requestAnimationFrame(h);
- }
- const now = new Date().getTime();
- if (((now - startTime) as number) > this.startSpeed - this.speed + this.speedFactor) {
- _this.updateFly();
- startTime = new Date().getTime();
- }
- };
- this.frameId = requestAnimationFrame(h);
- }
- resetParam() {
- window.cancelAnimationFrame(this.frameId);
- this.frameId = 0;
- this.isStop = false;
- this.flyPointArr.clear();
- this.opacity = 0;
- this.speed = 0;
- }
- changeDirection() {
- const _this = this;
- this.isStop = true;
- return new Promise((resolve) => {
- if (this.direction) {
- gsap.to(this, {
- section2Num: 25,
- spreadSection: 32,
- speed: 0,
- duration: 10,
- ease: 'easeInCirc',
- overwrite: true,
- onComplete: function () {
- _this.resetParam();
- _this.direction = !_this.direction;
- _this.move();
- resolve(null);
- },
- });
- } else {
- gsap.to(this, {
- section2Num: 0,
- spreadSection: 30,
- speed: 0,
- duration: 10,
- ease: 'easeInCirc',
- overwrite: true,
- onComplete: function () {
- _this.resetParam();
- _this.direction = !_this.direction;
- _this.move();
- resolve(null);
- },
- });
- }
- });
- }
- stopSmoke() {
- const _this = this;
- this.isStop = true;
- return new Promise((resolve) => {
- if (this.direction) {
- gsap.to(this, {
- speed: 0,
- opacity: 0,
- duration: 10,
- ease: 'easeInCirc',
- overwrite: true,
- onComplete: function () {
- _this.resetParam();
- resolve(null);
- },
- });
- } else {
- gsap.to(this, {
- speed: 0,
- opacity: 0,
- duration: 10,
- ease: 'easeInCirc',
- overwrite: true,
- onComplete: function () {
- _this.resetParam();
- resolve(null);
- },
- });
- }
- });
- }
- clearSmoke() {
- this.stopSmoke();
- this.flyPointArr.clear();
- this.pointsMesh.geometry.dispose();
- this.pointsMesh.texture.dispose();
- this.points = [];
- }
- }
|