diff --git a/index.html b/index.html
index de165071e42bbfc45e553a258c669e2d8500145b..21764dd2b5759400a2660cf69e05684dc7333e17 100644
--- a/index.html
+++ b/index.html
@@ -293,6 +293,103 @@
         }
     </script>
 
+    <script id="moveParticlesShader" type="x-shader/x-fragment">
+        precision mediump float;
+
+        uniform sampler2D u_velocity;
+        uniform sampler2D u_particles;
+
+        uniform vec2 u_textureSize;
+        uniform vec2 u_velocityTextureSize;
+
+        uniform float u_scale;
+
+        vec2 bilinearInterp(vec2 pos, sampler2D texture, vec2 size){
+            //bilinear interp between nearest cells
+
+            vec2 pxCenter = vec2(0.5, 0.5);
+
+            vec2 ceiled = ceil(pos);
+            vec2 floored = floor(pos);
+
+            vec2 n = texture2D(texture, (ceiled+pxCenter)/size).xy;//actually ne
+            vec2 s = texture2D(texture, (floored+pxCenter)/size).xy;//actually sw
+            if (ceiled.x != floored.x){
+                vec2 se = texture2D(texture, (vec2(ceiled.x, floored.y)+pxCenter)/size).xy;
+                vec2 nw = texture2D(texture, (vec2(floored.x, ceiled.y)+pxCenter)/size).xy;
+                n = n*(pos.x-floored.x) + nw*(ceiled.x-pos.x);
+                s = se*(pos.x-floored.x) + s*(ceiled.x-pos.x);
+            }
+            vec2 materialVal = n;
+            if (ceiled.y != floored.y){
+                materialVal = n*(pos.y-floored.y) + s*(ceiled.y-pos.y);
+            }
+            return materialVal;
+        }
+
+        void main() {
+
+            vec2 fragCoord = gl_FragCoord.xy;
+            vec2 particleCoord = texture2D(u_particles, fragCoord/u_textureSize).xy;
+
+            vec2 currentVelocity = 1.0/u_scale*bilinearInterp(vec2(1.0, 1.0) + particleCoord/u_velocityTextureSize*(u_velocityTextureSize-vec2(3.0, 3.0)), u_velocity, u_velocityTextureSize);
+
+            //explicitly solve advection
+            gl_FragColor = vec4(particlePos+currentVelocity*0.5, 0, 0);
+        }
+    </script>
+
+    <script id="packToBytesShader" type="x-shader/x-fragment">
+        precision mediump float;
+
+        uniform vec2 u_floatTextureDim;
+        uniform sampler2D u_floatTexture;
+        uniform float u_vectorLength;
+
+
+        float shift_right (float v, float amt) {
+            v = floor(v) + 0.5;
+            return floor(v / exp2(amt));
+        }
+        float shift_left (float v, float amt) {
+            return floor(v * exp2(amt) + 0.5);
+        }
+        float mask_last (float v, float bits) {
+            return mod(v, shift_left(1.0, bits));
+        }
+        float extract_bits (float num, float from, float to) {
+            from = floor(from + 0.5); to = floor(to + 0.5);
+            return mask_last(shift_right(num, from), to - from);
+        }
+        vec4 encode_float (float val) {
+            if (val == 0.0) return vec4(0, 0, 0, 0);
+            float sign = val > 0.0 ? 0.0 : 1.0;
+            val = abs(val);
+            float exponent = floor(log2(val));
+            float biased_exponent = exponent + 127.0;
+            float fraction = ((val / exp2(exponent)) - 1.0) * 8388608.0;
+            float t = biased_exponent / 2.0;
+            float last_bit_of_biased_exponent = fract(t) * 2.0;
+            float remaining_bits_of_biased_exponent = floor(t);
+            float byte4 = extract_bits(fraction, 0.0, 8.0) / 255.0;
+            float byte3 = extract_bits(fraction, 8.0, 16.0) / 255.0;
+            float byte2 = (last_bit_of_biased_exponent * 128.0 + extract_bits(fraction, 16.0, 23.0)) / 255.0;
+            float byte1 = (sign * 128.0 + remaining_bits_of_biased_exponent) / 255.0;
+            return vec4(byte4, byte3, byte2, byte1);
+        }
+
+        void main(){
+            vec2 fragCoord = gl_FragCoord.xy;
+            float textureXcoord = floor((fragCoord.x - 0.5)/u_vectorLength+0.0001) + 0.5;
+            vec4 data = texture2D(u_floatTexture, vec2(textureXcoord, fragCoord.y)/u_floatTextureDim);
+            int textureIndex = int(floor(mod(fragCoord.x-0.5+0.0001, u_vectorLength)));
+            if (textureIndex == 0) gl_FragColor = encode_float(data[0]);
+            else if (textureIndex == 1) gl_FragColor = encode_float(data[1]);
+            else if (textureIndex == 2) gl_FragColor = encode_float(data[2]);
+            else if (textureIndex == 3) gl_FragColor = encode_float(data[3]);
+        }
+    </script>
+
     <script type="text/javascript" src="dependencies/jquery-3.1.0.min.js"></script>
     <script type="text/javascript" src="dependencies/flat-ui.min.js"></script>
     <script type="text/javascript" src="dependencies/three.js"></script>
diff --git a/js/main.js b/js/main.js
index 0c81fc249e7a03a1959f42b2d03f27efb11f267d..fc5b5d3d53c33ea3c102c65dba8c88b945e46e9c 100755
--- a/js/main.js
+++ b/js/main.js
@@ -21,7 +21,8 @@ var GPU;
 
 var threeView;
 
-var numParticles = 100;
+var numParticles = 100;//perfect sq
+var particlesTextureDim = 10;//sqrt(numParticles)
 var particleData = new Float32Array(numParticles*4);//[position.x, position.y, velocity.x, velocity.y]
 var particles;
 
@@ -49,18 +50,6 @@ function init() {
 
     window.onresize = onResize;
 
-    threeView = initThreeView();
-
-    var geo = new THREE.Geometry();
-    geo.dynamic = true;
-    for (var i=0;i<numParticles;i++){
-        geo.vertices.push(new THREE.Vector3(Math.random()*200, Math.random()*200, 0));
-    }
-    particles = new THREE.Points(geo, new THREE.PointsMaterial({size:0.1, transparent: false, depthTest : false, color:0xff00ff}));
-    threeView.scene.add(particles);
-    threeView.render();
-
-
     GPU = initGPUMath();
 
     // setup a GLSL programs
@@ -95,8 +84,33 @@ function init() {
     GPU.createProgram("boundary", "2d-vertex-shader", "boundaryShader");
     GPU.setUniformForProgram("boundary", "u_texture", 0, "1i");
 
+    GPU.createProgram("packToBytes", "2d-vertex-shader", "packToBytesShader");
+
     resetWindow();
 
+    threeView = initThreeView();
+
+    var geo = new THREE.Geometry();
+    geo.dynamic = true;
+    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 = new THREE.Points(geo, new THREE.PointsMaterial({size:0.1, 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*2, particlesTextureDim, "UNSIGNED_BYTE", null);//2 comp vector [x,y]
+    GPU.initFrameBufferForTexture("outputParticleBytes");
+
     render();
 }
 
@@ -184,6 +198,19 @@ function render(){
 
     } else resetWindow();
 
+    //move particles
+    var vectorLength = 2;
+    GPU.setProgram("packToBytes");
+    GPU.setUniformForProgram("packToBytes", "u_vectorLength", vectorLength, "1f");
+    GPU.setSize(width, height);
+    GPU.step("packToBytes", ["particles"], "outputParticleBytes");
+    var pixels = new Uint8Array(width*height*4*vectorLength);
+    if (GPU.readyToRead()) {
+        GPU.readPixels(0, 0, width * vectorLength, height, pixels);
+        var parsedPixels = new Float32Array(pixels.buffer);
+        // console.log(parsedPixels);
+    }
+
     window.requestAnimationFrame(render);
 }
 
diff --git a/js/threeView.js b/js/threeView.js
index a4e9f07dd2de65ba9daa0297e25b91c2ed77f245..11ac05433442389b6b2934e04388820359bf5ed8 100644
--- a/js/threeView.js
+++ b/js/threeView.js
@@ -39,7 +39,7 @@ function initThreeView() {
         //renderer.setClearColor(scene.fog.color);
 
         camera.up = new THREE.Vector3(0,0,1);
-        camera.zoom = 10;
+        camera.zoom = 1;
         camera.updateProjectionMatrix();
         camera.position.z = 10;