diff --git a/index.html b/index.html
index 91df6a5d5da016bf7eb2436bd4901398ad2bb171..6dd01cadf27e837e5d8ff34ad9fac8689460ce7c 100644
--- a/index.html
+++ b/index.html
@@ -2,7 +2,7 @@
 <html lang="en">
 <head>
     <meta charset="UTF-8">
-    <title>Ink Drop</title>
+    <title>Fluid Simulation</title>
 
     <link rel="stylesheet" type="text/css" href="dependencies/bootstrap.min.css">
     <link rel="stylesheet" type="text/css" href="dependencies/flat-ui.min.css">
@@ -416,11 +416,13 @@
                 This simulation solves the <a href="https://en.wikipedia.org/wiki/Navier%E2%80%93Stokes_equations" target="_blank">Navier-Stokes equations</a> for incompressible fluids in a GPU fragment shader.
                 I implemented <a href="https://en.wikipedia.org/wiki/No-slip_condition" target="_blank">no-slip boundary conditions</a> at the borders to keep the fluid contained within the bounds of the screen.
                 To increase performance, I solved for the velocity vector field of the fluid at a lower resolution than I used to compute the visualization of fluid flow; I used bilinear interpolation to smooth out artifacts caused by this speedup.
+                I've also added 160,000 <a href="https://en.wikipedia.org/wiki/Lagrangian_particle_tracking" target="_blank">Lagrangian particles</a> on top of the simulation -
+                these particles are rendered using <a href="https://threejs.org/" target="_blank">threejs</a>, but their positions are computed on the GPU.
                 <br/><br/>
                 <b>Instructions:</b> Click and drag to apply a force to the fluid.  Over time, the colored material in the fluid will dissipate.
                 <br/><br/>
                 To learn more about the math involved, check out the following sources:<br/>
-                <a href="https://pdfs.semanticscholar.org/84b8/c7b7eecf90ebd9d54a51544ca0f8ff93c137.pdf" target="_blank">Real-time ink simulation using a grid-particle method</a>
+                <a href="https://pdfs.semanticscholar.org/84b8/c7b7eecf90ebd9d54a51544ca0f8ff93c137.pdf" target="_blank">Real-time ink simulation using a grid-particle method</a> - mixing Eulerian and Lagrangian techniques for fluids<br/>
                 <a href="http://http.developer.nvidia.com/GPUGems/gpugems_ch38.html" target="_blank">Fast Fluid Dynamics Simulation on the GPU</a> - a very well written tutorial about programming the Navier-Stokes equations on a GPU.
                 Though not WebGL specific, it was still very useful.<br/>
                 <a href="http://jamie-wong.com/2016/08/05/webgl-fluid-simulation/" target="_blank">Fluid Simulation (with WebGL demo)</a> - this article has some nice, interactive graphics that helped me debug my code.<br/>
diff --git a/js/main.js b/js/main.js
index cffeccc5faecb0247e758841980dbc05dcca73d4..cabcb208290613669bb1c331c7d3c477630bbf25 100755
--- a/js/main.js
+++ b/js/main.js
@@ -21,8 +21,8 @@ var GPU;
 
 var threeView;
 
-var numParticles = 40000;//perfect sq
-var particlesTextureDim = 200;//sqrt(numParticles)
+var numParticles = 160000;//perfect sq
+var particlesTextureDim = 400;//sqrt(numParticles)
 var particleData = new Float32Array(numParticles*4);//[position.x, position.y, velocity.x, velocity.y]
 var particles;
 var particlesVertices;
@@ -95,33 +95,39 @@ function init() {
     GPU.setUniformForProgram("moveParticles", "u_textureSize", [particlesTextureDim, particlesTextureDim], "2f");
     GPU.setUniformForProgram("moveParticles", "u_dt", 0.5, "1f");
 
-    resetWindow();
-
     threeView = initThreeView();
 
     var geo = new THREE.Geometry();
     geo.dynamic = true;
     particlesVertices = geo.vertices;
+    for (var i=0;i<numParticles;i++){
+        geo.vertices.push(new THREE.Vector3());
+    }
+    particles = new THREE.Points(geo, new THREE.PointsMaterial({size:0.04, opacity: 0.5, transparent: false, depthTest : false, color:0x000033}));
+    threeView.scene.add(particles);
+
+    GPU.initTextureFromData("outputParticleBytes", particlesTextureDim*vectorLength, particlesTextureDim, "UNSIGNED_BYTE", null);//2 comp vector [x,y]
+    GPU.initFrameBufferForTexture("outputParticleBytes", true);
+
+    resetWindow();
+
+    render();
+}
+
+function setThree(){
     for (var i=0;i<numParticles;i++){
         var vertex = new THREE.Vector3(Math.random()*actualWidth, Math.random()*actualHeight, 0);
         particleData[i*4] = vertex.x;
         particleData[i*4+1] = vertex.y;
-        geo.vertices.push(vertex);
+        particles.geometry.vertices[i].set(vertex.x, vertex.y, 0);
     }
-    particles = new THREE.Points(geo, new THREE.PointsMaterial({size:0.03, transparent: false, depthTest : false, color:0xff00ff}));
     particles.position.set(-actualWidth/2, -actualHeight/2, 0);
-    threeView.scene.add(particles);
     threeView.render();
 
     GPU.initTextureFromData("particles", particlesTextureDim, particlesTextureDim, "FLOAT", particleData, true);
     GPU.initFrameBufferForTexture("particles", true);
     GPU.initTextureFromData("nextParticles", particlesTextureDim, particlesTextureDim, "FLOAT", particleData, true);
     GPU.initFrameBufferForTexture("nextParticles", true);
-
-    GPU.initTextureFromData("outputParticleBytes", particlesTextureDim*vectorLength, particlesTextureDim, "UNSIGNED_BYTE", null);//2 comp vector [x,y]
-    GPU.initFrameBufferForTexture("outputParticleBytes", true);
-
-    render();
 }
 
 function render(){
@@ -185,26 +191,26 @@ function render(){
         GPU.setUniformForProgram("boundary", "u_scale", -1, "1f");
         GPU.step("boundary", ["nextVelocity"], "velocity");
 
-        // // move material
-        // GPU.setSize(actualWidth, actualHeight);
-        //
-        // //add material
-        // GPU.setProgram("addMaterial");
-        // if (!mouseout && mouseEnable){
-        //     GPU.setUniformForProgram("addMaterial", "u_mouseEnable", 1.0, "1f");
-        //     GPU.setUniformForProgram("addMaterial", "u_mouseCoord", mouseCoordinates, "2f");
-        //     GPU.setUniformForProgram("addMaterial", "u_mouseLength", Math.sqrt(Math.pow(3*(mouseCoordinates[0]-lastMouseCoordinates[0]),2)
-        //         +Math.pow(3*(mouseCoordinates[1]-lastMouseCoordinates[1]),2)), "1f");
-        // } else {
-        //     GPU.setUniformForProgram("addMaterial", "u_mouseEnable", 0.0, "1f");
-        // }
-        // GPU.step("addMaterial", ["material"], "nextMaterial");
-        //
-        // GPU.setProgram("advect");
-        // GPU.setUniformForProgram("advect" ,"u_textureSize", [actualWidth, actualHeight], "2f");
-        // GPU.setUniformForProgram("advect" ,"u_scale", scale, "1f");
-        // GPU.step("advect", ["velocity", "nextMaterial"], "material");
-        // GPU.step("render", ["material"]);
+        // move material
+        GPU.setSize(actualWidth, actualHeight);
+
+        //add material
+        GPU.setProgram("addMaterial");
+        if (!mouseout && mouseEnable){
+            GPU.setUniformForProgram("addMaterial", "u_mouseEnable", 1.0, "1f");
+            GPU.setUniformForProgram("addMaterial", "u_mouseCoord", mouseCoordinates, "2f");
+            GPU.setUniformForProgram("addMaterial", "u_mouseLength", Math.sqrt(Math.pow(3*(mouseCoordinates[0]-lastMouseCoordinates[0]),2)
+                +Math.pow(3*(mouseCoordinates[1]-lastMouseCoordinates[1]),2)), "1f");
+        } else {
+            GPU.setUniformForProgram("addMaterial", "u_mouseEnable", 0.0, "1f");
+        }
+        GPU.step("addMaterial", ["material"], "nextMaterial");
+
+        GPU.setProgram("advect");
+        GPU.setUniformForProgram("advect" ,"u_textureSize", [actualWidth, actualHeight], "2f");
+        GPU.setUniformForProgram("advect" ,"u_scale", scale, "1f");
+        GPU.step("advect", ["velocity", "nextMaterial"], "material");
+        GPU.step("render", ["material"]);
 
     } else resetWindow();
 
@@ -311,6 +317,8 @@ function resetWindow(){
     GPU.initTextureFromData("nextMaterial", actualWidth, actualHeight, "FLOAT", material, true);
     GPU.initFrameBufferForTexture("nextMaterial", true);
 
+    setThree();
+
     paused = false;
 }