main.js 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366
  1. import * as THREE from 'https://cdn.jsdelivr.net/npm/[email protected]/build/three.module.js';
  2. import {GLTFLoader} from 'https://cdn.jsdelivr.net/npm/[email protected]/examples/jsm/loaders/GLTFLoader.js';
  3. import {OrbitControls} from 'https://cdn.jsdelivr.net/npm/[email protected]/examples/jsm/controls/OrbitControls.js';
  4. const _VS = `
  5. uniform float pointMultiplier;
  6. attribute float size;
  7. attribute float angle;
  8. attribute vec4 colour;
  9. varying vec4 vColour;
  10. varying vec2 vAngle;
  11. void main() {
  12. vec4 mvPosition = modelViewMatrix * vec4(position, 1.0);
  13. gl_Position = projectionMatrix * mvPosition;
  14. gl_PointSize = size * pointMultiplier / gl_Position.w;
  15. vAngle = vec2(cos(angle), sin(angle));
  16. vColour = colour;
  17. }`;
  18. const _FS = `
  19. uniform sampler2D diffuseTexture;
  20. varying vec4 vColour;
  21. varying vec2 vAngle;
  22. void main() {
  23. vec2 coords = (gl_PointCoord - 0.5) * mat2(vAngle.x, vAngle.y, -vAngle.y, vAngle.x) + 0.5;
  24. gl_FragColor = texture2D(diffuseTexture, coords) * vColour;
  25. }`;
  26. class LinearSpline {
  27. constructor(lerp) {
  28. this._points = [];
  29. this._lerp = lerp;
  30. }
  31. AddPoint(t, d) {
  32. this._points.push([t, d]);
  33. }
  34. Get(t) {
  35. let p1 = 0;
  36. for (let i = 0; i < this._points.length; i++) {
  37. if (this._points[i][0] >= t) {
  38. break;
  39. }
  40. p1 = i;
  41. }
  42. const p2 = Math.min(this._points.length - 1, p1 + 1);
  43. if (p1 == p2) {
  44. return this._points[p1][1];
  45. }
  46. return this._lerp(
  47. (t - this._points[p1][0]) / (
  48. this._points[p2][0] - this._points[p1][0]),
  49. this._points[p1][1], this._points[p2][1]);
  50. }
  51. }
  52. class ParticleSystem {
  53. constructor(params) {
  54. const uniforms = {
  55. diffuseTexture: {
  56. value: new THREE.TextureLoader().load('./resources/fire.png')
  57. },
  58. pointMultiplier: {
  59. value: window.innerHeight / (2.0 * Math.tan(0.5 * 60.0 * Math.PI / 180.0))
  60. }
  61. };
  62. this._material = new THREE.ShaderMaterial({
  63. uniforms: uniforms,
  64. vertexShader: _VS,
  65. fragmentShader: _FS,
  66. blending: THREE.AdditiveBlending,
  67. depthTest: true,
  68. depthWrite: false,
  69. transparent: true,
  70. vertexColors: true
  71. });
  72. this._camera = params.camera;
  73. this._particles = [];
  74. this._geometry = new THREE.BufferGeometry();
  75. this._geometry.setAttribute('position', new THREE.Float32BufferAttribute([], 3));
  76. this._geometry.setAttribute('size', new THREE.Float32BufferAttribute([], 1));
  77. this._geometry.setAttribute('colour', new THREE.Float32BufferAttribute([], 4));
  78. this._geometry.setAttribute('angle', new THREE.Float32BufferAttribute([], 1));
  79. this._points = new THREE.Points(this._geometry, this._material);
  80. params.parent.add(this._points);
  81. this._alphaSpline = new LinearSpline((t, a, b) => {
  82. return a + t * (b - a);
  83. });
  84. this._alphaSpline.AddPoint(0.0, 0.0);
  85. this._alphaSpline.AddPoint(0.1, 1.0);
  86. this._alphaSpline.AddPoint(0.6, 1.0);
  87. this._alphaSpline.AddPoint(1.0, 0.0);
  88. this._colourSpline = new LinearSpline((t, a, b) => {
  89. const c = a.clone();
  90. return c.lerp(b, t);
  91. });
  92. this._colourSpline.AddPoint(0.0, new THREE.Color(0xFFFF80));
  93. this._colourSpline.AddPoint(1.0, new THREE.Color(0xFF8080));
  94. this._sizeSpline = new LinearSpline((t, a, b) => {
  95. return a + t * (b - a);
  96. });
  97. this._sizeSpline.AddPoint(0.0, 1.0);
  98. this._sizeSpline.AddPoint(0.5, 5.0);
  99. this._sizeSpline.AddPoint(1.0, 1.0);
  100. document.addEventListener('keyup', (e) => this._onKeyUp(e), false);
  101. this._UpdateGeometry();
  102. }
  103. _onKeyUp(event) {
  104. switch(event.keyCode) {
  105. case 32: // SPACE
  106. this._AddParticles();
  107. break;
  108. }
  109. }
  110. _AddParticles(timeElapsed) {
  111. if (!this.gdfsghk) {
  112. this.gdfsghk = 0.0;
  113. }
  114. this.gdfsghk += timeElapsed;
  115. const n = Math.floor(this.gdfsghk * 75.0);
  116. this.gdfsghk -= n / 75.0;
  117. for (let i = 0; i < n; i++) {
  118. const life = (Math.random() * 0.75 + 0.25) * 10.0;
  119. this._particles.push({
  120. position: new THREE.Vector3(
  121. (Math.random() * 2 - 1) * 1.0,
  122. (Math.random() * 2 - 1) * 1.0,
  123. (Math.random() * 2 - 1) * 1.0),
  124. size: (Math.random() * 0.5 + 0.5) * 4.0,
  125. colour: new THREE.Color(),
  126. alpha: 1.0,
  127. life: life,
  128. maxLife: life,
  129. rotation: Math.random() * 2.0 * Math.PI,
  130. velocity: new THREE.Vector3(0, -15, 0),
  131. });
  132. }
  133. }
  134. _UpdateGeometry() {
  135. const positions = [];
  136. const sizes = [];
  137. const colours = [];
  138. const angles = [];
  139. for (let p of this._particles) {
  140. positions.push(p.position.x, p.position.y, p.position.z);
  141. colours.push(p.colour.r, p.colour.g, p.colour.b, p.alpha);
  142. sizes.push(p.currentSize);
  143. angles.push(p.rotation);
  144. }
  145. this._geometry.setAttribute(
  146. 'position', new THREE.Float32BufferAttribute(positions, 3));
  147. this._geometry.setAttribute(
  148. 'size', new THREE.Float32BufferAttribute(sizes, 1));
  149. this._geometry.setAttribute(
  150. 'colour', new THREE.Float32BufferAttribute(colours, 4));
  151. this._geometry.setAttribute(
  152. 'angle', new THREE.Float32BufferAttribute(angles, 1));
  153. this._geometry.attributes.position.needsUpdate = true;
  154. this._geometry.attributes.size.needsUpdate = true;
  155. this._geometry.attributes.colour.needsUpdate = true;
  156. this._geometry.attributes.angle.needsUpdate = true;
  157. }
  158. _UpdateParticles(timeElapsed) {
  159. for (let p of this._particles) {
  160. p.life -= timeElapsed;
  161. }
  162. this._particles = this._particles.filter(p => {
  163. return p.life > 0.0;
  164. });
  165. for (let p of this._particles) {
  166. const t = 1.0 - p.life / p.maxLife;
  167. p.rotation += timeElapsed * 0.5;
  168. p.alpha = this._alphaSpline.Get(t);
  169. p.currentSize = p.size * this._sizeSpline.Get(t);
  170. p.colour.copy(this._colourSpline.Get(t));
  171. p.position.add(p.velocity.clone().multiplyScalar(timeElapsed));
  172. const drag = p.velocity.clone();
  173. drag.multiplyScalar(timeElapsed * 0.1);
  174. drag.x = Math.sign(p.velocity.x) * Math.min(Math.abs(drag.x), Math.abs(p.velocity.x));
  175. drag.y = Math.sign(p.velocity.y) * Math.min(Math.abs(drag.y), Math.abs(p.velocity.y));
  176. drag.z = Math.sign(p.velocity.z) * Math.min(Math.abs(drag.z), Math.abs(p.velocity.z));
  177. p.velocity.sub(drag);
  178. }
  179. this._particles.sort((a, b) => {
  180. const d1 = this._camera.position.distanceTo(a.position);
  181. const d2 = this._camera.position.distanceTo(b.position);
  182. if (d1 > d2) {
  183. return -1;
  184. }
  185. if (d1 < d2) {
  186. return 1;
  187. }
  188. return 0;
  189. });
  190. }
  191. Step(timeElapsed) {
  192. this._AddParticles(timeElapsed);
  193. this._UpdateParticles(timeElapsed);
  194. this._UpdateGeometry();
  195. }
  196. }
  197. class ParticleSystemDemo {
  198. constructor() {
  199. this._Initialize();
  200. }
  201. _Initialize() {
  202. this._threejs = new THREE.WebGLRenderer({
  203. antialias: true,
  204. });
  205. this._threejs.shadowMap.enabled = true;
  206. this._threejs.shadowMap.type = THREE.PCFSoftShadowMap;
  207. this._threejs.setPixelRatio(window.devicePixelRatio);
  208. this._threejs.setSize(window.innerWidth, window.innerHeight);
  209. document.body.appendChild(this._threejs.domElement);
  210. window.addEventListener('resize', () => {
  211. this._OnWindowResize();
  212. }, false);
  213. const fov = 60;
  214. const aspect = 1920 / 1080;
  215. const near = 1.0;
  216. const far = 1000.0;
  217. this._camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
  218. this._camera.position.set(25, 10, 0);
  219. this._scene = new THREE.Scene();
  220. let light = new THREE.DirectionalLight(0xFFFFFF, 1.0);
  221. light.position.set(20, 100, 10);
  222. light.target.position.set(0, 0, 0);
  223. light.castShadow = true;
  224. light.shadow.bias = -0.001;
  225. light.shadow.mapSize.width = 2048;
  226. light.shadow.mapSize.height = 2048;
  227. light.shadow.camera.near = 0.1;
  228. light.shadow.camera.far = 500.0;
  229. light.shadow.camera.near = 0.5;
  230. light.shadow.camera.far = 500.0;
  231. light.shadow.camera.left = 100;
  232. light.shadow.camera.right = -100;
  233. light.shadow.camera.top = 100;
  234. light.shadow.camera.bottom = -100;
  235. this._scene.add(light);
  236. light = new THREE.AmbientLight(0x101010);
  237. this._scene.add(light);
  238. const controls = new OrbitControls(
  239. this._camera, this._threejs.domElement);
  240. controls.target.set(0, 0, 0);
  241. controls.update();
  242. const loader = new THREE.CubeTextureLoader();
  243. const texture = loader.load([
  244. './resources/posx.jpg',
  245. './resources/negx.jpg',
  246. './resources/posy.jpg',
  247. './resources/negy.jpg',
  248. './resources/posz.jpg',
  249. './resources/negz.jpg',
  250. ]);
  251. this._scene.background = texture;
  252. this._particles = new ParticleSystem({
  253. parent: this._scene,
  254. camera: this._camera,
  255. });
  256. this._LoadModel();
  257. this._previousRAF = null;
  258. this._RAF();
  259. }
  260. _LoadModel() {
  261. const loader = new GLTFLoader();
  262. loader.load('./resources/rocket/Rocket_Ship_01.gltf', (gltf) => {
  263. gltf.scene.traverse(c => {
  264. c.castShadow = true;
  265. });
  266. this._scene.add(gltf.scene);
  267. });
  268. }
  269. _OnWindowResize() {
  270. this._camera.aspect = window.innerWidth / window.innerHeight;
  271. this._camera.updateProjectionMatrix();
  272. this._threejs.setSize(window.innerWidth, window.innerHeight);
  273. }
  274. _RAF() {
  275. requestAnimationFrame((t) => {
  276. if (this._previousRAF === null) {
  277. this._previousRAF = t;
  278. }
  279. this._RAF();
  280. this._threejs.render(this._scene, this._camera);
  281. this._Step(t - this._previousRAF);
  282. this._previousRAF = t;
  283. });
  284. }
  285. _Step(timeElapsed) {
  286. const timeElapsedS = timeElapsed * 0.001;
  287. this._particles.Step(timeElapsedS);
  288. }
  289. }
  290. let _APP = null;
  291. window.addEventListener('DOMContentLoaded', () => {
  292. _APP = new ParticleSystemDemo();
  293. });