├── public ├── font.mp4 └── galaxie2.png ├── src └── shaders │ ├── fragment.glsl │ ├── computeShaderPosition.glsl │ ├── vertex.glsl │ └── computeShaderVelocity.glsl ├── .gitignore ├── package.json ├── LICENSE ├── README.md ├── index.html ├── style.css └── main.js /public/font.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/N0rvel/galaxy_sim/HEAD/public/font.mp4 -------------------------------------------------------------------------------- /public/galaxie2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/N0rvel/galaxy_sim/HEAD/public/galaxie2.png -------------------------------------------------------------------------------- /src/shaders/fragment.glsl: -------------------------------------------------------------------------------- 1 | export default ` 2 | 3 | varying vec4 vColor; 4 | 5 | 6 | void main() 7 | { 8 | gl_FragColor = vColor; 9 | } 10 | ` 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "galaxy-threejs", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "vite build", 9 | "preview": "vite preview" 10 | }, 11 | "devDependencies": { 12 | "vite": "^3.0.0" 13 | }, 14 | "dependencies": { 15 | "@types/dat.gui": "^0.7.7", 16 | "dat.gui": "^0.7.9", 17 | "three": "^0.142.0" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/shaders/computeShaderPosition.glsl: -------------------------------------------------------------------------------- 1 | export default ` 2 | #define delta ( 1.0 / 30.0 ) 3 | 4 | void main() { 5 | 6 | vec2 uv = gl_FragCoord.xy / resolution.xy; 7 | 8 | vec4 tmpPos = texture2D( texturePosition, uv ); 9 | vec3 pos = tmpPos.xyz; 10 | 11 | vec4 tmpVel = texture2D( textureVelocity, uv ); 12 | vec3 vel = tmpVel.xyz; 13 | 14 | // Dynamics 15 | if (pos.x != 0.0 && pos.y != 0.0 && pos.z != 0.0){ 16 | pos += vel * delta; 17 | } 18 | 19 | // Output the positions and isDarkMatter in the output color 20 | gl_FragColor = vec4( pos, tmpPos.w); 21 | } 22 | ` 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Bastien Proudhom 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Galaxy Simulation with ThreeJS and Shaders 2 | 3 | This project is a simulation of thousands of particles that reproduce gravity to simulate galaxies. It uses ThreeJS for 3D rendering and shaders to calculate the movement of particles in real-time. 4 | 5 | ![Aperçu du projet](https://i.imgur.com/4NpWBF9.jpeg) 6 | 7 | # Demo 8 | Live demo here: https://galaxym.ovh 9 | 10 | ## Usage 11 | 12 | 1. Start the development server: `npm run dev` 13 | 2. Open your browser and navigate to `http://127.0.0.1:5174/` 14 | 15 | 16 | ## Features 17 | 18 | - Simulation of thousands of particles that reproduce gravity to simulate galaxies 19 | 20 | 21 | - A menu to change the settings of the simulation (like the graphics settings, the number of particles, the interaction rate...) 22 | 23 | 24 | - A camera controled with the mouse and wheel to zoom-in/zoom-out 25 | 26 | - It can simulate an entire expanding universe : 27 | ![Aperçu du projet](https://i.imgur.com/W1AqYoJ.png) 28 | 29 | - It can simulate a unique galaxy : 30 | ![Aperçu du projet](https://i.imgur.com/cUygkAD.png) 31 | 32 | 33 | - It can simulate the collision between two galaxies : 34 | ![Aperçu du projet](https://i.imgur.com/9EOj1SP.png) 35 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Galaxy Simulation 10 | 11 | 12 |
13 |
14 | 17 |
18 |
19 |
20 | 21 | 22 |
23 | 24 |
25 | 26 | Github Repository 27 |
28 |
29 |
30 |
31 |
32 | 33 | 34 | -------------------------------------------------------------------------------- /src/shaders/vertex.glsl: -------------------------------------------------------------------------------- 1 | export default ` 2 | // For PI declaration: 3 | #include 4 | 5 | // Declare uniforms for texture samplers 6 | uniform sampler2D texturePosition; 7 | uniform sampler2D textureVelocity; 8 | 9 | // Declare uniforms for camera parameters and particle count 10 | uniform float cameraConstant; 11 | uniform float particlesCount; 12 | // Declare constant for maximum acceleration that can be displayed 13 | uniform float uMaxAccelerationColor; 14 | 15 | // Declare uniform for luminosity 16 | uniform float uLuminosity; 17 | uniform float uHideDarkMatter; 18 | 19 | // Declare varying variable for color 20 | varying vec4 vColor; 21 | 22 | 23 | // Normalize an acceleration value to a range of 0 to 1 24 | float normalized(float acc){ 25 | return (acc-0.)/(uMaxAccelerationColor-0.); 26 | } 27 | 28 | void main() { 29 | // Retrieve position data from texture 30 | vec4 posTemp = texture2D( texturePosition, uv ); 31 | vec3 pos = posTemp.xyz; 32 | float hideDarkMatter = posTemp.w; 33 | 34 | // Retrieve velocity data from texture and calculate acceleration 35 | vec4 velTemp = texture2D( textureVelocity, uv ); 36 | vec3 vel = velTemp.xyz; 37 | float acc = velTemp.w; 38 | 39 | vec4 mvPosition = modelViewMatrix * vec4( pos, 1.0 ); 40 | 41 | /** 42 | * Size 43 | */ 44 | gl_PointSize = 1.0; 45 | // Scale point size based on the distance of the particle from the camera 46 | gl_PointSize *= ( 1.0 / - mvPosition.z ); 47 | 48 | // Calculate the final position of the particle using the projection matrix 49 | gl_Position = projectionMatrix * mvPosition; 50 | 51 | /** 52 | * Color 53 | */ 54 | // Declare colors for low and high acceleration vec3(1.,0.843,0.388) 55 | vec3 hightAccelerationColor= vec3(1.,0.376,0.188); 56 | vec3 lowAccelerationColor= vec3(0.012,0.063,0.988); 57 | vec3 finalColor = vec3(0.0,0.0,0.0); 58 | if(uHideDarkMatter == 1.0) { 59 | if(hideDarkMatter == 0.0){ 60 | // Interpolate color based on acceleration 61 | finalColor = mix(lowAccelerationColor, hightAccelerationColor, normalized(acc)); 62 | } else { 63 | finalColor = vec3(0.0,0.0,0.0); 64 | } 65 | } else { 66 | finalColor = mix(lowAccelerationColor, hightAccelerationColor, normalized(acc)); 67 | } 68 | 69 | 70 | // Set the color of the particle 71 | vColor = vec4(finalColor, uLuminosity); 72 | } 73 | ` 74 | -------------------------------------------------------------------------------- /style.css: -------------------------------------------------------------------------------- 1 | body{ 2 | margin: 0; 3 | } 4 | 5 | a{ 6 | display: block; 7 | text-decoration: inherit; /* no underline */ 8 | font-family: "Inter var",ui-sans-serif,system-ui,-apple-system,system-ui,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji"; 9 | font-size: 1.125rem; 10 | font-weight: 600; 11 | border: 1px solid transparent; 12 | border-radius: .75rem; 13 | box-sizing: border-box; 14 | color: #FFFFFF; 15 | width: 520px; 16 | background-color: #111827; 17 | padding: 14px; 18 | height: 77px; 19 | fill: #FFFFFF; 20 | } 21 | 22 | .dg li:not(.folder){ 23 | background: #1a1a1ad1!important; 24 | } 25 | 26 | .main-container{ 27 | height: 100vh; 28 | background: black; 29 | display: flex; 30 | align-items: center; 31 | justify-content: center; 32 | } 33 | 34 | .button-container{ 35 | display: flex; 36 | justify-content: center; 37 | width: 100%; 38 | z-index: 1; 39 | margin-bottom: 10px; 40 | flex-wrap: wrap; 41 | } 42 | 43 | .calque{ 44 | z-index: 1; 45 | width: 100vw; 46 | height: 100vh; 47 | } 48 | 49 | .container-buttons{ 50 | display: flex; 51 | justify-content: center; 52 | width: 100%; 53 | z-index: 1; 54 | margin-top: 415px; 55 | flex-wrap: wrap; 56 | } 57 | 58 | .button-svg{ 59 | display: flex; 60 | align-items: center; 61 | } 62 | 63 | .button-svg span{ 64 | margin-left: 20px; 65 | } 66 | 67 | .button { 68 | margin: 0 10px 10px 10px; 69 | background-color: #111827; 70 | border: 1px solid transparent; 71 | border-radius: .75rem; 72 | box-sizing: border-box; 73 | color: #FFFFFF; 74 | cursor: pointer; 75 | flex: 0 0 auto; 76 | font-family: "Inter var",ui-sans-serif,system-ui,-apple-system,system-ui,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji"; 77 | font-size: 1.125rem; 78 | font-weight: 600; 79 | line-height: 1.5rem; 80 | padding: .75rem 1.2rem; 81 | text-align: center; 82 | text-decoration: none #6B7280 solid; 83 | text-decoration-thickness: auto; 84 | transition-duration: .2s; 85 | transition-property: background-color,border-color,color,fill,stroke; 86 | transition-timing-function: cubic-bezier(.4, 0, 0.2, 1); 87 | user-select: none; 88 | -webkit-user-select: none; 89 | touch-action: manipulation; 90 | width: 250px; 91 | } 92 | 93 | .button:hover, a:hover{ 94 | background-color: #374151; 95 | cursor: pointer; 96 | } 97 | 98 | .button:focus { 99 | box-shadow: none; 100 | outline: 2px solid transparent; 101 | outline-offset: 2px; 102 | } 103 | .small{ 104 | font-size: 11px; 105 | } 106 | 107 | #fontVideo{ 108 | max-height: 1080px; 109 | max-width: 1920px; 110 | height: 100vh; 111 | width: 100vw; 112 | object-fit: cover; 113 | position: fixed; 114 | } -------------------------------------------------------------------------------- /src/shaders/computeShaderVelocity.glsl: -------------------------------------------------------------------------------- 1 | export default ` 2 | // For PI declaration: 3 | #include 4 | 5 | uniform float timeStep; 6 | uniform float gravity; 7 | uniform float interactionRate; 8 | uniform float blackHoleForce; 9 | uniform float uMaxAccelerationColor; 10 | 11 | 12 | const float width = resolution.x; 13 | const float height = resolution.y; 14 | 15 | 16 | void main() { 17 | 18 | // Calculate the ID and UV coordinate of the current pixel 19 | vec2 uv = gl_FragCoord.xy / resolution.xy; 20 | float idParticle = uv.y * resolution.x + uv.x; 21 | 22 | // Sample the position and velocity of the current particle from the input textures 23 | vec4 tmpPos = texture2D( texturePosition, uv ); 24 | vec3 pos = tmpPos.xyz; 25 | 26 | vec4 tmpVel = texture2D( textureVelocity, uv ); 27 | vec3 vel = tmpVel.xyz; 28 | 29 | float accColor = tmpVel.w; 30 | 31 | // Initialize the acceleration to zero 32 | vec3 acceleration = vec3( 0.0 ); 33 | 34 | // Calculate the acceleration due to gravity from all other particles 35 | for ( float y = 0.0; y < height * interactionRate; y++ ) { 36 | for ( float x = 0.0; x < width * interactionRate; x++ ) { 37 | // Calculate the UV coordinate of the other particle 38 | vec2 secondParticleCoords = vec2( x + 0.5, y + 0.5) / resolution.xy; 39 | // Sample the position and velocity of the other particle 40 | vec3 pos2 = texture2D( texturePosition, secondParticleCoords ).xyz; 41 | vec4 velTemp2 = texture2D( textureVelocity, secondParticleCoords ); 42 | vec3 vel2 = velTemp2.xyz; 43 | 44 | // Calculate the ID of the other particle 45 | float idParticle2 = secondParticleCoords.y * resolution.x + secondParticleCoords.x; 46 | 47 | // Skip the current particle 48 | if ( idParticle == idParticle2 ) { 49 | continue; 50 | } 51 | 52 | // Calculate the distance and displacement between the two particles 53 | vec3 dPos = pos2 - pos; 54 | float distance = length( dPos ); 55 | 56 | // Calculate the acceleration due to gravity using Newton's law of universal gravitation 57 | float distanceSq = (distance * distance) + 1.0; 58 | float gravityField = gravity * 1.0 / distanceSq; 59 | 60 | // Limit the maximum acceleration due to gravity 61 | gravityField = min( gravityField, 1.0 ); 62 | // Use a stronger force for the black hole 63 | if(pos2.x == 0.0 && pos2.y == 0.0 && pos2.z == 0.0){ 64 | gravityField = gravity * blackHoleForce / distanceSq; 65 | } 66 | // Add the acceleration to the total acceleration 67 | acceleration += gravityField * normalize( dPos ); 68 | } 69 | } 70 | 71 | // Update the velocity based on the acceleration and elapsed time 72 | vel += timeStep * acceleration; 73 | 74 | // Store the acceleration in the fourth component of the output color 75 | accColor = length(acceleration); 76 | if (length(accColor) > uMaxAccelerationColor) { 77 | // If it does, set it to the maximum value 78 | accColor = normalize(accColor) * uMaxAccelerationColor; 79 | } 80 | 81 | // Output the velocity and acceleration in the output color 82 | gl_FragColor = vec4( vel, accColor ); 83 | } 84 | ` 85 | -------------------------------------------------------------------------------- /main.js: -------------------------------------------------------------------------------- 1 | import galaxyVortexShader from '/src/shaders/vertex.glsl'; 2 | import galaxyFragmentShader from '/src/shaders/fragment.glsl'; 3 | import computeShaderVelocity from '/src/shaders/computeShaderVelocity.glsl'; 4 | import computeShaderPosition from '/src/shaders/computeShaderPosition.glsl'; 5 | import {GUI} from "dat.gui"; 6 | import * as THREE from "three"; 7 | import Stats from "three/examples/jsm/libs/stats.module"; 8 | 9 | import {OrbitControls} from "three/examples/jsm/controls/OrbitControls"; 10 | import {GPUComputationRenderer} from "three/examples/jsm/misc/GPUComputationRenderer"; 11 | import {EffectComposer} from "three/examples/jsm/postprocessing/EffectComposer"; 12 | import {UnrealBloomPass} from "three/examples/jsm/postprocessing/UnrealBloomPass"; 13 | import {RenderPass} from "three/examples/jsm/postprocessing/RenderPass"; 14 | import {ShaderPass} from "three/examples/jsm/postprocessing/ShaderPass"; 15 | import {BlendShader} from "three/examples/jsm/shaders/BlendShader"; 16 | import {SavePass} from "three/examples/jsm/postprocessing/SavePass"; 17 | import {CopyShader} from "three/examples/jsm/shaders/CopyShader"; 18 | 19 | let container, stats; 20 | let camera, scene, renderer, geometry, composer; 21 | 22 | 23 | let gpuCompute; 24 | let velocityVariable; 25 | let positionVariable; 26 | let velocityUniforms; 27 | let particleUniforms; 28 | let effectController; 29 | let particles; 30 | let material; 31 | let controls; 32 | let luminosity; 33 | let paused = false; 34 | let autoRotation = true; 35 | let bloom = { strength: 1.0}; 36 | let bloomPass; 37 | // motion blur 38 | let renderTargetParameters; 39 | let savePass; 40 | let blendPass; 41 | /*--------------------------INITIALISATION-----------------------------------------------*/ 42 | const gravity = 20; 43 | const interactionRate = 1.0; 44 | const timeStep = 0.001; 45 | const blackHoleForce = 100.0; 46 | const constLuminosity = 1.0; 47 | const numberOfStars = 30000; 48 | const radius = 100; 49 | const height = 5; 50 | const middleVelocity = 2; 51 | const velocity = 15; 52 | const typeOfSimulation = { "Galaxie": 1, "Univers": 2, "Collision de galaxies": 3 }; 53 | renderTargetParameters = { 54 | minFilter: THREE.LinearFilter, 55 | magFilter: THREE.LinearFilter, 56 | stencilBuffer: false 57 | }; 58 | 59 | // save pass 60 | savePass = new SavePass( 61 | new THREE.WebGLRenderTarget( 62 | window.innerWidth, 63 | window.innerHeight, 64 | renderTargetParameters 65 | ) 66 | ); 67 | 68 | // blend pass 69 | blendPass = new ShaderPass(BlendShader, "tDiffuse1"); 70 | blendPass.uniforms["tDiffuse2"].value = savePass.renderTarget.texture; 71 | blendPass.uniforms["mixRatio"].value = 0.5; 72 | 73 | // output pass 74 | const outputPass = new ShaderPass(CopyShader); 75 | outputPass.renderToScreen = true; 76 | 77 | effectController = { 78 | // Can be changed dynamically 79 | gravity: gravity, 80 | interactionRate: interactionRate, 81 | timeStep: timeStep, 82 | blackHoleForce: blackHoleForce, 83 | luminosity: constLuminosity, 84 | maxAccelerationColor: 50.0, 85 | maxAccelerationColorPercent: 5, 86 | motionBlur: false, 87 | hideDarkMatter: false, 88 | 89 | // Must restart simulation 90 | numberOfStars: numberOfStars, 91 | radius: radius, 92 | height: height, 93 | middleVelocity: middleVelocity, 94 | velocity: velocity, 95 | typeOfSimulation: 1, 96 | autoRotation: false 97 | }; 98 | 99 | let PARTICLES = effectController.numberOfStars; 100 | 101 | // 1 = normal mode ; 2 = experimental mode 102 | let selectedChoice = 1; 103 | document.getElementById("choice1").addEventListener("click", () => selectChoice(1)); 104 | document.getElementById("choice2").addEventListener("click", () => selectChoice(2)); 105 | function selectChoice(choice) { 106 | selectedChoice = choice; 107 | document.getElementById("main-container").remove(); 108 | if (selectedChoice === 1){ 109 | effectController = { 110 | // Can be changed dynamically 111 | gravity: gravity, 112 | interactionRate: 0.5, 113 | timeStep: timeStep, 114 | blackHoleForce: blackHoleForce, 115 | luminosity: constLuminosity, 116 | maxAccelerationColor: 4.0, 117 | maxAccelerationColorPercent: 0.4, 118 | motionBlur: false, 119 | hideDarkMatter: false, 120 | 121 | // Must restart simulation 122 | numberOfStars: 10000, 123 | radius: 50, 124 | height: height, 125 | middleVelocity: middleVelocity, 126 | velocity: 7, 127 | typeOfSimulation: 1, 128 | autoRotation: false 129 | }; 130 | } 131 | init(effectController.typeOfSimulation.toString()); 132 | animate(); 133 | } 134 | 135 | 136 | /*-------------------------------------------------------------------------*/ 137 | 138 | /** 139 | * 140 | * @param typeOfSimulation 141 | */ 142 | function init(typeOfSimulation) { 143 | 144 | container = document.createElement( 'div' ); 145 | document.body.appendChild( container ); 146 | 147 | camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.01, 9999999999999999999 ); 148 | camera.position.x = 15 149 | camera.position.y = 112; 150 | camera.position.z = 168; 151 | 152 | if (effectController.typeOfSimulation === 3){ 153 | camera.position.x = 15 154 | camera.position.y = 456; 155 | camera.position.z = 504; 156 | } 157 | 158 | if (selectedChoice === 1 && effectController.typeOfSimulation === 2){ 159 | camera.position.x = 15 160 | camera.position.y = 456; 161 | camera.position.z = 504; 162 | } 163 | 164 | 165 | scene = new THREE.Scene(); 166 | 167 | renderer = new THREE.WebGLRenderer(); 168 | renderer.setPixelRatio( window.devicePixelRatio ); 169 | renderer.setSize( window.innerWidth, window.innerHeight ); 170 | container.appendChild( renderer.domElement ); 171 | 172 | controls = new OrbitControls( camera, renderer.domElement ); 173 | if (effectController.typeOfSimulation === 1 || effectController.typeOfSimulation === 3) { 174 | controls.autoRotate = false; 175 | } else if (effectController.typeOfSimulation === 2){ 176 | controls.autoRotate = true; 177 | controls.autoRotateSpeed = -1.0; 178 | } 179 | 180 | initComputeRenderer(typeOfSimulation); 181 | 182 | // Show fps, ping, etc 183 | stats = new Stats(); 184 | container.appendChild( stats.dom ); 185 | 186 | window.addEventListener( 'resize', onWindowResize ); 187 | 188 | initGUI(); 189 | initParticles(typeOfSimulation); 190 | dynamicValuesChanger(); 191 | const renderScene = new RenderPass( scene, camera ); 192 | 193 | /* ---- Adding bloom effect ---- */ 194 | bloomPass = new UnrealBloomPass( 195 | new THREE.Vector2( window.innerWidth, window.innerHeight ), 196 | 0, 197 | 0, 198 | 0 199 | ); 200 | bloomPass.strength = bloom.strength; 201 | 202 | composer = new EffectComposer( renderer ); 203 | composer.addPass( renderScene ); 204 | composer.addPass( bloomPass ); 205 | composer.addPass(blendPass); 206 | composer.addPass(savePass); 207 | composer.addPass(outputPass); 208 | } 209 | 210 | function initComputeRenderer(typeOfSimulation) { 211 | let textureSize = Math.round(Math.sqrt(effectController.numberOfStars)); 212 | gpuCompute = new GPUComputationRenderer( textureSize, textureSize, renderer ); 213 | if ( renderer.capabilities.isWebGL2 === false ) { 214 | gpuCompute.setDataType( THREE.HalfFloatType ); 215 | } 216 | 217 | const dtPosition = gpuCompute.createTexture(); 218 | const dtVelocity = gpuCompute.createTexture(); 219 | 220 | if (typeOfSimulation === "1"){ 221 | fillTextures( dtPosition, dtVelocity ); 222 | } else if (typeOfSimulation === "2"){ 223 | fillUniverseTextures( dtPosition, dtVelocity ) 224 | } else if (typeOfSimulation === "3"){ 225 | fillGalaxiesCollisionTextures( dtPosition, dtVelocity ) 226 | } 227 | 228 | velocityVariable = gpuCompute.addVariable( 'textureVelocity', computeShaderVelocity, dtVelocity ); 229 | positionVariable = gpuCompute.addVariable( 'texturePosition', computeShaderPosition, dtPosition ); 230 | 231 | gpuCompute.setVariableDependencies( velocityVariable, [ positionVariable, velocityVariable ] ); 232 | gpuCompute.setVariableDependencies( positionVariable, [ positionVariable, velocityVariable ] ); 233 | 234 | velocityUniforms = velocityVariable.material.uniforms; 235 | velocityUniforms[ 'gravity' ] = { value: 0.0 }; 236 | velocityUniforms[ 'interactionRate' ] = { value: 0.0 }; 237 | velocityUniforms[ 'timeStep' ] = { value: 0.0 }; 238 | velocityUniforms[ 'uMaxAccelerationColor' ] = { value: 0.0 }; 239 | velocityUniforms[ 'blackHoleForce' ] = { value: 0.0 }; 240 | velocityUniforms[ 'luminosity' ] = { value: 0.0 }; 241 | 242 | const error = gpuCompute.init(); 243 | 244 | if ( error !== null ) { 245 | console.error( error ); 246 | } 247 | } 248 | 249 | /** 250 | * Init particles (material, positions, uvs coordinates) 251 | * @param typeOfSimulation 252 | */ 253 | function initParticles(typeOfSimulation) { 254 | 255 | // Create a buffer geometry to store the particle data 256 | geometry = new THREE.BufferGeometry(); 257 | 258 | // Create array to store the position of the particles 259 | const positions = new Float32Array( PARTICLES * 3 ); 260 | 261 | // Create an array to store the UV coordinates of each particle 262 | const uvs = new Float32Array( PARTICLES * 2 ); 263 | 264 | // Calculate the size of the matrix based on the number of particles 265 | let matrixSize = Math.sqrt(effectController.numberOfStars); 266 | let p = 0; 267 | for ( let j = 0; j < matrixSize; j ++ ) { 268 | for ( let i = 0; i < matrixSize; i ++ ) { 269 | uvs[ p ++ ] = i / ( matrixSize - 1 ); 270 | uvs[ p ++ ] = j / ( matrixSize - 1 ); 271 | } 272 | } 273 | 274 | geometry.setAttribute( 'position', new THREE.BufferAttribute( positions, 3 ) ); 275 | geometry.setAttribute( 'uv', new THREE.BufferAttribute( uvs, 2 ) ); 276 | 277 | particleUniforms = { 278 | 'texturePosition': { value: null }, 279 | 'textureVelocity': { value: null }, 280 | 'cameraConstant': { value: getCameraConstant( camera ) }, 281 | 'particlesCount': { value: PARTICLES }, 282 | 'uMaxAccelerationColor': { value: effectController.maxAccelerationColor }, 283 | 'uLuminosity' : { value: luminosity}, 284 | 'uHideDarkMatter' : { value: effectController.hideDarkMatter}, 285 | }; 286 | 287 | // THREE.ShaderMaterial 288 | // Create the material of the particles 289 | material = new THREE.ShaderMaterial( { 290 | depthWrite: false, 291 | blending: THREE.AdditiveBlending, 292 | vertexColors: true, 293 | uniforms: particleUniforms, 294 | vertexShader: galaxyVortexShader, 295 | fragmentShader: galaxyFragmentShader 296 | }); 297 | if (typeOfSimulation === "2"){ 298 | material = new THREE.ShaderMaterial( { 299 | depthWrite: false, 300 | blending: THREE.AdditiveBlending, 301 | vertexColors: true, 302 | uniforms: particleUniforms, 303 | vertexShader: galaxyVortexShader, 304 | fragmentShader: galaxyFragmentShader 305 | }); 306 | } 307 | 308 | particles = new THREE.Points( geometry, material ); 309 | particles.frustumCulled = false; 310 | particles.matrixAutoUpdate = false; 311 | particles.updateMatrix(); 312 | scene.add( particles ); 313 | } 314 | 315 | /** 316 | * Init positions et volocities for all particles 317 | * @param texturePosition array that contain positions of particles 318 | * @param textureVelocity array that contain velocities of particles 319 | */ 320 | function fillTextures( texturePosition, textureVelocity ) { 321 | 322 | const posArray = texturePosition.image.data; 323 | const velArray = textureVelocity.image.data; 324 | 325 | const radius = effectController.radius; 326 | const height = effectController.height; 327 | const middleVelocity = effectController.middleVelocity; 328 | const maxVel = effectController.velocity; 329 | 330 | for ( let k = 0, kl = posArray.length; k < kl; k += 4 ) { 331 | // Position 332 | let x, z, rr, y, vx, vy, vz; 333 | // The first particle will be the black hole 334 | if (k === 0){ 335 | x = 0; 336 | z = 0; 337 | y = 0; 338 | rr = 0; 339 | } else { 340 | // Generate random position for the particle within the radius 341 | do { 342 | x = ( Math.random() * 2 - 1 ); 343 | z = ( Math.random() * 2 - 1 ); 344 | // The variable rr is used to calculate the distance from the center of the radius for each particle. 345 | // It is used in the calculation of rExp which is used to determine the position of the particle within the radius. 346 | // If a particle is closer to the center, rr will be smaller, and rExp will be larger, which means that the particle will be placed closer to the center. 347 | // It also can affect the velocity of the particle as it is used in the calculation of the velocity of the particle. 348 | rr = x * x + z * z; 349 | 350 | } while ( rr > 1 ); 351 | rr = Math.sqrt( rr ); 352 | 353 | const rExp = radius * Math.pow( rr, middleVelocity ); 354 | 355 | // Velocity 356 | const vel = maxVel * Math.pow( rr, 0.2 ); 357 | 358 | vx = vel * z + ( Math.random() * 2 - 1 ) * 0.001; 359 | vy = ( Math.random() * 2 - 1 ) * 0.001 * 0.05; 360 | vz = - vel * x + ( Math.random() * 2 - 1 ) * 0.001; 361 | 362 | x *= rExp; 363 | z *= rExp; 364 | y = ( Math.random() * 2 - 1 ) * height; 365 | } 366 | 367 | // Fill in texture values 368 | posArray[ k + 0 ] = x; 369 | posArray[ k + 1 ] = y; 370 | posArray[ k + 2 ] = z; 371 | 372 | // Hide dark matter (hide 85% of stars) 373 | if (k > 0.85 * (posArray.length / 4)){ 374 | posArray[ k + 3 ] = 1; 375 | } else { 376 | posArray[ k + 3 ] = 0; 377 | } 378 | 379 | 380 | velArray[ k + 0 ] = vx; 381 | velArray[ k + 1 ] = vy; 382 | velArray[ k + 2 ] = vz; 383 | velArray[ k + 3 ] = 0; 384 | } 385 | } 386 | 387 | /** 388 | * Init positions et volocities for all particles 389 | * @param texturePosition array that contain positions of particles 390 | * @param textureVelocity array that contain velocities of particles 391 | */ 392 | function fillUniverseTextures( texturePosition, textureVelocity ) { 393 | 394 | const posArray = texturePosition.image.data; 395 | const velArray = textureVelocity.image.data; 396 | 397 | // Set the radius of the sphere 398 | const radius = effectController.radius; 399 | 400 | // Set the pulse strength 401 | let pulseScale = 5; 402 | if (selectedChoice === 1){ 403 | pulseScale = 3.18; 404 | } 405 | 406 | for ( let k = 0, kl = posArray.length; k < kl; k += 4 ) { 407 | // Generate random point within a unit sphere 408 | let x, y, z; 409 | do { 410 | x = ( Math.random() * 2 - 1 ); 411 | y = ( Math.random() * 2 - 1 ); 412 | z = ( Math.random() * 2 - 1 ); 413 | } while ( x*x + y*y + z*z > 1 ); 414 | 415 | // Scale point to desired radius 416 | x *= radius; 417 | y *= radius; 418 | z *= radius; 419 | 420 | // Velocity 421 | const vx = pulseScale * x; 422 | const vy = pulseScale * y; 423 | const vz = pulseScale * z; 424 | 425 | // Fill in texture values 426 | posArray[ k + 0 ] = x; 427 | posArray[ k + 1 ] = y; 428 | posArray[ k + 2 ] = z; 429 | // Hide dark matter (hide 85% of stars) 430 | if (k > 0.85 * (posArray.length / 4)){ 431 | posArray[ k + 3 ] = 1; 432 | } else { 433 | posArray[ k + 3 ] = 0; 434 | } 435 | 436 | velArray[ k + 0 ] = vx; 437 | velArray[ k + 1 ] = vy; 438 | velArray[ k + 2 ] = vz; 439 | velArray[ k + 3 ] = 0; 440 | } 441 | } 442 | 443 | function fillGalaxiesCollisionTextures( texturePosition, textureVelocity ){ 444 | const posArray = texturePosition.image.data; 445 | const velArray = textureVelocity.image.data; 446 | 447 | const radius = effectController.radius; 448 | const height = effectController.height; 449 | const middleVelocity = effectController.middleVelocity; 450 | const maxVel = effectController.velocity; 451 | let indice = 0; 452 | for ( let k = 0, kl = posArray.length; k < kl; k += 4 ) { 453 | // Position 454 | let x, z, rr, y, vx, vy, vz; 455 | // If pair 456 | if (indice % 2 === 0){ 457 | // Generate random position for the particle within the radius 458 | do { 459 | x = ( Math.random() * 2 - 1 ); 460 | z = ( Math.random() * 2 - 1 ); 461 | // The variable rr is used to calculate the distance from the center of the radius for each particle. 462 | // It is used in the calculation of rExp which is used to determine the position of the particle within the radius. 463 | // If a particle is closer to the center, rr will be smaller, and rExp will be larger, which means that the particle will be placed closer to the center. 464 | // It also can affect the velocity of the particle as it is used in the calculation of the velocity of the particle. 465 | rr = x * x + z * z; 466 | 467 | } while ( rr > 1 ); 468 | rr = Math.sqrt( rr ); 469 | 470 | const rExp = radius * Math.pow( rr, middleVelocity ); 471 | 472 | // Velocity 473 | const vel = maxVel * Math.pow( rr, 0.2 ); 474 | 475 | vx = vel * z + ( Math.random() * 2 - 1 ) * 0.001; 476 | vy = ( Math.random() * 2 - 1 ) * 0.001 * 0.05; 477 | vz = - vel * x + ( Math.random() * 2 - 1 ) * 0.001; 478 | 479 | x *= rExp; 480 | z *= rExp; 481 | y = ( Math.random() * 2 - 1 ) * height; 482 | } 483 | // If impair 484 | else { 485 | // Generate random position for the particle within the radius 486 | do { 487 | x = ( Math.random() * 2 - 1 ); 488 | y = ( Math.random() * 2 - 1 ); 489 | // The variable rr is used to calculate the distance from the center of the radius for each particle. 490 | // It is used in the calculation of rExp which is used to determine the position of the particle within the radius. 491 | // If a particle is closer to the center, rr will be smaller, and rExp will be larger, which means that the particle will be placed closer to the center. 492 | // It also can affect the velocity of the particle as it is used in the calculation of the velocity of the particle. 493 | rr = x*x + y*y; 494 | 495 | } while ( rr > 1 ); 496 | rr = Math.sqrt( rr ); 497 | 498 | const rExp = radius * Math.pow( rr, middleVelocity ); 499 | 500 | // Velocity 501 | const vel = maxVel * Math.pow( rr, 0.2 ); 502 | 503 | vx = -vel * y + ( Math.random() * 2 - 1 ) * 0.001; 504 | vy = vel * x + ( Math.random() * 2 - 1 ) * 0.001; 505 | vz = -( Math.random() * 2 - 1 ) * 0.001 * 0.05; 506 | const angle = -Math.PI/4; 507 | 508 | const vy_temp = vy; 509 | const vz_temp = vz; 510 | vy = vy_temp * Math.cos(angle) - vz_temp * Math.sin(angle); 511 | vz = vy_temp * Math.sin(angle) + vz_temp * Math.cos(angle); 512 | 513 | x = x*rExp +200; 514 | y = y*rExp +200; 515 | z = ( Math.random() * 2 - 1 ) * height +10; 516 | const y_temp = y; 517 | const z_temp = z; 518 | y = y_temp * Math.cos(angle) - z_temp * Math.sin(angle); 519 | z = y_temp * Math.sin(angle) + z_temp * Math.cos(angle); 520 | } 521 | 522 | 523 | // Fill in texture values 524 | posArray[ k + 0 ] = x; 525 | posArray[ k + 1 ] = y; 526 | posArray[ k + 2 ] = z; 527 | // Hide dark matter (hide 85% of stars) 528 | if (k > 0.85 * (posArray.length / 4)){ 529 | posArray[ k + 3 ] = 1; 530 | } else { 531 | posArray[ k + 3 ] = 0; 532 | } 533 | 534 | velArray[ k + 0 ] = vx; 535 | velArray[ k + 1 ] = vy; 536 | velArray[ k + 2 ] = vz; 537 | velArray[ k + 3 ] = 0; 538 | indice++; 539 | } 540 | } 541 | 542 | /** 543 | * Restart the simulation 544 | */ 545 | function restartSimulation() { 546 | paused = false; 547 | scene.remove(particles); 548 | material.dispose(); 549 | geometry.dispose(); 550 | document.getElementsByClassName('dg ac').item(0).removeChild(document.getElementsByClassName('dg main a').item(0)); 551 | 552 | document.body.removeChild(document.querySelector('canvas').parentNode); 553 | 554 | PARTICLES = effectController.numberOfStars; 555 | 556 | init(effectController.typeOfSimulation.toString()); 557 | } 558 | 559 | function resetParameters(){ 560 | switchSimulation(); 561 | } 562 | 563 | /** 564 | * manage the resize of the windows to keep the scene centered 565 | */ 566 | function onWindowResize() { 567 | camera.aspect = window.innerWidth / window.innerHeight; 568 | camera.updateProjectionMatrix(); 569 | renderer.setSize( window.innerWidth, window.innerHeight ); 570 | particleUniforms[ 'cameraConstant' ].value = getCameraConstant( camera ); 571 | } 572 | 573 | function dynamicValuesChanger() { 574 | velocityUniforms[ 'gravity' ].value = effectController.gravity; 575 | velocityUniforms[ 'interactionRate' ].value = effectController.interactionRate; 576 | velocityUniforms[ 'timeStep' ].value = effectController.timeStep; 577 | console.log(effectController.maxAccelerationColor); 578 | velocityUniforms[ 'uMaxAccelerationColor' ].value = effectController.maxAccelerationColor; 579 | velocityUniforms[ 'blackHoleForce' ].value = effectController.blackHoleForce; 580 | velocityUniforms[ 'luminosity' ].value = effectController.luminosity; 581 | } 582 | 583 | /** 584 | * Init the menu 585 | */ 586 | function initGUI() { 587 | 588 | const gui = new GUI( { width: 350 } ); 589 | 590 | const folder1 = gui.addFolder( 'Dynamic Parameters' ); 591 | 592 | const folderGraphicSettings = gui.addFolder( 'Graphics settings' ); 593 | 594 | const folder2 = gui.addFolder( 'Static parameters (need to restart the simulation)' ); 595 | 596 | folder1.add( effectController, 'gravity', 0.0, 1000.0, 0.05 ).onChange( dynamicValuesChanger ).name("Gravitational force"); 597 | folder1.add( effectController, 'interactionRate', 0.0, 1.0, 0.001 ).onChange( dynamicValuesChanger ).name("Interaction rate (%)"); 598 | folder1.add( effectController, 'timeStep', 0.0, 0.01, 0.0001 ).onChange( dynamicValuesChanger ).name("Time step"); 599 | folder1.add( effectController, 'hideDarkMatter', 0, 1, 1 ).onChange( function ( value ) { 600 | effectController.hideDarkMatter = value ; 601 | } ).name("Hide dark matter"); 602 | folderGraphicSettings.add( bloom, 'strength', 0.0, 2.0, 0.1 ).onChange( function ( value ) { 603 | bloom.strength = value ; 604 | bloomPass.strength = bloom.strength; 605 | } ).name("Bloom"); 606 | folderGraphicSettings.add( effectController, 'motionBlur', 0, 1, 1 ).onChange( function ( value ) { 607 | effectController.motionBlur = value ; 608 | } ).name("Motion blur"); 609 | if (effectController.typeOfSimulation === 1 || effectController.typeOfSimulation === 3){ 610 | folder1.add( effectController, 'blackHoleForce', 0.0, 10000.0, 1.0 ).onChange( dynamicValuesChanger ).name("Black hole mass"); 611 | folderGraphicSettings.add( effectController, 'maxAccelerationColorPercent', 0.01, 100, 0.01 ).onChange( function ( value ) { 612 | effectController.maxAccelerationColor = value * 10; 613 | dynamicValuesChanger(); 614 | } ).name("Colors mix (%)"); 615 | folder2.add( effectController, 'numberOfStars', 2.0, 1000000.0, 1.0 ).name("Number of stars"); 616 | folder2.add( effectController, 'radius', 1.0, 1000.0, 1.0 ).name("Galaxy diameter"); 617 | folder2.add( effectController, 'height', 0.0, 50.0, 0.01 ).name("Galaxy height"); 618 | folder2.add( effectController, 'middleVelocity', 0.0, 20.0, 0.001 ).name("Center rotation speed"); 619 | folder2.add( effectController, 'velocity', 0.0, 150.0, 0.1 ).name("Initial rotation speed"); 620 | } else if (effectController.typeOfSimulation === 2){ 621 | folderGraphicSettings.add( effectController, 'luminosity', 0.0, 1.0, 0.0001 ).onChange( dynamicValuesChanger ).name("Luminosity"); 622 | folderGraphicSettings.add( effectController, 'maxAccelerationColorPercent', 0.01, 100, 0.01 ).onChange( function ( value ) { 623 | effectController.maxAccelerationColor = value / 10; 624 | dynamicValuesChanger(); 625 | } ).name("Colors mix (%)"); 626 | folder2.add( effectController, 'numberOfStars', 2.0, 10000000.0, 1.0 ).name("Number of galaxies"); 627 | folder2.add( effectController, 'radius', 1.0, 1000.0, 1.0 ).name("Initial diameter of the universe"); 628 | folder2.add( effectController, 'autoRotation').name('Auto-rotation').listen().onChange(function(){setChecked()}); 629 | } 630 | 631 | 632 | const buttonRestart = { 633 | restartSimulation: function () { 634 | restartSimulation(); 635 | } 636 | }; 637 | 638 | const buttonReset = { 639 | resetParameters: function () { 640 | resetParameters(); 641 | } 642 | }; 643 | const buttonPause = { 644 | pauseSimulation: function () { 645 | } 646 | }; 647 | 648 | 649 | function setChecked(){ 650 | autoRotation = !autoRotation; 651 | controls.autoRotate = autoRotation; 652 | } 653 | 654 | folder2.add( effectController, 'typeOfSimulation', typeOfSimulation ).onChange(switchSimulation).name("Type of simulation"); 655 | folder2.add( buttonRestart, 'restartSimulation' ).name("Restart the simulation"); 656 | folder2.add( buttonReset, 'resetParameters' ).name("Reset parameters"); 657 | let buttonPauseController = folder2.add( buttonPause, 'pauseSimulation' ).name("Pause"); 658 | buttonPauseController.onChange(function(){ 659 | paused = !paused; 660 | if(paused){ 661 | buttonPauseController.name("Resume"); 662 | }else{ 663 | buttonPauseController.name("Pause"); 664 | } 665 | buttonPauseController.updateDisplay(); 666 | }); 667 | 668 | folder1.open(); 669 | folder2.open(); 670 | folderGraphicSettings.open(); 671 | } 672 | 673 | function getCameraConstant( camera ) { 674 | return window.innerHeight / ( Math.tan( THREE.MathUtils.DEG2RAD * 0.5 * camera.fov ) / camera.zoom ); 675 | } 676 | 677 | /** 678 | * Switch the current simulation 679 | */ 680 | function switchSimulation(){ 681 | paused = false; 682 | // Normal mode (small configuration) 683 | if (selectedChoice === 1){ 684 | switch (effectController.typeOfSimulation.toString()) { 685 | // Single galaxy 686 | case "1": 687 | scene.remove(particles); 688 | bloom.strength = 1.0; 689 | effectController = { 690 | // Can be changed dynamically 691 | gravity: gravity, 692 | interactionRate: 0.5, 693 | timeStep: timeStep, 694 | blackHoleForce: blackHoleForce, 695 | luminosity: constLuminosity, 696 | maxAccelerationColor: 4.0, 697 | maxAccelerationColorPercent: 0.4, 698 | motionBlur: false, 699 | hideDarkMatter: false, 700 | 701 | // Must restart simulation 702 | numberOfStars: 10000, 703 | radius: 50, 704 | height: height, 705 | middleVelocity: middleVelocity, 706 | velocity: 7, 707 | typeOfSimulation: 1, 708 | autoRotation: false 709 | }; 710 | material.dispose(); 711 | geometry.dispose(); 712 | document.getElementsByClassName('dg ac').item(0).removeChild(document.getElementsByClassName('dg main a').item(0)); 713 | 714 | document.body.removeChild(document.querySelector('canvas').parentNode); 715 | 716 | PARTICLES = effectController.numberOfStars; 717 | 718 | init(effectController.typeOfSimulation.toString()); 719 | break; 720 | // Universe 721 | case "2": 722 | scene.remove(particles); 723 | bloom.strength = 0.7; 724 | effectController = { 725 | // Can be changed dynamically 726 | gravity: 225.0, 727 | interactionRate: 0.05, 728 | timeStep: 0.0001, 729 | blackHoleForce: 100.0, 730 | luminosity: 0.25, 731 | maxAccelerationColor: 2.0, 732 | maxAccelerationColorPercent: 20, 733 | motionBlur: false, 734 | hideDarkMatter: false, 735 | 736 | // Must restart simulation 737 | numberOfStars: 100000, 738 | radius: 2, 739 | height: 5, 740 | middleVelocity: 2, 741 | velocity: 15, 742 | typeOfSimulation: 2, 743 | autoRotation: true 744 | }; 745 | material.dispose(); 746 | geometry.dispose(); 747 | document.getElementsByClassName('dg ac').item(0).removeChild(document.getElementsByClassName('dg main a').item(0)); 748 | 749 | document.body.removeChild(document.querySelector('canvas').parentNode); 750 | 751 | PARTICLES = effectController.numberOfStars; 752 | 753 | init(effectController.typeOfSimulation.toString()); 754 | break; 755 | // Galaxies collision 756 | case "3": 757 | scene.remove(particles); 758 | bloom.strength = 1.0; 759 | effectController = { 760 | // Can be changed dynamically 761 | gravity: 40, 762 | interactionRate: 0.5, 763 | timeStep: timeStep, 764 | blackHoleForce: blackHoleForce, 765 | luminosity: constLuminosity, 766 | maxAccelerationColor: 15.0, 767 | maxAccelerationColorPercent: 1.5, 768 | motionBlur: false, 769 | hideDarkMatter: false, 770 | 771 | // Must restart simulation 772 | numberOfStars: 10000, 773 | radius: 50, 774 | height: height, 775 | middleVelocity: middleVelocity, 776 | velocity: 7, 777 | typeOfSimulation: 3, 778 | autoRotation: false 779 | }; 780 | material.dispose(); 781 | geometry.dispose(); 782 | document.getElementsByClassName('dg ac').item(0).removeChild(document.getElementsByClassName('dg main a').item(0)); 783 | 784 | document.body.removeChild(document.querySelector('canvas').parentNode); 785 | 786 | PARTICLES = effectController.numberOfStars; 787 | 788 | init(effectController.typeOfSimulation.toString()); 789 | break; 790 | default: 791 | break; 792 | } 793 | } else { 794 | switch (effectController.typeOfSimulation.toString()) { 795 | // Single galaxy 796 | case "1": 797 | scene.remove(particles); 798 | bloom.strength = 1.0; 799 | effectController = { 800 | // Can be changed dynamically 801 | gravity: gravity, 802 | interactionRate: interactionRate, 803 | timeStep: timeStep, 804 | blackHoleForce: blackHoleForce, 805 | luminosity: constLuminosity, 806 | maxAccelerationColor: 50.0, 807 | maxAccelerationColorPercent: 5.0, 808 | motionBlur: false, 809 | hideDarkMatter: false, 810 | 811 | // Must restart simulation 812 | numberOfStars: numberOfStars, 813 | radius: radius, 814 | height: height, 815 | middleVelocity: middleVelocity, 816 | velocity: velocity, 817 | typeOfSimulation: 1, 818 | autoRotation: false 819 | }; 820 | material.dispose(); 821 | geometry.dispose(); 822 | document.getElementsByClassName('dg ac').item(0).removeChild(document.getElementsByClassName('dg main a').item(0)); 823 | 824 | document.body.removeChild(document.querySelector('canvas').parentNode); 825 | 826 | PARTICLES = effectController.numberOfStars; 827 | 828 | init(effectController.typeOfSimulation.toString()); 829 | break; 830 | // Universe 831 | case "2": 832 | scene.remove(particles); 833 | bloom.strength = 0.7; 834 | effectController = { 835 | // Can be changed dynamically 836 | gravity: 20.0, 837 | interactionRate: 0.05, 838 | timeStep: 0.0001, 839 | blackHoleForce: 100.0, 840 | luminosity: 0.25, 841 | maxAccelerationColor: 2.0, 842 | maxAccelerationColorPercent: 20, 843 | motionBlur: false, 844 | hideDarkMatter: false, 845 | 846 | // Must restart simulation 847 | numberOfStars: 1000000, 848 | radius: 2, 849 | height: 5, 850 | middleVelocity: 2, 851 | velocity: 15, 852 | typeOfSimulation: 2, 853 | autoRotation: true 854 | }; 855 | material.dispose(); 856 | geometry.dispose(); 857 | document.getElementsByClassName('dg ac').item(0).removeChild(document.getElementsByClassName('dg main a').item(0)); 858 | 859 | document.body.removeChild(document.querySelector('canvas').parentNode); 860 | 861 | PARTICLES = effectController.numberOfStars; 862 | 863 | init(effectController.typeOfSimulation.toString()); 864 | break; 865 | // Galaxies collision 866 | case "3": 867 | scene.remove(particles); 868 | bloom.strength = 1.0; 869 | effectController = { 870 | // Can be changed dynamically 871 | gravity: gravity, 872 | interactionRate: interactionRate, 873 | timeStep: timeStep, 874 | blackHoleForce: blackHoleForce, 875 | luminosity: constLuminosity, 876 | maxAccelerationColor: 19.0, 877 | maxAccelerationColorPercent: 1.9, 878 | motionBlur: false, 879 | hideDarkMatter: false, 880 | 881 | // Must restart simulation 882 | numberOfStars: numberOfStars, 883 | radius: radius, 884 | height: height, 885 | middleVelocity: middleVelocity, 886 | velocity: 12, 887 | typeOfSimulation: 3, 888 | autoRotation: false 889 | }; 890 | material.dispose(); 891 | geometry.dispose(); 892 | document.getElementsByClassName('dg ac').item(0).removeChild(document.getElementsByClassName('dg main a').item(0)); 893 | 894 | document.body.removeChild(document.querySelector('canvas').parentNode); 895 | 896 | PARTICLES = effectController.numberOfStars; 897 | init(effectController.typeOfSimulation.toString()); 898 | 899 | break; 900 | default: 901 | break; 902 | } 903 | } 904 | 905 | } 906 | 907 | function animate() { 908 | controls.update(); 909 | requestAnimationFrame(animate); 910 | render(); 911 | stats.update(); 912 | } 913 | 914 | function render() { 915 | if (!paused){ 916 | gpuCompute.compute(); 917 | particleUniforms[ 'texturePosition' ].value = gpuCompute.getCurrentRenderTarget( positionVariable ).texture; 918 | particleUniforms[ 'textureVelocity' ].value = gpuCompute.getCurrentRenderTarget( velocityVariable ).texture; 919 | material.uniforms.uMaxAccelerationColor.value = effectController.maxAccelerationColor; 920 | } 921 | if (effectController.motionBlur){ 922 | composer.removePass(blendPass); 923 | composer.removePass(savePass); 924 | composer.removePass(outputPass); 925 | composer.addPass(blendPass); 926 | composer.addPass(savePass); 927 | composer.addPass(outputPass); 928 | } else { 929 | composer.removePass(blendPass); 930 | composer.removePass(savePass); 931 | composer.removePass(outputPass); 932 | } 933 | material.uniforms.uLuminosity.value = effectController.luminosity; 934 | material.uniforms.uHideDarkMatter.value = effectController.hideDarkMatter; 935 | composer.render(scene, camera); 936 | 937 | } --------------------------------------------------------------------------------