/** * this sketch shows how the forest burned down every now and then. */ import gestalt.Gestalt; import gestalt.candidates.quad.JoglQuad; import gestalt.context.GLContext; import gestalt.impl.jogl.context.JoglGLContext; import gestalt.p5.GestaltPlugIn; import gestalt.shape.AbstractDrawable; import gestalt.shape.Color; import mathematik.Vector2i; import mathematik.Vector3f; import werkzeug.interpolation.InterpolateClamp; import werkzeug.interpolation.InterpolateExponential; import werkzeug.interpolation.InterpolateInvert; import werkzeug.interpolation.Interpolator; import werkzeug.interpolation.InterpolatorKernel; import werkzeug.interpolation.InterpolatorKernelCombiner; import net.java.games.jogl.GL; import processing.opengl.*; public class ForestFire extends PApplet { private GestaltPlugIn gestalt; private FireWorld _myFire; private final Vector2i _myWorldSize = new Vector2i(40 * 2, 30 * 2); public void setup() { /* setup p5 */ size(640, 480, OPENGL); framerate(50); rectMode(CENTER); noStroke(); gestalt = new GestaltPlugIn(this); /* fire */ _myFire = new FireWorld(_myWorldSize); _myFire.scale.set(width / (float) _myWorldSize.x, height / (float) _myWorldSize.y); gestalt.bin(Gestalt.BIN_3D).add(_myFire); } public void draw() { /* clear screen */ background(50); if (mousePressed) { int myX = (int) (_myWorldSize.x * (float) mouseX / (float) width); int myY = (int) (_myWorldSize.y * (float) mouseY / (float) height); _myFire.getCells()[myX][myY].temperature += _myFire.getCells()[myX][myY].ignitiontemperatur; } if (keyPressed) { if (key == 'r') { _myFire.reset(); } } /* work fire */ _myFire.loop(1 / 50f * 10); } private class FireWorld extends AbstractDrawable { public Vector3f scale; public Vector3f position; private final FireCell[][] _myCells; private Vector2i _myWorldSize; public FireWorld(Vector2i theWorldSize) { _myWorldSize = theWorldSize; _myCells = new FireCell[_myWorldSize.x][_myWorldSize.y]; scale = new Vector3f(1, 1, 1); position = new Vector3f(); reset(); } public void reset() { for (int x = 0; x < _myCells.length; x++) { for (int y = 0; y < _myCells[x].length; y++) { _myCells[x][y] = new FireCell(_myWorldSize, new Vector2i(x, y)); _myCells[x][y].ignitiontemperatur *= random(0.75f, 1); _myCells[x][y].temperatureincrease *= random(0.75f, 1); } } } public FireCell[][] getCells() { return _myCells; } public void loop(float theDeltaTime) { for (int x = 0; x < _myCells.length; x++) { for (int y = 0; y < _myCells[x].length; y++) { /* process cells */ FireCell myCell = _myCells[x][y]; /* burn resources */ myCell.burn(theDeltaTime, _myCells); /* check to for ignition */ myCell.ignite(theDeltaTime, _myCells); } } } public void draw(GLContext theGLContext) { GL gl = ( (JoglGLContext) theGLContext).gl; gl.glPushMatrix(); gl.glTranslatef(position.x, position.y, position.z); gl.glScalef(scale.x, scale.y, scale.z); for (int x = 0; x < _myCells.length; x++) { for (int y = 0; y < _myCells[x].length; y++) { gl.glPushMatrix(); gl.glTranslatef(x, y, 0); _myCells[x][y].draw(theGLContext); gl.glPopMatrix(); } } gl.glPopMatrix(); } } public class FireCell { public float temperature; public float flickrtemperature; private int flickrtoggle; public float ignitiontemperatur; public float temperatureincrease; public float temperaturedecrease; private float _myResource; private JoglQuad _myPlane; private final Vector2i _myWorldSize; private final Vector2i _myPosition; private final float[] _myEdgeTemperature; private static final float TEMPERATURE_FALL_OFF = 0.5f; private static final float IGNITION_TEMPERATURE = 10f; private static final float MAXIMUM_TEMPERATURE = 20f; private static final float TEMPERATURE_COLOR_CLAMP = 12f; private static final float TEMPERATURE_INCREASE = 0.015f; private static final float LOWEST_ACCEPTABLE_TEMPERATURE = 0.0001f; private static final float TEMPERATURE_DECREASE = 0.8f; private static final float INITIAL_RESOURCES = 80; private static final float RESOURCES_ALPHA_CLAMP = 60f; private static final float FLICKER_DELTA = 1.5f; private final Interpolator _myTemperatureAlphaInterpolator; private final Interpolator _myTemperatureRedInterpolator; private final Interpolator _myTemperatureWhiteInterpolator; private final Interpolator _myAshInterpolator; private int ONE = 0; private int TWO = 1; private int THREE = 2; private int FOUR = 3; public FireCell(final Vector2i theWorldSize, final Vector2i thePosition) { _myWorldSize = theWorldSize; _myPosition = thePosition; _myEdgeTemperature = new float[4]; _myResource = INITIAL_RESOURCES; temperature = 0; ignitiontemperatur = IGNITION_TEMPERATURE; temperatureincrease = TEMPERATURE_INCREASE; temperaturedecrease = TEMPERATURE_DECREASE; /* create plane */ _myPlane = new JoglQuad(); /* rotate vertex order to prevent ugly vertex color blend effect */ int myRandomOffset = (int) random(0, 10000); ONE += myRandomOffset; ONE %= _myPlane.getPoints().length; TWO += myRandomOffset; TWO %= _myPlane.getPoints().length; THREE += myRandomOffset; THREE %= _myPlane.getPoints().length; FOUR += myRandomOffset; FOUR %= _myPlane.getPoints().length; _myPlane.getPoints()[ONE].set(0, 0); _myPlane.getPoints()[TWO].set(1, 0); _myPlane.getPoints()[THREE].set(1, 1); _myPlane.getPoints()[FOUR].set(0, 1); _myPlane.setColorsRef(new Color[] { new Color(0, 0), new Color(0, 0), new Color(0, 0), new Color(0, 0) } ); /* interpolators */ { InterpolatorKernel myTemperatureAlpha = new InterpolatorKernelCombiner(new InterpolateClamp(0.2f, 0.8f), new InterpolateExponential(0.8f)); _myTemperatureAlphaInterpolator = new Interpolator(0, 1, myTemperatureAlpha); } { InterpolatorKernel myTemperatureColor = new InterpolatorKernelCombiner(new InterpolateClamp(0.6f, 1.0f), new InterpolateExponential(0.5f)); _myTemperatureRedInterpolator = new Interpolator(0, 1, myTemperatureColor); } { InterpolatorKernel myTemperatureColor = new InterpolatorKernelCombiner(new InterpolateClamp(0.6f, 1.0f), new InterpolateExponential(5f)); _myTemperatureWhiteInterpolator = new Interpolator(0, 0.7f, myTemperatureColor); } { InterpolatorKernel myAsh = new InterpolatorKernelCombiner(new InterpolateInvert(), new InterpolateClamp(0.0f, 1.0f)); _myAshInterpolator = new Interpolator(0, 0.90f, myAsh); } } public void ignite(final float theDeltaTime, final FireCell[][] theFireCells) { if (_myResource > 0) { /* check neighbors if it is okey to ignite */ if (_myPosition.x > 0) { igniteByNeigbor(theFireCells[_myPosition.x - 1][_myPosition.y].temperature / 1, theDeltaTime); } if (_myPosition.x < _myWorldSize.x - 1) { igniteByNeigbor(theFireCells[_myPosition.x + 1][_myPosition.y].temperature / 1, theDeltaTime); } if (_myPosition.y > 0) { igniteByNeigbor(theFireCells[_myPosition.x][_myPosition.y - 1].temperature / 1, theDeltaTime); } if (_myPosition.y < _myWorldSize.y - 1) { igniteByNeigbor(theFireCells[_myPosition.x][_myPosition.y + 1].temperature / 1, theDeltaTime); } /* diagonal */ final float myDiagonal = sqrt(2); if (_myPosition.x > 0 && _myPosition.y > 0) { igniteByNeigbor(theFireCells[_myPosition.x - 1][_myPosition.y - 1].temperature / myDiagonal, theDeltaTime); } if (_myPosition.x < _myWorldSize.x - 1 && _myPosition.y > 0) { igniteByNeigbor(theFireCells[_myPosition.x + 1][_myPosition.y - 1].temperature / myDiagonal, theDeltaTime); } if (_myPosition.x < _myWorldSize.x - 1 && _myPosition.y < _myWorldSize.y - 1) { igniteByNeigbor(theFireCells[_myPosition.x + 1][_myPosition.y + 1].temperature / myDiagonal, theDeltaTime); } if (_myPosition.x > 0 && _myPosition.y < _myWorldSize.y - 1) { igniteByNeigbor(theFireCells[_myPosition.x - 1][_myPosition.y + 1].temperature / myDiagonal, theDeltaTime); } } } private void calculateEdgeTemperature(final FireCell[][] theFireCells) { final int myNumberOfValues = 5; /* lower left */ if (_myPosition.x > 0 && _myPosition.y > 0) { _myEdgeTemperature[ONE] = (flickrtemperature + flickrtemperature + theFireCells[_myPosition.x - 1][_myPosition.y - 1].flickrtemperature + theFireCells[_myPosition.x - 1][_myPosition.y].flickrtemperature + theFireCells[_myPosition.x][_myPosition.y - 1].flickrtemperature) / myNumberOfValues; } /* lower right */ if (_myPosition.x < _myWorldSize.x - 1 && _myPosition.y > 0) { _myEdgeTemperature[TWO] = (flickrtemperature + flickrtemperature + theFireCells[_myPosition.x + 1][_myPosition.y - 1].flickrtemperature + theFireCells[_myPosition.x + 1][_myPosition.y].flickrtemperature + theFireCells[_myPosition.x][_myPosition.y - 1].flickrtemperature) / myNumberOfValues; } /* upper right */ if (_myPosition.x < _myWorldSize.x - 1 && _myPosition.y < _myWorldSize.y - 1) { _myEdgeTemperature[THREE] = (flickrtemperature + flickrtemperature + theFireCells[_myPosition.x + 1][_myPosition.y + 1].flickrtemperature + theFireCells[_myPosition.x + 1][_myPosition.y].flickrtemperature + theFireCells[_myPosition.x][_myPosition.y + 1].flickrtemperature) / myNumberOfValues; } /* upper left */ if (_myPosition.x > 0 && _myPosition.y < _myWorldSize.y - 1) { _myEdgeTemperature[FOUR] = (flickrtemperature + flickrtemperature + theFireCells[_myPosition.x - 1][_myPosition.y + 1].flickrtemperature + theFireCells[_myPosition.x - 1][_myPosition.y].flickrtemperature + theFireCells[_myPosition.x][_myPosition.y + 1].flickrtemperature) / myNumberOfValues; } } private void igniteByNeigbor(final float theTemperature, final float theDeltaTime) { if (temperature < theTemperature && theTemperature >= ignitiontemperatur) { temperature += (theTemperature - temperature) * TEMPERATURE_FALL_OFF * theDeltaTime; } } public void burn(final float theDeltaTime, final FireCell[][] theFireCells) { if (_myResource <= 0) { /* put out the fire */ temperature -= temperature * theDeltaTime * temperaturedecrease; if (temperature < LOWEST_ACCEPTABLE_TEMPERATURE) { temperature = 0; } _myResource = 0; } else if (temperature > 0) { /* increase heat */ _myResource -= temperature * theDeltaTime; if (temperature < MAXIMUM_TEMPERATURE) { temperature += (MAXIMUM_TEMPERATURE - temperature) * theDeltaTime * temperatureincrease; } } /* flickr */ flickrtemperature = temperature; if (flickrtoggle % 4 == 0) { flickrtemperature *= random(1 - FLICKER_DELTA * theDeltaTime, 1 + FLICKER_DELTA * theDeltaTime); } flickrtoggle += theDeltaTime * 10; /* edge temperatures */ calculateEdgeTemperature(theFireCells); } public void draw(final GLContext theGLContext) { /* ashes */ if (_myResource < INITIAL_RESOURCES) { for (int i = 0; i < _myEdgeTemperature.length; i++) { final float myPercent = _myResource / RESOURCES_ALPHA_CLAMP; _myPlane.getColors()[i].set(0, _myAshInterpolator.get(myPercent)); } /* draw */ _myPlane.material().blendmode = Gestalt.MATERIAL_BLEND_ALPHA; _myPlane.draw(theGLContext); } /* fire */ if (temperature > 0) { for (int i = 0; i < _myEdgeTemperature.length; i++) { final float myPercent = _myEdgeTemperature[i] / TEMPERATURE_COLOR_CLAMP; _myPlane.getColors()[i].set(1, _myTemperatureRedInterpolator.get(myPercent), _myTemperatureWhiteInterpolator.get(myPercent), _myTemperatureAlphaInterpolator.get(myPercent)); } /* draw */ _myPlane.material().blendmode = Gestalt.MATERIAL_BLEND_ALPHA; _myPlane.draw(theGLContext); } } } public static void main(String[] args) { PApplet.main(new String[] { "ForestFire" } ); } }