/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
* Copyright 2009 Sun Microsystems, Inc. All rights reserved. Use is subject to license terms.
*
* This file is available and licensed under the following license:
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of Sun Microsystems nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package solarsystem;
import javafx.animation.*;
import javafx.stage.*;
import javafx.ext.swing.*;
import javafx.scene.*;
import javafx.scene.effect.*;
import javafx.scene.effect.light.*;
import javafx.scene.shape.*;
import javafx.scene.paint.*;
import javafx.scene.transform.*;
import javafx.scene.layout.*;
import javafx.scene.text.*;
import javafx.util.*;
import java.lang.Math;
def earthOrbitTime = 2s;
def zoomSliderMax = 200;
def zoomSliderDefault = 5;
def zoomSlider = SwingSlider {maximum: zoomSliderMax, value: zoomSliderDefault};
var scaleFactor = bind (80.0 * Math.max(1, zoomSlider.value)) / zoomSliderMax;
def sunRadius = bind scaleFactor * 0.9;
def earthRadius = bind scaleFactor * 0.5;
def earthOrbitPixels = bind 10 * earthRadius;
def speedSliderMax = 300;
def speedSliderDefault = 100;
def speedSlider = SwingSlider {maximum: speedSliderMax, value: speedSliderDefault};
var speed = bind Math.max(0.00001, (speedSlider.value as Number) / speedSliderMax);
def sunColor = Color.YELLOW;
def planetDarkSideColor = Color.BLUE;
def planetLightSideColor = Color.LIGHTBLUE;
def orbitColor = Color.GREY;
def sunName = "Sun";
def eccentricityThreshold = 0.05;
class Planet {
var name: String;
var darkSideColor: Color = planetDarkSideColor;
var brightSideColor: Color = planetLightSideColor;
var orbit: Number;
var day: Number;
var year: Number;
var radius: Number;
var eccentricity: Number;
}
class PlanetarySystem extends CustomNode {
var planets: Planet[];
override function create(): Node {
def maxOrbit = Sequences.max(for (planet in planets) planet.orbit) as Number;
def maxOrbitPixels = earthOrbitPixels * maxOrbit;
def padX = earthRadius * 1.5;
def padY = earthRadius * 1.5;
def sunCenterX = maxOrbitPixels + padX;
def sunCenterY = maxOrbitPixels + padY;
def height = 2 * sunCenterX;
def width = 2 * sunCenterY;
var nodes: Node[];
for (i in [0..height*width/1000])
insert star(Math.random() * width, Math.random() * height) into nodes;
insert Circle {
centerX: sunCenterX, centerY: sunCenterY
radius: bind sunRadius
fill: Color.YELLOW
effect: Glow {level: 0.9}
} into nodes;
insert Text {
content: sunName
fill: Color.BLACK
x: sunCenterX y: sunCenterY
font: bind Font {size: sunRadius / 3}
visible: bind sunRadius > 30
} into nodes;
for (planet in planets) {
insert
if (planet.eccentricity > eccentricityThreshold) {
EllipticalOrbitingPlanet {
sunCenterX: sunCenterX, sunCenterY: sunCenterY
planet: planet
}
} else {
CircularOrbitingPlanet {
sunCenterX: sunCenterX, sunCenterY: sunCenterY
planet: planet
}
} into nodes
}
return Group {content: nodes}
}
}
function star(x: Number, y: Number): Node {
return Rectangle {
x: x, y: y, height: 1, width: 1, fill: Color.WHITE
}
}
class CircularOrbitingPlanet extends CustomNode {
var planet: Planet;
var sunCenterX: Number;
var sunCenterY: Number;
def period = earthOrbitTime.mul(planet.year);
var angle: Number;
var timer: Timeline = Timeline {
repeatCount: Timeline.INDEFINITE
rate: bind speed
keyFrames: [
KeyFrame {time: 0s, values: [angle => 0]},
KeyFrame {time: period, values: [angle => 360]}
]
}
override function create(): Node {
timer.play();
def orbit = bind planet.orbit * earthOrbitPixels;
return Group {
content: [
Circle {
centerX: sunCenterX, centerY: sunCenterY
radius: bind orbit
strokeWidth: 2;
stroke: orbitColor;
fill: null
},
Text {
content: planet.name
fill: planet.brightSideColor
x: sunCenterX
y: bind sunCenterY - orbit * 0.85
font: bind Font {size: orbit / 10}
visible: bind (orbit > 60)
},
Circle {
transforms: bind Transform.rotate(angle, sunCenterX, sunCenterY)
centerX: bind sunCenterX - orbit, centerY: sunCenterY
radius: bind planet.radius * earthRadius
fill: LinearGradient {
endX: 1.0, endY: 0.0
stops: [
Stop {offset: 0.0, color: planet.darkSideColor},
Stop {offset: 1.0, color: planet.brightSideColor}
]
}
effect: Lighting {light: PointLight {x: sunCenterX, y: sunCenterY, z: 300}}
}
]
};
}
}
class EllipticalOrbitingPlanet extends CustomNode {
var planet: Planet;
var sunCenterX: Number;
var sunCenterY: Number;
def period = earthOrbitTime.mul(planet.year);
def nSubPeriods = 72;
var angle: Number;
var timer: Timeline;
override function create(): Node {
def semiLatusRectum = planet.orbit * (1.0 + planet.eccentricity);
def semiMajorAxis = planet.orbit / (1.0 - planet.eccentricity);
def offsetFromCentre = semiMajorAxis - planet.orbit;
def shiftPixels = bind offsetFromCentre * earthOrbitPixels;
var frames: KeyFrame[];
def eccFactor = Math.sqrt((1 + planet.eccentricity) / (1 - planet.eccentricity));
for (t in [0..nSubPeriods-1]) {
def tFraction = (t as Number) / nSubPeriods;
def meanAnomaly = 2 * Math.PI * tFraction;
def eccentricAnomaly = solveEccentricAnomaly(meanAnomaly);
def theta = 2 * Math.atan(eccFactor * Math.tan(eccentricAnomaly / 2));
def r = semiLatusRectum / (1 + planet.eccentricity * Math.cos(theta));
def x = -offsetFromCentre - r * Math.cos(theta);
def y = -r * Math.sin(theta);
def centreAngle = 180 + Math.toDegrees(Math.atan2(y, x));
insert
KeyFrame {time: period.mul(tFraction)
values: [angle => centreAngle]}
into frames;
}
insert
KeyFrame {time: period
values: [angle => 360]}
into frames;
timer = Timeline {
repeatCount: Timeline.INDEFINITE, rate: bind speed, keyFrames: frames
};
timer.play();
def orbit = bind planet.orbit * earthOrbitPixels;
return Group {
content: [
Circle {
centerX: bind sunCenterX + shiftPixels, centerY: sunCenterY
radius: bind orbit
strokeWidth: 2;
stroke: orbitColor;
fill: null
},
Text {
content: planet.name
fill: planet.brightSideColor
x: sunCenterX + shiftPixels
y: bind sunCenterY - orbit * 0.85
font: bind Font {size: orbit / 10}
visible: bind (orbit > 60)
},
Circle {
transforms: bind Transform.rotate(angle, sunCenterX + shiftPixels, sunCenterY);
centerX: bind sunCenterX + shiftPixels - orbit, centerY: sunCenterY
radius: bind planet.radius * earthRadius
fill: LinearGradient {
endX: 1.0, endY: 0.0
stops: [
Stop {offset: 0.0, color: planet.darkSideColor},
Stop {offset: 1.0, color: planet.brightSideColor}
]
}
effect: Lighting {light: PointLight {x: sunCenterX, y: sunCenterY, z: 300}}
}
]
};
}
function solveEccentricAnomaly(meanAnomaly: Number) {
var e = meanAnomaly;
for (i in [1..10])
e = meanAnomaly + planet.eccentricity * Math.sin(e);
return e;
}
}
class NamedSlider extends CustomNode {
var slider: SwingSlider;
var name: String;
var width: Integer;
override public function create(): Node {
return HBox {
content: [
Group {
content: [
Rectangle {
height: 1 width: 80 visible: false
},
Text {
content: name
font: Font {
size: 24
}
fill: Color.WHITE
transforms: Transform.translate(0, 20)
}
]
},
slider
]
}
}
}
function translucentNode(n: Node): Node {
var mouse: Boolean;
return Group {
opacity: bind if (mouse) 1 else 0.4
content: [
n,
Rectangle {
opacity: 0
width: bind n.boundsInParent.width + 10
height: bind n.boundsInParent.height + 10
onMouseEntered: function(event) {mouse = true}
onMouseExited: function(event) {mouse = false}
}
]
}
}
def planets = [
Planet{name: "Mercury" orbit: 0.3075 radius: 0.383 day: 58.65 year: 0.24
brightSideColor: Color.LIGHTGREY darkSideColor: Color.DARKGREY
eccentricity: 0.20563},
Planet{name: "Venus" orbit: 0.7184 radius: 0.95 day: 243 year: 0.615
brightSideColor: Color.WHITE darkSideColor: Color.BEIGE
eccentricity: 0.0068},
Planet{name: "Earth" orbit: 0.9833 radius: 1 day: 0.997 year: 1
brightSideColor: Color.LIGHTBLUE darkSideColor: Color.BLUE
eccentricity: 0.01671},
Planet{name: "Mars" orbit: 1.3815 radius: 0.532 day: 1.025 year: 1.88
brightSideColor: Color.ORANGE darkSideColor: Color.DARKORANGE
eccentricity: 0.093315},
Planet{name: "Jupiter" orbit: 4.95 radius: 11.2 day: 0.41 year: 11.86
brightSideColor: Color.LIGHTBLUE darkSideColor: Color.BLUE
eccentricity: 0.048775},
Planet{name: "Saturn" orbit: 9.0481 radius: 9.45 day: 0.439 year: 29.66
brightSideColor: Color.YELLOW darkSideColor: Color.SLATEGREY
eccentricity: 0.055723},
Planet{name: "Uranus" orbit: 18.376 radius: 4 day: 0.718 year: 84.323
brightSideColor: Color.LIGHTCYAN darkSideColor: Color.CYAN
eccentricity: 0.0444},
Planet{name: "Neptune" orbit: 29.766 radius: 3.8 day: 0.671 year: 164.79
brightSideColor: Color.BLUE darkSideColor: Color.DARKBLUE
eccentricity: 0.0112}
];
Stage {
title: "SolarSystem";
visible: true
resizable: false
height: 600 width: 600
scene: Scene {
content: [
Group {
content: [
PlanetarySystem {planets: planets},
translucentNode(VBox {
transforms: Transform.translate(10, 10);
content: [
NamedSlider {name: "Speed" slider: speedSlider},
NamedSlider {name: "Zoom" slider: zoomSlider}
]
})
]
}
]
fill: Color.BLACK
}
}