265 lines
5.6 KiB
JavaScript
265 lines
5.6 KiB
JavaScript
const { cos, PI, ceil, random } = Math;
|
|
|
|
const deg = degrees => {
|
|
return PI / 180 * degrees;
|
|
};
|
|
|
|
const maybeNegative = number => {
|
|
return random() >= .5 ? -number : number;
|
|
};
|
|
|
|
const times = (howMany, what) => {
|
|
return [...Array(howMany).keys()].map(what);
|
|
};
|
|
|
|
const query = (selector) =>
|
|
selector[0] === '#' ?
|
|
document.getElementById(selector.slice(1)) :
|
|
document.querySelectorAll(selector);
|
|
|
|
const getPlace = (placeList, index) => {
|
|
const { name, country, lat } = placeList[index];
|
|
const nameAndCountry = [name, country].filter(Boolean);
|
|
const hemisphere = lat > 0 ? 'N' : lat < 0 ? 'S' : '';
|
|
return {
|
|
name: `${nameAndCountry.join(', ')} (${lat}°${hemisphere})`,
|
|
lat };
|
|
|
|
};
|
|
|
|
const calculateMonth = (monthList, time) => {
|
|
const monthLength = 1 / 6;
|
|
const monthIndex = time / monthLength;
|
|
return monthList[ceil(monthIndex) % 12];
|
|
};
|
|
|
|
const places = [
|
|
{ name: 'South Pole', country: null, lat: -90 },
|
|
{ name: 'Vostok', country: 'Antarctica', lat: -75 },
|
|
{ name: 'Falkland Islands', country: 'UK', lat: -51 },
|
|
{ name: 'Christchurch', country: 'New Zealand', lat: -43 },
|
|
{ name: 'São Paulo', country: 'Brazil', lat: -23 },
|
|
{ name: 'Kampala', country: 'Uganda', lat: 0 },
|
|
{ name: 'Dubai', country: 'UAE', lat: 23 },
|
|
{ name: 'Istanbul', country: 'Turkey', lat: 41 },
|
|
{ name: 'Helsinki', country: 'Finland', lat: 60 },
|
|
{ name: 'Tromsø', country: 'Norway', lat: 69 },
|
|
{ name: 'Alert', country: 'Canada', lat: 82 },
|
|
{ name: 'North Pole', country: null, lat: 90 }];
|
|
|
|
|
|
const months = [
|
|
'June',
|
|
'July',
|
|
'August',
|
|
'September',
|
|
'October',
|
|
'November',
|
|
'December',
|
|
'January',
|
|
'February',
|
|
'March',
|
|
'April',
|
|
'May'];
|
|
|
|
|
|
const daySpeeds = {
|
|
paused: 0,
|
|
playing: 0.01,
|
|
rewinding: -0.01,
|
|
forwarding: 0.1 };
|
|
|
|
|
|
const maxAscension = .23;
|
|
|
|
const state = {
|
|
place: 8,
|
|
mode: 'playing',
|
|
season: 0 // 0 = june, 1 = december
|
|
};
|
|
|
|
const drawing = new Zdog.Illustration({
|
|
element: '#zdog-canvas',
|
|
dragRotate: true,
|
|
zoom: 500,
|
|
rotate: { x: -deg(25) } });
|
|
|
|
|
|
const ground = new Zdog.Cylinder({
|
|
addTo: drawing,
|
|
diameter: 1,
|
|
length: .05,
|
|
stroke: false,
|
|
color: 'forestgreen',
|
|
frontFace: 'yellowgreen',
|
|
backface: 'rgb(30, 100, 10)',
|
|
rotate: { x: deg(90) } });
|
|
|
|
|
|
const clouds = [];
|
|
|
|
const addRandomCloud = (parent, x) => {
|
|
const color = 'rgba(230, 240, 255, .2)';
|
|
const z = maybeNegative(random() / 2);
|
|
clouds.push(new Zdog.Box({
|
|
addTo: parent,
|
|
width: .001,
|
|
height: .0025,
|
|
depth: .001,
|
|
stroke: .1,
|
|
color,
|
|
translate: {
|
|
x,
|
|
y: -.3,
|
|
z } }));
|
|
|
|
|
|
|
|
clouds.push(new Zdog.Box({
|
|
addTo: parent,
|
|
width: .001,
|
|
height: .003,
|
|
depth: .001,
|
|
stroke: .15,
|
|
color,
|
|
translate: {
|
|
x: x + .1,
|
|
y: -.3,
|
|
z } }));
|
|
|
|
|
|
|
|
if (random() > .75) {
|
|
clouds.push(new Zdog.Box({
|
|
addTo: parent,
|
|
width: .001,
|
|
height: .0025,
|
|
depth: .001,
|
|
stroke: .1,
|
|
color,
|
|
translate: {
|
|
x: x + .2,
|
|
y: -.3,
|
|
z } }));
|
|
|
|
|
|
if (random() > .5) {
|
|
clouds.push(new Zdog.Box({
|
|
addTo: parent,
|
|
width: .0005,
|
|
height: .0005,
|
|
depth: .0005,
|
|
stroke: .07,
|
|
color,
|
|
translate: {
|
|
x: x + .27,
|
|
y: -.3,
|
|
z } }));
|
|
|
|
|
|
}
|
|
}
|
|
};
|
|
|
|
const sunTiltPlane = new Zdog.Anchor({
|
|
addTo: drawing });
|
|
|
|
|
|
const sunOrbitPlane = new Zdog.Anchor({
|
|
addTo: sunTiltPlane });
|
|
|
|
|
|
const sunAscensionPlane = new Zdog.Anchor({
|
|
addTo: sunOrbitPlane });
|
|
|
|
|
|
const sun = new Zdog.Shape({
|
|
addTo: sunAscensionPlane,
|
|
stroke: .1,
|
|
color: '#fd5',
|
|
translate: { y: -.5 } });
|
|
|
|
|
|
let cloudX = 0;
|
|
let cloudBoost = 2.5;
|
|
|
|
const loop = () => {
|
|
const speed = daySpeeds[state.mode];
|
|
const place = getPlace(places, state.place);
|
|
const tilt = deg(-place.lat);
|
|
if (sunTiltPlane.rotate.z !== tilt) {
|
|
sunTiltPlane.rotate.z += (tilt - sunTiltPlane.rotate.z) / 5;
|
|
}
|
|
sunOrbitPlane.rotate.x += speed;
|
|
sunAscensionPlane.translate.x =
|
|
cos(PI * state.season) * maxAscension;
|
|
|
|
state.season += speed / 180;
|
|
if (random() < speed * cloudBoost) {
|
|
cloudX += (.6 - cloudX) / 10;
|
|
cloudBoost -= (cloudBoost - .5) / 10;
|
|
addRandomCloud(drawing, cloudX);
|
|
}
|
|
clouds.forEach(cloud => {
|
|
if (cloud) {
|
|
cloud.translate.x -= speed / 10;
|
|
if (cloud.translate.x < -3) {
|
|
cloud.remove();
|
|
cloud = null;
|
|
}
|
|
}
|
|
});
|
|
query('#place').innerText = place.name;
|
|
const month = calculateMonth(months, state.season);
|
|
query('#month').innerText = month;
|
|
drawing.updateRenderGraph();
|
|
requestAnimationFrame(loop);
|
|
};
|
|
|
|
loop();
|
|
|
|
const onClickSouth = () => {
|
|
state.place--;
|
|
query('#north').removeAttribute('disabled');
|
|
if (!places[state.place - 1]) {
|
|
query('#south').setAttribute('disabled', true);
|
|
}
|
|
};
|
|
|
|
const onClickNorth = () => {
|
|
state.place++;
|
|
query('#south').removeAttribute('disabled');
|
|
if (!places[state.place + 1]) {
|
|
query('#north').setAttribute('disabled', true);
|
|
}
|
|
};
|
|
|
|
const playPause = () => {
|
|
if (state.mode !== 'paused') {
|
|
state.savedMode = state.mode;
|
|
state.mode = 'paused';
|
|
} else {
|
|
state.mode = state.savedMode;
|
|
}
|
|
query('#play').classList.toggle('hidden');
|
|
query('#pause').classList.toggle('hidden');
|
|
};
|
|
|
|
const toggleRewind = () => {
|
|
state.mode = state.mode !== 'rewinding' ?
|
|
'rewinding' :
|
|
'playing';
|
|
};
|
|
|
|
const toggleForward = () => {
|
|
state.mode = state.mode !== 'forwarding' ?
|
|
'forwarding' :
|
|
'playing';
|
|
};
|
|
|
|
query('#south').addEventListener('click', onClickSouth);
|
|
query('#north').addEventListener('click', onClickNorth);
|
|
query('#play').addEventListener('click', playPause);
|
|
query('#pause').addEventListener('click', playPause);
|
|
query('#rewind').addEventListener('click', toggleRewind);
|
|
query('#forward').addEventListener('click', toggleForward); |