SmokePath1.ts 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365
  1. import * as THREE from 'three';
  2. import gsap from 'gsap';
  3. // import * as dat from 'dat.gui';
  4. // const gui = new dat.GUI();
  5. export default class SmokePath {
  6. smokeTexturePath;
  7. pointsMesh;
  8. points = [];
  9. opacity = 0;
  10. size = 15;
  11. scale = 0.8;
  12. color;
  13. range = 30; // 半径
  14. progress = 0;
  15. frameId;
  16. startSpeed = 100;
  17. speed = 0;
  18. speedFactor = 2; // 速度因素
  19. scaleFactor = 0.1; // 缩放比率, 范围0-1
  20. opacityFactor = 1; // 透明度因素, 范围0-1
  21. section1Num = 0; // 默认direction为true时后面一段
  22. // section2Num = 25; // 默认direction为true时前面一段 仅用来测试 18
  23. // spreadSection = 32; // 默认direction为true时前面一段 扩散部分
  24. // section2Num = 10; // 默认direction为true时前面一段 仅用来测试 18
  25. // spreadSection = 14; // 默认direction为true时前面一段 扩散部分
  26. section2Num = 0; // 默认direction为true时前面一段 仅用来测试 18
  27. spreadSection = 0; // 默认direction为true时前面一段 扩散部分
  28. flyPointArr = new Map();
  29. spreadAttributeArr: any[] = [];
  30. noSpreadAttributeArr: any[] = [];
  31. pointAttributeArr: any[] = [];
  32. geometry;
  33. direction = true; // true 从口到里面
  34. isStop = false;
  35. constructor(smokeTexturePath, color, range, opacityFactor, scaleFactor, life, path) {
  36. this.smokeTexturePath = smokeTexturePath;
  37. this.color = color;
  38. this.opacityFactor = opacityFactor;
  39. this.scaleFactor = scaleFactor;
  40. this.range = range;
  41. path.getSpacedPoints(50).forEach((point) => {
  42. const { x, y, z } = point;
  43. this.points.push([x, y, z]);
  44. });
  45. //
  46. //
  47. // gui.add(this, 'speedFactor', 0, 100);
  48. // gui.add(this, 'scaleFactor', 0, 1);
  49. // gui.add(this, 'opacityFactor', 0, 10);
  50. }
  51. setPoints() {
  52. const textureLoader = new THREE.TextureLoader();
  53. // 先创建一个空的缓冲几何体
  54. const geometry = new THREE.BufferGeometry();
  55. geometry.setAttribute('position', new THREE.BufferAttribute(new Float32Array([]), 3)); // 一个顶点由3个坐标构成
  56. geometry.setAttribute('a_opacity', new THREE.BufferAttribute(new Float32Array([]), 1)); // 点的透明度,用1个浮点数表示
  57. geometry.setAttribute('a_size', new THREE.BufferAttribute(new Float32Array([]), 1)); // 点的初始大小,用1个浮点数表示
  58. geometry.setAttribute('a_scale', new THREE.BufferAttribute(new Float32Array([]), 1)); // 点的放大量,用1个浮点数表示
  59. geometry.boundingBox = new THREE.Box3(new THREE.Vector3(10000, -10000, 10000), new THREE.Vector3(-10000, 10000, -10000));
  60. geometry.boundingSphere = new THREE.Sphere(new THREE.Vector3(0, 0, 0), 10000);
  61. // 创建材质
  62. const material = new THREE.PointsMaterial({
  63. color: this.color,
  64. map: textureLoader.load(this.smokeTexturePath), // 纹理图
  65. transparent: true, // 开启透明度
  66. depthWrite: false, // 禁止深度写入
  67. sizeAttenuation: true,
  68. side: THREE.DoubleSide,
  69. });
  70. // 修正着色器
  71. material.onBeforeCompile = function (shader) {
  72. const vertexShader_attribute = `
  73. attribute float a_opacity;
  74. attribute float a_size;
  75. attribute float a_scale;
  76. varying float v_opacity;
  77. void main() {
  78. v_opacity = a_opacity;
  79. `;
  80. const vertexShader_size = `
  81. gl_PointSize = a_size * a_scale;
  82. `;
  83. shader.vertexShader = shader.vertexShader.replace('void main() {', vertexShader_attribute);
  84. shader.vertexShader = shader.vertexShader.replace('gl_PointSize = size;', vertexShader_size);
  85. const fragmentShader_varying = `
  86. varying float v_opacity;
  87. void main() {
  88. `;
  89. const fragmentShader_opacity = `
  90. vec4 diffuseColor = vec4( diffuse, opacity * v_opacity );
  91. `;
  92. shader.fragmentShader = shader.fragmentShader.replace('void main() {', fragmentShader_varying);
  93. shader.fragmentShader = shader.fragmentShader.replace('vec4 diffuseColor = vec4( diffuse, opacity );', fragmentShader_opacity);
  94. };
  95. // 创建点,并添加进场景
  96. this.pointsMesh = new THREE.Points(geometry, material);
  97. geometry.dispose();
  98. material.dispose();
  99. }
  100. updateFly() {
  101. type attributeType = {
  102. scale: number;
  103. size: number;
  104. opacity: number;
  105. oldOpacity: number;
  106. };
  107. const total = this.points.length - this.section1Num - Math.floor(this.section2Num);
  108. let startIndex = this.direction ? this.section1Num : this.points.length - Math.floor(this.section2Num);
  109. let scale, opacity;
  110. if (this.flyPointArr.size < total - 1) {
  111. let index;
  112. if (this.flyPointArr.size == 0) {
  113. // 起始计算
  114. index = this.direction ? ++startIndex : --startIndex;
  115. } else {
  116. // 根据flyPointArr 已有数量时根据自己的数量结合起点时的数,判断方向, 但是要注意最大、最小的临界值
  117. if (this.direction) {
  118. index = this.flyPointArr.size + startIndex + 1;
  119. } else {
  120. index = startIndex - this.flyPointArr.size - 1;
  121. }
  122. }
  123. if (index != undefined) {
  124. let point: number[] = this.points[index];
  125. if (index >= this.points.length - 1 - this.spreadSection) {
  126. const spreadSectionIndex = this.points.length - 1 - index - this.section2Num;
  127. const factor = -(spreadSectionIndex / (this.spreadSection - this.section2Num + 1)) + 1; // 扩散点对scale的影响
  128. // 扩散气体部分
  129. scale = ((1 + factor) / 4) * 3 + ((1 + factor) / 4) * Math.random();
  130. opacity = 0.01 + (1 - factor) * Math.random();
  131. point = [Math.random() * factor * 3 + point[0], point[1] + scale * factor * 5, Math.random() * factor * 1 + point[2]];
  132. } else {
  133. opacity = (index / this.points.length - 1 - this.spreadSection) * 0.2 * this.opacityFactor + 0.02;
  134. if (this.flyPointArr.size < total - 1 && !this.isStop) {
  135. // 逐渐增加粒子时
  136. scale = 0.6 + Math.random() * 0.3;
  137. }
  138. }
  139. const pointObj = {
  140. index: index,
  141. point: point,
  142. };
  143. const attribute: attributeType = {
  144. scale: scale,
  145. size: this.size,
  146. opacity: opacity,
  147. oldOpacity: opacity,
  148. };
  149. // this.pointAttributeArr.push(attribute);
  150. // 判断点是否已经存在
  151. if (!this.flyPointArr.has(index)) {
  152. this.flyPointArr.set(index, pointObj);
  153. if (index >= this.points.length - 1 - this.spreadSection) {
  154. this.spreadAttributeArr.push(attribute);
  155. } else {
  156. this.noSpreadAttributeArr.push(attribute);
  157. }
  158. }
  159. }
  160. } else {
  161. // 移动点信息
  162. const opacity = (this.flyPointArr.size / total) * 0.2 * this.opacityFactor + 0.02;
  163. const scale = this.scaleFactor + Math.random() * (1 - this.scaleFactor);
  164. const attribute: attributeType = {
  165. scale: scale,
  166. size: this.size,
  167. opacity: opacity,
  168. oldOpacity: opacity,
  169. };
  170. this.noSpreadAttributeArr.pop();
  171. this.noSpreadAttributeArr.unshift(attribute);
  172. const spreadAttributeArr: any[] = [];
  173. for (let i = 0; i < this.spreadAttributeArr.length; i++) {
  174. const spreadAttribute = this.spreadAttributeArr[i];
  175. if (i == 0) {
  176. // this.spreadAttributeArr.pop();
  177. spreadAttribute.opacity = 0.01;
  178. spreadAttribute.scale = 2;
  179. spreadAttribute.size = this.size;
  180. spreadAttributeArr.unshift(spreadAttribute);
  181. } else {
  182. const factor = -(i / this.spreadAttributeArr.length) + 1; // 扩散点对scale的影响
  183. spreadAttribute.opacity = 0.01 + (1 - factor) * Math.random();
  184. spreadAttribute.scale = ((1 + factor) / 4) * 3 + ((1 + factor) / 4) * Math.random();
  185. spreadAttribute.size = this.size;
  186. spreadAttributeArr.push(spreadAttribute);
  187. }
  188. }
  189. this.spreadAttributeArr = spreadAttributeArr;
  190. }
  191. if (this.direction) {
  192. this.pointAttributeArr = [...this.noSpreadAttributeArr, ...this.spreadAttributeArr.reverse()];
  193. } else {
  194. this.pointAttributeArr = [...this.spreadAttributeArr, ...this.noSpreadAttributeArr];
  195. }
  196. if (this.flyPointArr.size > 0) {
  197. // 计算新数据
  198. const posArr = [];
  199. const scaleArr: number[] = [];
  200. const sizeArr: number[] = [];
  201. const opacityArr: number[] = [];
  202. const flyPointArr = this.flyPointArr.entries();
  203. for (let i = 0; i < this.flyPointArr.size; i++) {
  204. const attribute = flyPointArr.next().value[1];
  205. let point = attribute.point;
  206. const index = attribute.index;
  207. if (this.isStop) {
  208. if (index > this.points.length - 1 - this.spreadSection) {
  209. point = [attribute.point[0] - Math.random() * 1, attribute.point[1] - Math.random() * 7, attribute.point[2] - Math.random() * 1];
  210. }
  211. attribute.opacity = attribute.oldOpacity - (1 - this.speed / this.startSpeed) * attribute.oldOpacity;
  212. attribute.scale = (attribute.scale * this.speed) / this.startSpeed;
  213. }
  214. opacity = this.opacityFactor * attribute.opacity;
  215. posArr.push(...point);
  216. }
  217. // if (this.direction) {
  218. // this.pointAttributeArr = this.pointAttributeArr.reverse();
  219. // }
  220. for (let i = 0; i < this.pointAttributeArr.length; i++) {
  221. sizeArr.push(this.pointAttributeArr[i].size);
  222. scaleArr.push(this.pointAttributeArr[i].scale);
  223. opacityArr.push(this.pointAttributeArr[i].opacity);
  224. }
  225. // 更新几何体
  226. this.pointsMesh.geometry.setAttribute('position', new THREE.BufferAttribute(new Float32Array(posArr), 3));
  227. this.pointsMesh.geometry.setAttribute('a_scale', new THREE.BufferAttribute(new Float32Array(scaleArr), 1));
  228. this.pointsMesh.geometry.setAttribute('a_opacity', new THREE.BufferAttribute(new Float32Array(opacityArr), 1));
  229. this.pointsMesh.geometry.setAttribute('a_size', new THREE.BufferAttribute(new Float32Array(sizeArr), 1));
  230. }
  231. }
  232. // 移动
  233. move(duration = 10) {
  234. if (this.frameId) return;
  235. const _this = this;
  236. gsap.to(this, {
  237. opacity: 0.2,
  238. duration: duration,
  239. ease: 'easeInCirc',
  240. overwrite: true,
  241. });
  242. gsap.to(this, {
  243. speed: this.startSpeed,
  244. duration: duration,
  245. ease: 'easeInCirc',
  246. overwrite: true,
  247. });
  248. let startTime = new Date().getTime();
  249. const h = () => {
  250. if (_this.frameId) {
  251. _this.frameId = requestAnimationFrame(h);
  252. }
  253. const now = new Date().getTime();
  254. if (((now - startTime) as number) > this.startSpeed - this.speed + this.speedFactor) {
  255. _this.updateFly();
  256. startTime = new Date().getTime();
  257. }
  258. };
  259. this.frameId = requestAnimationFrame(h);
  260. }
  261. resetParam() {
  262. window.cancelAnimationFrame(this.frameId);
  263. this.frameId = 0;
  264. this.isStop = false;
  265. this.flyPointArr.clear();
  266. this.opacity = 0;
  267. this.speed = 0;
  268. }
  269. changeDirection() {
  270. const _this = this;
  271. this.isStop = true;
  272. return new Promise((resolve) => {
  273. if (this.direction) {
  274. gsap.to(this, {
  275. section2Num: 12,
  276. spreadSection: 16,
  277. speed: 0,
  278. duration: 10,
  279. ease: 'easeInCirc',
  280. overwrite: true,
  281. onComplete: function () {
  282. _this.resetParam();
  283. _this.direction = !_this.direction;
  284. _this.move();
  285. resolve(null);
  286. },
  287. });
  288. } else {
  289. gsap.to(this, {
  290. section2Num: 0,
  291. spreadSection: 20,
  292. speed: 0,
  293. duration: 10,
  294. ease: 'easeInCirc',
  295. overwrite: true,
  296. onComplete: function () {
  297. _this.resetParam();
  298. _this.direction = !_this.direction;
  299. _this.move();
  300. resolve(null);
  301. },
  302. });
  303. }
  304. });
  305. }
  306. stopSmoke() {
  307. const _this = this;
  308. this.isStop = true;
  309. return new Promise((resolve) => {
  310. if (this.direction) {
  311. gsap.to(this, {
  312. speed: 0,
  313. opacity: 0,
  314. duration: 10,
  315. ease: 'easeInCirc',
  316. overwrite: true,
  317. onComplete: function () {
  318. _this.resetParam();
  319. resolve(null);
  320. },
  321. });
  322. } else {
  323. gsap.to(this, {
  324. speed: 0,
  325. opacity: 0,
  326. duration: 10,
  327. ease: 'easeInCirc',
  328. overwrite: true,
  329. onComplete: function () {
  330. _this.resetParam();
  331. resolve(null);
  332. },
  333. });
  334. }
  335. });
  336. }
  337. clearSmoke() {
  338. this.stopSmoke();
  339. this.flyPointArr.clear();
  340. this.pointsMesh.geometry.dispose();
  341. this.pointsMesh.texture.dispose();
  342. this.points = [];
  343. }
  344. }