//Program to model the changing states of water //One molecule of water contains one oxygen atom (red) and two hydrogen atoms (white) //Water appears commonly on earth in three states of matter (solid, liquid and gas) //The phase of matter of water depends on temperature (and pressure). //This simulation only uses temperature to model water molecules in their different //phases. //In its solid state the molecules are close together and fixed in place (ice) //in its liquid state, the molecules are close together but can move around freely ("water") //in its gaseous state, the molecules can move freely are not necessarily close together (steam) //written by C. Hebert - with very, very, small mods Z. Wood // For 5th grade //TODO: Fill in the correct temperatures in Farenheit for when water changes its state of matter //for sealevel - the below values are incorrect float iceTemperature = -100; float gasTemperature = 300; boolean myAnim = false; color oxygenColor = color(255, 0, 0); color hydrogenColor = color(255, 255, 255); color backgroundColor = color(135, 206, 250); //For the scroll bar coloring color waterScrollbarColor = color(69, 105, 127); // Water is blue color iceScrollbarColor = color(248, 248, 255); // White is cold color gasScrollbarColor = color(295, 194, 15); // Orange is hot // Be careful and only change to code in the code block that says TODO class FloatingWaterMolecule { int x, y; int targetX, targetY; float speed; float angleOffset, angularVelocity; void initializeRandomTarget(int minWidth, int minHeight, int maxWidth, int maxHeight) { targetX = int(random(minWidth, maxWidth)); targetY = int(random(minHeight, maxHeight)); } // TODO: You can choose to change the animatation of the water molecules. //The deftault animation uses a target to direct the water, but you can modify the animation void update(int minWidth, int minHeight, int maxWidth, int maxHeight) { if (myAnim == false) { float targetDistance = sqrt(pow(targetX - x, 2) + pow(targetY - y, 2)); x += (float)(targetX - x) / targetDistance * speed; y += (float)(targetY - y) / targetDistance * speed; angleOffset += angularVelocity; if (targetDistance < 5 * speed) { initializeRandomTarget(minWidth, minHeight, maxWidth, maxHeight); } } else { //TODO - fill in how to update the water molecules for example, what does it look like // with: //x += 2; //y += -2; //or //x += random(-1, 3); //y += random(-1, 3); //angleOffset += angularVelocity; } } } //DO not change anything below this line---------------------------- FloatingWaterMolecule largeWaterParticle; FloatingWaterMolecule[] smallWaterParticles; FloatingWaterMolecule[] molecules; int screenWidth = 400; int screenHeight = 400; int numberOfMolecules = 27; int numberOfSmallWaterParticles = (numberOfMolecules - 6) / 3; float minimumTemperature = 0; float maximumTemperature = 250; int oxygenRadius = 14, oxygenDiameter = 2*oxygenRadius; int hydrogenRadius = oxygenRadius / 2, hydrogenDiameter = 2*hydrogenRadius; // Class taken from: https://processing.org/examples/scrollbar.html class HScrollbar { int swidth, sheight; // width and height of bar float xpos, ypos; // x and y position of bar float spos, newspos; // x position of slider float sposMin, sposMax; // max and min values of slider int loose; // how loose/heavy boolean over; // is the mouse over the slider? boolean locked; float ratio; HScrollbar (float xp, float yp, int sw, int sh, int l) { swidth = sw; sheight = sh; int widthtoheight = sw - sh; ratio = (float)sw / (float)widthtoheight; xpos = xp; ypos = yp-sheight/2; spos = xpos + swidth/2 - sheight/2; newspos = spos; sposMin = xpos; sposMax = xpos + swidth - sheight; loose = l; } void update() { if (overEvent()) { over = true; } else { over = false; } if (mousePressed && over) { locked = true; } if (!mousePressed) { locked = false; } if (locked) { newspos = constrain(mouseX-sheight/2, sposMin, sposMax); } if (abs(newspos - spos) > 1) { spos = spos + (newspos-spos)/loose; } } float constrain(float val, float minv, float maxv) { return min(max(val, minv), maxv); } boolean overEvent() { if (mouseX > xpos && mouseX < xpos+swidth && mouseY > ypos && mouseY < ypos+sheight) { return true; } else { return false; } } void display() { float temperatureRange = maximumTemperature - minimumTemperature; float iceWaterBoundary = xpos + (swidth * (iceTemperature - minimumTemperature) / temperatureRange); float waterWidth = swidth * (gasTemperature - iceTemperature) / temperatureRange; float gasWaterBoundary = iceWaterBoundary + (waterWidth); float gasWidth = swidth * (maximumTemperature - gasTemperature) / temperatureRange; noStroke(); fill(iceScrollbarColor); text("Solid", width*.05, 40); rect(xpos, ypos, iceWaterBoundary, sheight); fill(waterScrollbarColor); rect(iceWaterBoundary, ypos, (waterWidth), sheight); text("Liquid", width*.45, 40); fill(gasScrollbarColor); rect(gasWaterBoundary, ypos, gasWidth, sheight); text("Gas", width*.85, 40); if (over || locked) { fill(0, 0, 0); } else { fill(102, 102, 102); } rect(spos, ypos, sheight, sheight); } float getPos() { // Convert spos to be values between // 0 and the total width of the scrollbar return spos * ratio; } } HScrollbar temperatureScrollbar; int HOT_WATER = 0; int COLD_WATER = 1; int ICE = 2; int GAS = 3; int WATER = 4; class Phase { float bias; int stage; } void setup() { temperatureScrollbar = new HScrollbar(0, 16, screenWidth, 16, 16); float moleculeMinSpeed = 2.0; float moleculeMaxSpeed = 2.5; largeWaterParticle = new FloatingWaterMolecule(); largeWaterParticle.x = int(random(0, screenWidth)); largeWaterParticle.y = int(random(screenHeight/2, screenHeight)); largeWaterParticle.initializeRandomTarget(0, screenHeight / 2, screenWidth, screenHeight); largeWaterParticle.speed = moleculeMinSpeed; largeWaterParticle.angleOffset = random(0, 2*PI); largeWaterParticle.angularVelocity = random(2 * PI / 360, 2*PI / 400); smallWaterParticles = new FloatingWaterMolecule[numberOfSmallWaterParticles]; for (int moleculeIndex = 0; moleculeIndex < numberOfSmallWaterParticles; moleculeIndex++) { smallWaterParticles[moleculeIndex] = new FloatingWaterMolecule(); smallWaterParticles[moleculeIndex].x = int(random(0, screenWidth)); smallWaterParticles[moleculeIndex].y = int(random(0, screenHeight)); smallWaterParticles[moleculeIndex].initializeRandomTarget(0, 0, screenWidth, screenHeight); smallWaterParticles[moleculeIndex].speed = random(moleculeMinSpeed, moleculeMaxSpeed); smallWaterParticles[moleculeIndex].angleOffset = random(0, 2*PI); } molecules = new FloatingWaterMolecule[numberOfMolecules]; for (int moleculeIndex = 0; moleculeIndex < numberOfMolecules; moleculeIndex++) { molecules[moleculeIndex] = new FloatingWaterMolecule(); molecules[moleculeIndex].x = int(random(0, screenWidth)); molecules[moleculeIndex].y = int(random(0, screenHeight)); molecules[moleculeIndex].initializeRandomTarget(0, 0, screenWidth, screenHeight); molecules[moleculeIndex].speed = random(moleculeMinSpeed, moleculeMaxSpeed); molecules[moleculeIndex].angleOffset = random(0, 2*PI); molecules[moleculeIndex].angularVelocity = random(2 * PI / 360, 2*PI / 60); } size(screenWidth, screenHeight); } void drawWaterMolecule(int x, int y, float angleOffset) { fill(oxygenColor); ellipse(x, y, oxygenDiameter, oxygenDiameter); fill(hydrogenColor); float angle = 1.0 * PI / 4.0 + angleOffset; ellipse(x + cos(angle) * oxygenRadius, y + sin(angle) * oxygenRadius, hydrogenDiameter, hydrogenDiameter); angle = 3.0 * PI / 4.0 + angleOffset; ellipse(x + cos(angle) * oxygenRadius, y + sin(angle) * oxygenRadius, hydrogenDiameter, hydrogenDiameter); } void drawFloatingWaterMolecules(int numberToDraw) { for (FloatingWaterMolecule molecule : molecules) { if (numberToDraw <= 0) { return; } /* NOTE: Draws target */ /* fill(255, 255, 255); ellipse(molecule.targetX, molecule.targetY, 20, 20); */ molecule.update(0, 0, screenWidth, screenHeight); drawWaterMolecule(molecule.x, molecule.y, molecule.angleOffset); --numberToDraw; } } void drawWaterMoleculeClump(int x, int y) { drawWaterMolecule(x, y, -PI); // NOTE: This is not how the molecule is actually laid out. drawWaterMolecule(x + int(hydrogenRadius + oxygenDiameter), y, -PI); drawWaterMolecule(x + int(oxygenRadius + hydrogenRadius / 2), y + int(oxygenDiameter), -PI); } void drawWaterMoleculeIceRow(int x, int y, int numberToDraw) { if (numberToDraw > 0) { drawWaterMoleculeClump(x, y); if (numberToDraw > 1) { drawWaterMoleculeClump(x - oxygenDiameter*2, y + oxygenDiameter); if (numberToDraw > 2) { drawWaterMoleculeClump(x + oxygenDiameter*2, y + oxygenDiameter); } } } } void drawIce(int x, int y) { drawWaterMoleculeIceRow(x, y, 3); drawWaterMoleculeIceRow(x, y + oxygenDiameter*2, 3); drawWaterMoleculeIceRow(x, y + oxygenDiameter*4, 3); } void drawWater(int numberOfParticles) { if (numberOfParticles <= 0) { return; } largeWaterParticle.update(0, screenHeight / 2, screenWidth, screenHeight); pushMatrix(); translate(largeWaterParticle.x, largeWaterParticle.y); rotate(largeWaterParticle.angleOffset); drawWaterMoleculeIceRow(0, 0, 2); popMatrix(); for (FloatingWaterMolecule molecule : smallWaterParticles) { if (numberOfParticles <= 1) { return; } pushMatrix(); molecule.update(0, screenHeight / 2, screenWidth, screenHeight); translate(molecule.x, molecule.y); rotate(molecule.angleOffset); drawWaterMoleculeIceRow(0, 0, 1); popMatrix(); --numberOfParticles; } } void drawIceWater(float iceBias) { drawIce(200, 200); if (iceBias < 0.5) { drawWater(int(numberOfSmallWaterParticles * (1.0 - iceBias))); } } void drawHotWater(float gasBias) { drawFloatingWaterMolecules(int(numberOfMolecules * gasBias)); drawWater(int(numberOfSmallWaterParticles * (1.0 - gasBias))); } // NOTE: The temperature is measured in degrees Farenheit. Phase getPhaseForTemperature(float temperature) { Phase phase = new Phase(); float phaseTransitionRange = 8; if (temperature <= iceTemperature - phaseTransitionRange) { phase.stage = ICE; } else if (temperature <= iceTemperature) { phase.stage = COLD_WATER; phase.bias = 1.0 - (temperature - iceTemperature + phaseTransitionRange) / phaseTransitionRange; } else if (temperature <= gasTemperature) { phase.stage = WATER; } else if (temperature <= gasTemperature + phaseTransitionRange) { phase.stage = HOT_WATER; phase.bias = 1.0 - (gasTemperature - temperature + phaseTransitionRange) / phaseTransitionRange; } else { phase.stage = GAS; } return phase; } void draw() { background(backgroundColor); temperatureScrollbar.update(); float temperature = (maximumTemperature - minimumTemperature) * (temperatureScrollbar.getPos()/temperatureScrollbar.swidth) + minimumTemperature; Phase phase = getPhaseForTemperature(temperature); if (phase.stage == HOT_WATER) { drawHotWater(phase.bias); } else if (phase.stage == COLD_WATER) { drawIceWater(phase.bias); } else if (phase.stage == WATER) { drawHotWater(0.0); } else if (phase.stage == ICE) { drawIce(200, 200); } else if (phase.stage == GAS) { drawFloatingWaterMolecules(27); } temperatureScrollbar.display(); }