/*
 This example demonstrates how to use a two dimensional slice of a three
 dimensional noise field to guide particles that are flying around. It was
 originally based on the idea of simulating "pollen" being blown around by
 the wind, and implemented in the Processing:
 http://www.openprocessing.org/visuals/?visualID=2785
 */

#include "ofApp.h"

/*
 All these settings control the behavior of the app. In general it's a better
 idea to keep variables in the .h file, but this makes it easy to set them at
 the same time you declare them.
 */
int nPoints = 4096; // points to draw
float complexity = 6; // wind complexity
float pollenMass = .8; // pollen mass
float timeSpeed = .02; // wind variation speed
float phase = TWO_PI; // separate u-noise from v-noise
float windSpeed = 40; // wind vector magnitude for debug
int step = 10; // spatial sampling rate for debug
bool debugMode = false;

/*
 This is the magic method that samples a 2d slice of the 3d noise field. When
 you call this method with a position, it returns a direction (a 2d vector). The
 trick behind this method is that the u,v values for the field are taken from
 out-of-phase slices in the first dimension: t + phase for the u, and t - phase
 for the v.
 */
//--------------------------------------------------------------
glm::vec2 ofApp::getField(const glm::vec2& position) {
	float normx = ofNormalize(position.x, 0, ofGetWidth());
	float normy = ofNormalize(position.y, 0, ofGetHeight());
	float u = ofNoise(t + phase, normx * complexity + phase, normy * complexity + phase);
	float v = ofNoise(t - phase, normx * complexity - phase, normy * complexity + phase);
	return glm::vec2(u, v);
}

//--------------------------------------------------------------
void ofApp::setup() {
	ofSetVerticalSync(true); // don't go too fast
	ofEnableAlphaBlending();
	
	// randomly allocate the points across the screen
  points.resize(nPoints);
  for(int i = 0; i < nPoints; i++) {
    points[i] = glm::vec2(ofRandom(0, ofGetWidth()), ofRandom(0, ofGetHeight()));
  }
	
	// we'll be drawing the points into an ofMesh that is drawn as bunch of points
	cloud.clear();
	cloud.setMode(OF_PRIMITIVE_POINTS);
}

//--------------------------------------------------------------
void ofApp::update() {
	width = ofGetWidth(), height = ofGetHeight();
  t = ofGetFrameNum() * timeSpeed;
  for(int i = 0; i < nPoints; i++) {
		float x = points[i].x, y = points[i].y;
		glm::vec2 field = getField(points[i]); // get the field at this position
		// use the strength of the field to determine a speed to move
		// the speed is changing over time and velocity-space as well
    float speed = (1 + ofNoise(t, field.x, field.y)) / pollenMass;
		// add the velocity of the particle to its position 
    x += ofLerp(-speed, speed, field.x);
    y += ofLerp(-speed, speed, field.y);
		// if we've moved outside of the screen, reinitialize randomly
    if(x < 0 || x > width || y < 0 || y > height) {
      x = ofRandom(0, width);
      y = ofRandom(0, height);
    }
		// save the changes we made to the position
    points[i].x = x;
    points[i].y = y;
		// add the current point to our collection of drawn points
      cloud.addVertex({x, y,0});
	}
} 

//--------------------------------------------------------------
void ofApp::draw() {
	ofBackground(255);
  if(debugMode) {
    ofSetColor(0);
		// draw a vector field for the debug screen
    for(int i = 0; i < width; i += step) {
      for(int j = 0; j < height; j += step) {
				glm::vec2 field = getField(glm::vec2(i, j));
        ofPushMatrix();
        ofTranslate(i, j);
				ofSetColor(0);
        ofDrawLine(0, 0, ofLerp(-windSpeed, windSpeed, field.x), ofLerp(-windSpeed, windSpeed, field.y));
        ofPopMatrix();
      }
    }
		// draw the points as circles
		ofSetColor(ofColor::red);
		for(int i = 0; i < nPoints; i++) {
		ofDrawCircle(points[i], 2);
		}
  } else {
		// when not in debug mode, draw all the points to the screen
    ofSetColor(0, 10);
		cloud.draw();
	}
	
	ofDrawBitmapStringHighlight("click to reset\nhit any key for debug", 10, 10, ofColor::white, ofColor::black);
}

//--------------------------------------------------------------
void ofApp::keyPressed(int key) {
	// when you hit a key, draw the debug screen
  debugMode = !debugMode;
}

//--------------------------------------------------------------
void ofApp::keyReleased(int key) {

}

//--------------------------------------------------------------
void ofApp::mouseMoved(int x, int y) {

}

//--------------------------------------------------------------
void ofApp::mouseDragged(int x, int y, int button) {

}

//--------------------------------------------------------------
void ofApp::mousePressed(int x, int y, int button) {
	// when you click the mouse, reset all the points
  setup();
}

//--------------------------------------------------------------
void ofApp::mouseReleased(int x, int y, int button) {

}

//--------------------------------------------------------------
void ofApp::mouseEntered(int x, int y){

}

//--------------------------------------------------------------
void ofApp::mouseExited(int x, int y){

}

//--------------------------------------------------------------
void ofApp::windowResized(int w, int h) {

}

//--------------------------------------------------------------
void ofApp::gotMessage(ofMessage msg){

}

//--------------------------------------------------------------
void ofApp::dragEvent(ofDragInfo dragInfo){ 

}
