/***********************************************************************
* mt4j Copyright (c) 2008 - 2009 C.Ruff, Fraunhofer-Gesellschaft All rights reserved.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
***********************************************************************/
package advanced.fluidSimulator;
import java.awt.event.KeyEvent;
import java.nio.FloatBuffer;
import javax.media.opengl.GL;
import msafluid.MSAFluidSolver2D;
import org.mt4j.MTApplication;
import org.mt4j.components.MTComponent;
import org.mt4j.input.IMTInputEventListener;
import org.mt4j.input.inputData.AbstractCursorInputEvt;
import org.mt4j.input.inputData.InputCursor;
import org.mt4j.input.inputData.MTInputEvent;
import org.mt4j.sceneManagement.AbstractScene;
import org.mt4j.util.MT4jSettings;
import org.mt4j.util.math.Vector3D;
import processing.core.PApplet;
import processing.core.PGraphics;
import processing.core.PImage;
import processing.opengl.PGraphicsOpenGL;
import com.sun.opengl.util.BufferUtil;
/**
* The Class FluidSimulationScene.
*
* The original fluid simulation code was taken from
* memo akten (www.memo.tv)
*
*/
public class FluidSimulationScene extends AbstractScene{
private final float FLUID_WIDTH = 120;
private float invWidth, invHeight; // inverse of screen dimensions
private float aspectRatio, aspectRatio2;
private MSAFluidSolver2D fluidSolver;
private PImage imgFluid;
private boolean drawFluid = true;
private ParticleSystem particleSystem;
/////////
private MTApplication app;
public FluidSimulationScene(MTApplication mtApplication, String name) {
super(mtApplication, name);
this.app = mtApplication;
if (!MT4jSettings.getInstance().isOpenGlMode()){
System.err.println("Scene only usable when using the OpenGL renderer! - See settings.txt");
return;
}
//pa.hint( PApplet.ENABLE_OPENGL_4X_SMOOTH ); // Turn on 4X antialiasing
invWidth = 1.0f/mtApplication.width;
invHeight = 1.0f/mtApplication.height;
aspectRatio = mtApplication.width * invHeight;
aspectRatio2 = aspectRatio * aspectRatio;
// Create fluid and set options
fluidSolver = new MSAFluidSolver2D((int)(FLUID_WIDTH), (int)(FLUID_WIDTH * mtApplication.height/mtApplication.width));
// fluidSolver.enableRGB(true).setFadeSpeed(0.003f).setDeltaT(0.5f).setVisc(0.00005f);
fluidSolver.enableRGB(true).setFadeSpeed(0.003f).setDeltaT(0.8f).setVisc(0.00004f);
// Create image to hold fluid picture
imgFluid = mtApplication.createImage(fluidSolver.getWidth(), fluidSolver.getHeight(), PApplet.RGB);
// Create particle system
particleSystem = new ParticleSystem(mtApplication, fluidSolver);
this.getCanvas().addInputListener(new IMTInputEventListener() {
//@Override
public boolean processInputEvent(MTInputEvent inEvt){
if(inEvt instanceof AbstractCursorInputEvt){
AbstractCursorInputEvt posEvt = (AbstractCursorInputEvt)inEvt;
if (posEvt.hasTarget() && posEvt.getTargetComponent().equals(getCanvas())){
InputCursor m = posEvt.getCursor();
AbstractCursorInputEvt prev = m.getPreviousEventOf(posEvt);
if (prev == null)
prev = posEvt;
Vector3D pos = new Vector3D(posEvt.getPosX(), posEvt.getPosY(), 0);
Vector3D prevPos = new Vector3D(prev.getPosX(), prev.getPosY(), 0);
//System.out.println("Pos: " + pos);
float mouseNormX = pos.x * invWidth;
float mouseNormY = pos.y * invHeight;
//System.out.println("MouseNormPosX: " + mouseNormX + "," + mouseNormY);
float mouseVelX = (pos.x - prevPos.x) * invWidth;
float mouseVelY = (pos.y - prevPos.y) * invHeight;
/*
System.out.println("Mouse vel X: " + mouseVelX + " mouseNormX:" + mouseNormX);
System.out.println("Mouse vel Y: " + mouseVelY + " mouseNormY:" + mouseNormY);
*/
addForce(mouseNormX, mouseNormY, mouseVelX, mouseVelY);
}
}
return false;
}
});
//FIXME make componentInputProcessor?
this.getCanvas().addChild(new FluidImage(mtApplication));
this.getCanvas().setDepthBufferDisabled(true);
}
/**
* The Class FluidImage.
*/
private class FluidImage extends MTComponent{
public FluidImage(PApplet applet) {
super(applet);
}
//@Override
public void drawComponent(PGraphics g) {
super.drawComponent(g);
drawFluidImage();
g.noSmooth();
g.fill(255,255,255,255);
g.tint(255,255,255,255);
//FIXME TEST
PGraphicsOpenGL pgl = (PGraphicsOpenGL)g;
GL gl = pgl.gl;
gl.glDisableClientState(GL.GL_VERTEX_ARRAY);
gl.glDisableClientState(GL.GL_COLOR_ARRAY);
gl.glDisable(GL.GL_LINE_SMOOTH);
gl.glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
}
}
//@Override
public void drawAndUpdate(PGraphics graphics, long timeDelta) {
// this.drawFluidImage();
super.drawAndUpdate(graphics, timeDelta);
// app.noSmooth();
// app.fill(255,255,255,255);
// app.tint(255,255,255,255);
//
// //FIXME TEST
// PGraphicsOpenGL pgl = (PGraphicsOpenGL)app.g;
// GL gl = pgl.gl;
// gl.glDisableClientState(GL.GL_VERTEX_ARRAY);
// gl.glDisableClientState(GL.GL_COLOR_ARRAY);
// gl.glDisable(GL.GL_LINE_SMOOTH);
// gl.glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
// mtApp.colorMode(PApplet.RGB, 255);
}
// add force and dye to fluid, and create particles
private void addForce(float x, float y, float dx, float dy) {
float speed = dx * dx + dy * dy * aspectRatio2; // balance the x and y components of speed with the screen aspect ratio
if(speed > 0) {
if(x < 0){
x = 0;
}else if(x > 1){
x = 1;
}if(y < 0){
y = 0;
}else if(y > 1){
y = 1;
}
float colorMult = 5;
float velocityMult = 30.0f;
int index = fluidSolver.getIndexForNormalizedPosition(x, y);
// PApplet.color drawColor;
app.colorMode(PApplet.HSB, 360, 1, 1);
float hue = ((x + y) * 180 + app.frameCount) % 360;
int drawColor = app.color(hue, 1, 1);
app.colorMode(PApplet.RGB, 1);
fluidSolver.rOld[index] += app.red(drawColor) * colorMult;
fluidSolver.gOld[index] += app.green(drawColor) * colorMult;
fluidSolver.bOld[index] += app.blue(drawColor) * colorMult;
//Particles
particleSystem.addParticles(x * app.width, y * app.height, 10);
fluidSolver.uOld[index] += dx * velocityMult;
fluidSolver.vOld[index] += dy * velocityMult;
// mtApp.noSmooth();
// mtApp.fill(255,255,255,255);
// mtApp.tint(255,255,255,255);
//FIXME TEST
app.colorMode(PApplet.RGB, 255);
}
}
private void drawFluidImage(){
app.colorMode(PApplet.RGB, 1);
fluidSolver.update();
if(drawFluid) {
for(int i=0; i<fluidSolver.getNumCells(); i++) {
int d = 2;
imgFluid.pixels[i] = app.color(fluidSolver.r[i] * d, fluidSolver.g[i] * d, fluidSolver.b[i] * d);
}
imgFluid.updatePixels();// fastblur(imgFluid, 2);
// app.image(imgFluid, 0, 0, app.width, app.height); //FIXME this messes up blend transition!
app.textureMode(app.NORMALIZED);
// app.textureMode(app.IMAGE);
app.beginShape(app.QUADS);
app.texture(imgFluid);
app.vertex(0, 0, 0, 0);
app.vertex(app.width, 0, 1, 0);
app.vertex(app.width, app.height, 1, 1);
app.vertex(0, app.height, 0, 1);
app.endShape();
}
particleSystem.updateAndDraw();
app.colorMode(PApplet.RGB, 255);
}
//@Override
public void init() {
app.registerKeyEvent(this);
}
//@Override
public void shutDown() {
app.unregisterKeyEvent(this);
/*
mtApp.noSmooth();
mtApp.fill(255,255,255,255);
mtApp.tint(255,255,255,255);
PGraphicsOpenGL pgl = (PGraphicsOpenGL)mtApp.g;
GL gl = pgl.gl;
gl.glDisableClientState(GL.GL_VERTEX_ARRAY);
gl.glDisableClientState(GL.GL_COLOR_ARRAY);
gl.glDisable(GL.GL_LINE_SMOOTH);
gl.glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
mtApp.colorMode(PApplet.RGB, 255);
*/
}
/**
*
* @param e
*/
public void keyEvent(KeyEvent e){
int evtID = e.getID();
if (evtID != KeyEvent.KEY_PRESSED)
return;
switch (e.getKeyCode()){
case KeyEvent.VK_BACK_SPACE:
app.popScene();
break;
default:
break;
}
}
private class Particle {
private final static float MOMENTUM = 0.5f;
private final static float FLUID_FORCE = 0.6f;
private float x, y;
private float vx, vy;
//private float radius; // particle's size
protected float alpha;
private float mass;
private PApplet p;
private float invWidth;
private float invHeight;
private MSAFluidSolver2D fluidSolver;
public Particle(PApplet p, MSAFluidSolver2D fluidSolver, float invWidth, float invHeight){
this.p = p;
this.invWidth = invWidth;
this.invHeight = invHeight;
this.fluidSolver = fluidSolver;
}
public void init(float x, float y) {
this.x = x;
this.y = y;
vx = 0;
vy = 0;
//radius = 5;
alpha = p.random(0.3f, 1);
mass = p.random(0.1f, 1);
}
public void update() {
// only update if particle is visible
if(alpha == 0) return;
// read fluid info and add to velocity
int fluidIndex = fluidSolver.getIndexForNormalizedPosition(x * invWidth, y * invHeight);
vx = fluidSolver.u[fluidIndex] * p.width * mass * FLUID_FORCE + vx * MOMENTUM;
vy = fluidSolver.v[fluidIndex] * p.height * mass * FLUID_FORCE + vy * MOMENTUM;
// update position
x += vx;
y += vy;
// bounce of edges
if(x<0) {
x = 0;
vx *= -1;
}else if(x > p.width) {
x = p.width;
vx *= -1;
}
if(y<0) {
y = 0;
vy *= -1;
}else if(y > p.height) {
y = p.height;
vy *= -1;
}
// hackish way to make particles glitter when the slow down a lot
if(vx * vx + vy * vy < 1) {
vx = p.random(-1, 1);
vy = p.random(-1, 1);
}
// fade out a bit (and kill if alpha == 0);
alpha *= 0.999;
if(alpha < 0.01)
alpha = 0;
}
public void updateVertexArrays(int i, FloatBuffer posBuffer, FloatBuffer colBuffer) {
int vi = i * 4;
posBuffer.put(vi++, x - vx);
posBuffer.put(vi++, y - vy);
posBuffer.put(vi++, x);
posBuffer.put(vi++, y);
int ci = i * 6;
colBuffer.put(ci++, alpha);
colBuffer.put(ci++, alpha);
colBuffer.put(ci++, alpha);
colBuffer.put(ci++, alpha);
colBuffer.put(ci++, alpha);
colBuffer.put(ci++, alpha);
}
public void drawOldSchool(GL gl) {
gl.glColor3f(alpha, alpha, alpha);
gl.glVertex2f(x-vx, y-vy);
gl.glVertex2f(x, y);
}
}//end particle class
public class ParticleSystem{
private FloatBuffer posArray;
private FloatBuffer colArray;
private final static int maxParticles = 5000;
private int curIndex;
boolean renderUsingVA = true;
private Particle[] particles;
private PApplet p;
private MSAFluidSolver2D fluidSolver;
private float invWidth;
private float invHeight;
private boolean drawFluid;
public ParticleSystem(PApplet p, MSAFluidSolver2D fluidSolver) {
this.p = p;
this.fluidSolver = fluidSolver;
this.invWidth = 1.0f/p.width;
this.invHeight = 1.0f/p.height;
this.drawFluid = true;
particles = new Particle[maxParticles];
for(int i=0; i<maxParticles; i++) {
particles[i] = new Particle(p, this.fluidSolver, invWidth, invHeight);
}
curIndex = 0;
posArray = BufferUtil.newFloatBuffer(maxParticles * 2 * 2);// 2 coordinates per point, 2 points per particle (current and previous)
colArray = BufferUtil.newFloatBuffer(maxParticles * 3 * 2);
}
public void updateAndDraw(){
PGraphicsOpenGL pgl = (PGraphicsOpenGL)p.g; // processings opengl graphics object
GL gl = pgl.beginGL(); // JOGL's GL object
gl.glEnable( GL.GL_BLEND ); // enable blending
if(!drawFluid)
fadeToColor(p, gl, 0, 0, 0, 0.05f);
gl.glBlendFunc(GL.GL_ONE, GL.GL_ONE); // additive blending (ignore alpha)
gl.glEnable(GL.GL_LINE_SMOOTH); // make points round
gl.glLineWidth(1);
if(renderUsingVA) {
for(int i=0; i<maxParticles; i++) {
if(particles[i].alpha > 0) {
particles[i].update();
particles[i].updateVertexArrays(i, posArray, colArray);
}
}
gl.glEnableClientState(GL.GL_VERTEX_ARRAY);
gl.glVertexPointer(2, GL.GL_FLOAT, 0, posArray);
gl.glEnableClientState(GL.GL_COLOR_ARRAY);
gl.glColorPointer(3, GL.GL_FLOAT, 0, colArray);
gl.glDrawArrays(GL.GL_LINES, 0, maxParticles * 2);
}
else {
gl.glBegin(GL.GL_LINES); // start drawing points
for(int i=0; i<maxParticles; i++) {
if(particles[i].alpha > 0) {
particles[i].update();
particles[i].drawOldSchool(gl); // use oldschool renderng
}
}
gl.glEnd();
}
// gl.glDisable(GL.GL_BLEND);
//Reset blendfunction
gl.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE_MINUS_SRC_ALPHA);
pgl.endGL();
}
public void fadeToColor(PApplet p, GL gl, float r, float g, float b, float speed) {
// gl.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE_MINUS_SRC_ALPHA);
gl.glColor4f(r, g, b, speed);
gl.glBegin(GL.GL_QUADS);
gl.glVertex2f(0, 0);
gl.glVertex2f(p.width, 0);
gl.glVertex2f(p.width, p.height);
gl.glVertex2f(0, p.height);
gl.glEnd();
}
public void addParticles(float x, float y, int count ){
for(int i=0; i<count; i++) addParticle(x + p.random(-15, 15), y + p.random(-15, 15));
}
public void addParticle(float x, float y) {
particles[curIndex].init(x, y);
curIndex++;
if(curIndex >= maxParticles) curIndex = 0;
}
public boolean isDrawFluid() {
return drawFluid;
}
public void setDrawFluid(boolean drawFluid) {
this.drawFluid = drawFluid;
}
}//end psystem class
}