scene = (height = 500, theme = "Day") => {
const
day = theme === "Day",
sunset = theme === "Sunset",
twilight = theme === "Twilight";
if (!(day || sunset || twilight)) throw "Invalid theme! The available options are \"Day\", \"Sunset\" and \"Twilight\".";
const
empty = (() => () => Plot.dot([]))(),
sky = (() => {
const
data = Array.from({length: Y.max}, (_, i) => ({x1: X.min, y1: i, x2: X.max, y2: i+2})),
fill = day ? "skyblue" : sunset ? "orange" : "purple";
return () => Plot.rect(data, {x1: "x1", y1: "y1", x2: "x2", y2: "y2", fill: fill, opacity: d => d.y2 / Y.max});
})(),
sun = (() => () => sunset ? Plot.dot([{x:7.8, y:20}], {x:"x", y:"y", r: 50, fill: "salmon"}) : empty())(),
mountains = (() => {
const data = [
{
shapes: [[{x:0,y:20},{x:3,y:40},{x:6,y:20}], [{x:3,y:40},{x:6,y:20}]],
shades: ["#828ba4", "#6c758b"]
},
{
shapes: [[{x:4,y:20},{x:6,y:50},{x:8,y:10}], [{x:6,y:50},{x:8,y:10}]],
shades: ["#6c758b", "#686c7c"]
},
{
shapes: [[{x:9,y:10},{x:12,y:62.5},{x:16,y:10}], [{x:12,y:62.5},{x:16,y:10}]],
shades: ["#ffffff", "#dddddd"]
},
{
shapes: [[{x:8,y:10},{x:12,y:60},{x:17,y:10}], [{x:12,y:60},{x:17,y:10}]],
shades: ["#686c7c", "#626977"]
},
{
shapes: [[{x:7,y:10},{x:10,y:50},{x:13,y:10}], [{x:10,y:50},{x:13,y:10}]],
shades: ["#79829b", "#6c758b"]
},
{
shapes: [[{x:10,y:10},{x:15,y:40},{x:19,y:0}], [{x:15,y:40},{x:19,y:0}]],
shades: ["#7e8a9d", "#6c758b"]
}
];
return () => data.flatMap(m => m.shapes.map((s, i) => Plot.areaY(s, {x:"x", y:"y", fill:m.shades[i]})));
})(),
mask = (() => {
if (day) return empty;
let fill = "orange", opacity = 0.2;
if (twilight) {
fill = "black";
opacity = 0.4;
}
return () => Plot.rect([{x1: X.min, y1: Y.min, x2: X.max, y2: Y.max}], {x1: "x1", y1: "y1", x2: "x2", y2: "y2", fill: fill, opacity: opacity});
})(),
land = (() => {
const data = [
...Array(20).fill().map((_, i) => ({x: i, y: 20 - i + Math.random(), layer: 0, fill: "#8a3700"})),
...Array(15).fill().map((_, i) => ({x: 5 + i, y: i / 2, layer: 1, fill: "#b96b31"})),
];
return () => Plot.areaY(data, Plot.stackY({x: "x", y: "y", curve: "natural", fill: "fill"}));
})(),
hills = (() => {
const data = [
{x:0,y:35,l:0}, {x:3, y: 25, l:0}, {x:7, y: 10, l:0},
{x:3,y:0,l:1}, {x:8, y:22, l:1}, {x:12, y: 5, l:1},
{x:6,y:0,l:2}, {x:12, y:20, l:2}, {x:16, y: 7, l:2},
{x:10,y:0,l:3}, {x:14.5, y:16, l:3}, {x:19, y: 0, l:3},
{x:12,y:5,l:0}, {x:16, y:20, l:0}, {x:19, y: 23, l:0}
];
const colors = ["#007f5f", "#80b918", "#bfd200", "#55a630"]
return () => Plot.areaY(data, Plot.stackY({x: "x", y: "y", fill: d => colors[d.l], curve: "natural"}));
})();
class Scene {
constructor() {
this.clouds = new CloudManager(sunset);
this.water = new WaterManager();
this.stars = new StarManager();
this.shootingStar = new ShootingStar();
this.airplane = new Airplane();
this.frameId;
}
start() {
const
that = this,
div = document.createElement("div");
update();
animate();
return div;
function animate(ts) {
if (move(ts)) update();
that.frameId = requestAnimationFrame(animate);
}
function move(ts) {
return that.water.animate(ts)
|| !twilight && that.clouds.animate(ts)
|| twilight && (that.stars.animate(ts) | that.shootingStar.animate(ts) | that.airplane.animate(ts));
}
function update() {
if (div.children.length) div.children[0].remove();
div.append(that.render());
}
}
dispose() {
cancelAnimationFrame(this.frameId);
}
render() {
return Plot.plot({
width: width,
height: height,
x: {
axis: null
},
y: {
axis: null,
domain: [Y.min, Y.max]
},
color: { type: "identity" },
marks: [
sky(),
twilight ? this.stars.render() : empty(),
twilight ? this.shootingStar.render() : empty(),
twilight ? this.airplane.render() : empty(),
sun(),
mountains(),
hills(),
land(),
this.water.render(),
!twilight ? this.clouds.render() : empty(),
mask()
]
});
}
}
return new Scene();
}