/************************************************************************** Synopsis: Solar System animation using JavaFX Author: Kannan Balasubramanian Last Updated Date: 11/28/2007 **************************************************************************/ import javafx.ui.*; import javafx.ui.filter.*; import javafx.ui.canvas.*; import java.lang.System; import java.awt.Toolkit; import java.lang.Math; import java.awt.Dimension; /* * Documented: * Press key 's' or 'S' to toggle stars visibility * Press 'up/down' arrow to increase/decrease orbit rotation speed * Press 'left/right' arrow key to increase/decrease stars count * Press 'p' to pause/resume * Press 'Esc' key to exit * * Undocumented: * Press PageUp/PageDown key to increase/decrease planet radius */ var screensize = Toolkit.getDefaultToolkit().getScreenSize(); var model = SolarModel { colors: [orange:Color, blue:Color, cyan:Color, magenta:Color, red:Color, yellow:Color, pink:Color, white:Color, green:Color, orange:Color ] planetRadius: [30, 36, 40, 50, 56, 60, 40, 36, 30, 80] }; System.out.println("Screen Resolution: {screensize.width} * {screensize.height}"); class Planet extends Circle { attribute count:Number; //count for fetching the planet's path attribute direction:Boolean; //true: clockwise, false: anticlockwise } class SolarModel { attribute MIN_RADIUS:Number; attribute MAX_RADIUS:Number; attribute MIN_SPEED:Number; attribute MAX_SPEED:Number; attribute curSpeed:Number; //speed of rotation of planet attribute orbits: Arc*; //path for each planet to revolute on attribute planets: Planet*; attribute elapsed: Number; attribute pause: Boolean; attribute screensize: Dimension; attribute customColor: Color; attribute colors: Color*; attribute planetRadius: Number*; attribute saturnRing: Arc; operation animatePlanets(); operation modifyPlanetRadius(radius:Number); operation moveClockwise(planet:Planet, arc:Arc); operation moveAntAntiClockwise(planet:Planet, arc:Arc); } //initialize variables attribute SolarModel.elapsed = bind [0..5000] dur 200000 linear; attribute SolarModel.screensize = Toolkit.getDefaultToolkit().getScreenSize(); attribute SolarModel.pause = false; attribute SolarModel.MIN_RADIUS= 2; attribute SolarModel.MAX_RADIUS= 140; attribute SolarModel.MIN_SPEED= 1; attribute SolarModel.MAX_SPEED= 20; attribute SolarModel.curSpeed= 2; attribute SolarModel.customColor = Color { red: 238/255 blue: 154/255 green: 73/255 }; trigger on SolarModel.elapsed = newValue { animatePlanets(); } operation SolarModel.animatePlanets() { if (pause) { return; } if (planets[0] == null) { //This check is required to not proceed until all planets //have been initialized. Do not remove it! return; } for (i in [0..8]) { if (planets[i].direction == true) { moveClockwise(planets[i], orbits[i]); } else { moveAntiClockwise(planets[i], orbits[i]); } } } //Function to increase/decrease planet radius. //Just for internal use operation SolarModel.modifyPlanetRadius(offset:Number) { for (c in planets) { if (offset >= 0) { if (c.radius + offset > MAX_RADIUS) { continue; } } else if (c.radius + offset < MIN_RADIUS) { continue; } c.radius = c.radius + offset; } } //Move planet in clockwise direction operation SolarModel.moveClockwise(planet:Planet, arc:Arc) { if (planet == null or arc == null) { return; } if (planet.count == -1) { planet.count = Math.random() * arc.length(); } var pt = arc.pointAt(planet.count); planet.cx = pt.getX(); planet.cy = pt.getY(); planet.count = planet.count + curSpeed; if (planet.count >= arc.length()) { planet.count = 0; } } //Move planet in anticlockwise direction operation SolarModel.moveAntiClockwise(planet:Planet, arc:Arc) { if (planet == null or arc == null) { return; } if (planet.count == -1) { planet.count = Math.random() * arc.length(); } var pt = arc.pointAt(planet.count); planet.cx = pt.getX(); planet.cy = pt.getY(); planet.count = planet.count - curSpeed; if (planet.count <= 0) { planet.count = arc.length(); } } class StarModel { attribute MIN_STARS:Number; attribute MAX_STARS:Number; attribute stars: Circle*; attribute backupstars: Circle*; attribute screensize: Dimension; attribute elapsed: Number; operation addStars(count:Number); operation removeStars(count:Number); operation animateStars(); } attribute StarModel.screensize = Toolkit.getDefaultToolkit().getScreenSize(); attribute StarModel.MIN_STARS = 10; attribute StarModel.MAX_STARS = 1000; attribute StarModel.elapsed = bind [0..5000] dur 200000 linear; trigger on StarModel.elapsed = newValue { animateStars(); } //Show stars at random places within the screen's dimensions operation StarModel.animateStars() { for (c in stars) { c.cx = Math.random()*screensize.width; c.cy = Math.random()*screensize.height; } } operation StarModel.addStars(count:Number) { if (sizeof stars + count <= MAX_STARS) { var starcount = 0; var r = 1; while (starcount < count) { var c = Circle { cx: Math.random()*screensize.width cy: Math.random()*screensize.height fill: white radius:r }; insert c into stars; if (starcount%50 == 0) { r = 3; } else if (starcount%2 == 0) { r = 2; } else { r = 1; } starcount = starcount + 1; } } } operation StarModel.removeStars(count:Number) { if (sizeof stars - count >= MIN_STARS) { var starcount = 0; while (starcount < count) { delete stars[0]; starcount = starcount + 1; } } } var starmodel = StarModel { }; trigger on new StarModel { var r = 1; var starcount = 0; while (starcount < 100) { var c = Circle { cx: Math.random()*screensize.width cy: Math.random()*screensize.height fill: white radius:r }; insert c into stars; if (starcount%50 == 0) { //generate one out of 50 stars with a radius of 3 r = 3; } else if (starcount%2 == 0) { r = 2; } else { r = 1; } starcount = starcount + 1; } } var sx = 20; var sy = 30; var rw = screensize.width-30; var rh = screensize.height-2*sy; var offset = 30; var planetRotation = true; for (i in [0..9]) { var orbit = Arc { x: sx y: sy width : rw height : rh outline: true stroke: model.colors[i] startAngle: 0 length: 360 //end Angle closure: CHORD:ArcClosure }; var planet = Planet { cx: sx + rw/2 cy: sy radius: model.planetRadius[i]/3 count: -1 direction: planetRotation }; if (i <> 9) { planet.fill = RadialGradient { cx: planet.cx cy: planet.cy focusX: planet.cx focusY: planet.cy radius: planet.radius spreadMethod: REPEAT:SpreadMethod stops: [Stop { color: model.customColor offset: 0.0 }, Stop { color: white offset: 0.3 }, Stop { color: model.customColor offset: 0.5 }, Stop { color: white offset: 0.7 }, Stop { color: model.customColor offset: 1.0 }] }; } else { //Sun planet.fill = red; planet.cx = sx + rw/2; planet.cy = sy + rh/2; } if (i == 3) { model.saturnRing = Arc { x: bind planet.cx - 2*planet.radius y: bind planet.cy - planet.radius/2 width: bind planet.radius * 4 height: 10 outline: true startAngle:117 length: 305 //end Angle stroke: red strokeWidth:3 closure: CHORD:ArcClosure }; } insert planet into model.planets; if (i <> 9) { insert orbit into model.orbits; } sx = sx + offset; sy = sy + offset; rw = rw - 2*offset; rh = rh - 2*offset; planetRotation = not planetRotation; } var frame = Frame { width: screensize.width height: screensize.height undecorated: true disposeOnClose: true content: Canvas { background: black focusable: true focused: true doubleBuffered: true var starText = "Stars Count: {sizeof starmodel.stars}" var planetText = "Speed: {model.curSpeed*5}%" content: bind [model.orbits, model.planets, model.saturnRing, starmodel.stars, Text { x: screensize.width/4-30 y: 20 fill: white outline: true content: "JAVAFX" font: Font { size:20 style:BOLD faceName: "Courier" } }, Text { x: 20 y: 40 fill: white content: "Press s to toggle stars visibility" font: Font { size:12 style:BOLD faceName: "Courier" } }, Text { x: 20 y: 60 fill: white content: "Press up/down arrow to +/- orbit rotation speed" font: Font { size:12 style:BOLD faceName: "Courier" } }, Text { x: 20 y: 80 fill: white content: "Press left/right arrow to +/- stars" font: Font { size:12 style:BOLD faceName: "Courier" } }, Text { x: 20 y: 100 fill: white content: "Press p to pause/resume" font: Font { size:12 style:BOLD faceName: "Courier" } }, Text { x: 20 y: 120 fill: white content: "Press Esc to exit" font: Font { size:12 style:BOLD faceName: "Courier" } }, Text { x: 20 y: 140 fill: magenta content: bind starText font: Font { size:12 style:BOLD faceName: "Courier" } }, Text { x: 20 y: 160 fill: magenta content: bind planetText font: Font { size:12 style:BOLD faceName: "Courier" } } ] keyboardAction: KeyboardAction { //This enables focus and hence keyboard action on the canvas enabled: true } onKeyTyped: operation(e:KeyEvent) { if (not model.pause and (e.keyChar == 's' or e.keyChar == 's')) { if (starmodel.stars == null) { //get back stars from backbuffer starmodel.stars = starmodel.backupstars; } else { //cache stars offscreen when not required starmodel.backupstars = starmodel.stars; starmodel.stars = null; } } else if (e.keyChar == 'p' or e.keyChar == 'P') { model.pause = not model.pause; } } onKeyDown: operation(e:KeyEvent) { if (model.pause) { //Ignore all other keys in pause mode return; } if (e.keyStroke == ESCAPE:KeyStroke) { System.exit(0); } if (e.keyStroke == LEFT:KeyStroke) { if (starmodel.stars <> null) { starmodel.addStars(60); starText = "Stars Count: {sizeof starmodel.stars}"; } } else if (e.keyStroke == RIGHT:KeyStroke) { if (starmodel.stars <> null) { starmodel.removeStars(60); starText = "Stars Count: {sizeof starmodel.stars}"; } } else if (e.keyStroke == UP:KeyStroke) { if (model.curSpeed + 2 <= model.MAX_SPEED) { model.curSpeed = model.curSpeed + 2; planetText = "Speed: {model.curSpeed*5}%"; } } else if (e.keyStroke == DOWN:KeyStroke) { if (model.curSpeed - 2 >= model.MIN_SPEED) { model.curSpeed = model.curSpeed - 2; planetText = "Speed: {model.curSpeed*5}%"; } } else if (e.keyStroke == PAGE_UP:KeyStroke) { model.modifyPlanetRadius(2); } else if (e.keyStroke == PAGE_DOWN:KeyStroke) { model.modifyPlanetRadius(-2); } } } visible: true };