'textures/cloud5b.png': 2065,
'textures/cloud5c.png': 2066,
'textures/feather.png': 9196,
+ 'textures/highcontrast-backdrop.png': 500,
'textures/logo.png': 221524,
'textures/pinwheel.png': 904,
'textures/windowsill1.png': 483119,
if(segments[0] == 'textures') {
result.colorSpace = THREE.SRGBColorSpace;
result.magFilter = THREE.NearestFilter;
+ if(segments[1].split('.')[0] == 'highcontrast-backdrop') {
+ result.repeat = new THREE.Vector2(25, 25);
+ result.wrapS = THREE.RepeatWrapping;
+ result.wrapT = THREE.RepeatWrapping;
+ }
}
game.assets[segments[0]][segments[1].split('.')[0]] = result;
progress[todo] = todoList[todo];
game.objects.pinwheel.cameraY = 9999;
scene.add(game.objects.pinwheel);
- game.objects.clouds = [];
- const cloudGeometry = new THREE.PlaneGeometry(1, 200 / 350);
- const cloudShaders = {
- vertexShader:
- `
- precision highp float;
- precision highp int;
- varying vec2 vUv;
- void main() {
- vUv = uv;
- gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
- }
- `,
- fragmentShader:
- `
- precision mediump float;
- uniform sampler2D texture1;
- uniform sampler2D texture2;
- uniform sampler2D texture3;
- uniform float lerp;
- varying vec2 vUv;
- void main() {
- if(lerp > 1.0) {
- vec4 col1 = texture2D(texture2, vUv);
- vec4 col2 = texture2D(texture3, vUv);
- gl_FragColor = mix(col1, col2, lerp - 1.0);
- } else {
- vec4 col1 = texture2D(texture1, vUv);
- vec4 col2 = texture2D(texture2, vUv);
- gl_FragColor = mix(col1, col2, lerp);
- }
- // I don't know how GLSL works: why do I need to do this to match the textures?
- gl_FragColor = mix(gl_FragColor, vec4(1.0, 1.0, 1.0, gl_FragColor.a), 0.5);
- }
- `
- };
- game.view.materials = {};
- for(let i = 0; i < 6; i++) {
- game.view.materials['cloud' + i] = new THREE.ShaderMaterial({
- uniforms: THREE.UniformsUtils.merge([{
- texture1: null,
- texture2: null,
- texture3: null,
- lerp: null,
- }]),
- ...cloudShaders,
- });
- game.view.materials['cloud' + i].uniforms.texture1.value = game.assets.textures['cloud' + i + 'a'];
- game.view.materials['cloud' + i].uniforms.texture2.value = game.assets.textures['cloud' + i + 'b'];
- game.view.materials['cloud' + i].uniforms.texture3.value = game.assets.textures['cloud' + i + 'c'];
- game.view.materials['cloud' + i].uniforms.lerp.value = 0.0;
- game.view.materials['cloud' + i].transparent = true;
- }
- game.objects.backdrop = new THREE.Mesh(new THREE.PlaneGeometry(350, 350), game.view.materials['cloud0']);
- game.objects.backdrop.position.setZ(-100);
- scene.add(game.objects.backdrop);
- for(let i = 0; i < 300; i++) {
- let randomAngle = Math.random() * 2 * Math.PI;
- let randomCameraX = game.courseRadius * Math.sin(randomAngle);
- let randomCameraY = game.courseRadius * Math.cos(randomAngle);
- let cloud = new THREE.Mesh(cloudGeometry, game.view.materials['cloud' + (i % 5 + 1)]);
- cloud.position.z = -10 - Math.round(Math.random() * 40);
- const vFOV = THREE.MathUtils.degToRad(game.view.camera.fov);
- cloud.maxCameraDistance = 2 * Math.tan(vFOV / 2) * Math.abs(cloud.position.z - game.view.camera.position.z);
- cloud.position.x = randomCameraX + cloud.maxCameraDistance * 2 * (Math.random() - 0.5);
- cloud.position.y = randomCameraY + cloud.maxCameraDistance * 2 * (Math.random() - 0.5);
- let scale = 20 + (Math.random() * 0.5 + 0.5) * Math.abs(cloud.position.z);
- cloud.scale.set(scale, scale, scale);
- scene.add(cloud);
- game.objects.clouds.push(cloud);
- }
-
const startingWindowsillMaterial = new THREE.MeshBasicMaterial({
map: game.assets.textures.windowsill1,
transparent: true,
game.view.camera.position.set(-5, -game.courseRadius, game.view.camera.position.z);
game.view.scene = scene;
+ createMeshes(game);
reset(game);
function pinwheelPositionUpdate(game, viewportX, viewportY) {
function reset(game) {
game.ui.reachedEnd = false;
- for(let i = 0; i < 6; i++) {
- game.view.materials['cloud' + i].uniforms.lerp.value = 0.0;
+ if(game.settings['graphics'] <= 2 && !game.settings['highcontrast']) {
+ for(let i = 0; i < 6; i++) {
+ game.view.materials['cloud' + i].uniforms.lerp.value = 0.0;
+ }
}
game.view.scene.add(game.objects.startingWindowsill);
game.view.scene.remove(game.objects.endingWindowsill);
if(game.ui.currentPage != 'gameplay') {
let cameraSwayFactor = 1;
+ if(game.settings['graphics'] == 3) {
+ cameraSwayFactor = 0;
+ }
let cameraX = -5;
if(game.ui.currentPage == 'title') {
if(!game.ui.reachedEnd) {
if(game.ui.reachedEnd) {
reset(game);
}
- cameraSwayFactor = 1 - (game.timeProgress / 8);
+ cameraSwayFactor = cameraSwayFactor * (1 - (game.timeProgress / 8));
cameraX = -5 + Math.pow(Math.max(0, game.timeProgress - 3) / 5, 1.6) * 5;
game.objects.feather.position.setX(-11.45 + 12 * Math.min(1, easeInOut(Math.max(0, game.timeProgress - 4) / 4)));
game.ui.moveToPage('gameplay', true);
}
} else if(game.ui.currentPage == 'endingcutscene') {
- cameraSwayFactor = game.timeProgress / 8;
+ cameraSwayFactor = cameraSwayFactor * game.timeProgress / 8;
cameraX = 5 - Math.pow(Math.max(0, 5 - game.timeProgress) / 5, 1.6) * 5;
game.objects.feather.rotation.z = -0.2;
game.objects.feather.position.setX(10.5 * Math.min(1, easeInOut(1 - Math.max(0, 4 - game.timeProgress) / 4)));
game.view.camera.position.x = game.courseRadius * Math.sin(angle);
game.view.camera.position.y = - game.courseRadius * Math.cos(angle);
- let sunsetValue = 2.0;
- if(!game.ui.reachedEnd) {
- sunsetValue = sunsetValue * easeInOut(Math.min(1, Math.max(0, ((game.timeProgress / game.timeTotal) - 0.3) / 0.6)));
- }
- for(let i = 0; i < 6; i++) {
- game.view.materials['cloud' + i].uniforms.lerp.value = sunsetValue;
+ if(!game.settings['highcontrast']) {
+ let sunsetValue = 2.0;
+ if(!game.ui.reachedEnd) {
+ sunsetValue = sunsetValue * easeInOut(Math.min(1, Math.max(0, ((game.timeProgress / game.timeTotal) - 0.3) / 0.6)));
+ }
+ if(game.settings['graphics'] <= 2) {
+ for(let i = 0; i < 6; i++) {
+ game.view.materials['cloud' + i].uniforms.lerp.value = sunsetValue;
+ }
+ } else {
+ let cloudMaterialVariant = null;
+ if(sunsetValue < 0.5 && !game.objects.clouds.children[0].material.name.endsWith('a')) {
+ cloudMaterialVariant = 'a';
+ } else if(sunsetValue >= 0.5 && sunsetValue < 1.5 && !game.objects.clouds.children[0].material.name.endsWith('b')) {
+ cloudMaterialVariant = 'b';
+ } else if(sunsetValue >= 1.5 && !game.objects.clouds.children[0].material.name.endsWith('c')) {
+ cloudMaterialVariant = 'c';
+ }
+ if(cloudMaterialVariant) {
+ for(let cloud of game.objects.clouds.children) {
+ cloud.material = game.view.materials[cloud.material.name.slice(0, -1) + cloudMaterialVariant];
+ }
+ game.objects.backdrop.material = game.view.materials['cloud0' + cloudMaterialVariant];
+ }
+ }
}
if(game.timeProgress / game.timeTotal > 0.5 && game.objects.startingWindowsill.parent == scene) {
}
}
+function createMeshes(game) {
+ if(game.objects.clouds && game.objects.clouds.parent == game.view.scene) {
+ game.view.scene.remove(game.objects.clouds);
+ if(game.objects.clouds.children.length > 0) {
+ game.objects.clouds.children[0].geometry.dispose();
+ }
+ for(let mKey in game.view.materials) {
+ game.view.materials[mKey].dispose();
+ }
+ delete game.objects.clouds;
+ }
+ if(game.objects.backdrop && game.objects.backdrop.parent == game.view.scene) {
+ game.view.scene.remove(game.objects.backdrop);
+ game.objects.backdrop.material.dispose();
+ game.objects.backdrop.geometry.dispose();
+ }
+ game.view.materials = {};
+
+ if(!game.settings['highcontrast']) {
+ let cloudShaders;
+ let cloudGeometry = new THREE.PlaneGeometry(1, 200 / 350);
+ if(game.settings['graphics'] <= 2) {
+ cloudShaders = {
+ vertexShader:
+ `
+ precision highp float;
+ precision highp int;
+ varying vec2 vUv;
+ void main() {
+ vUv = uv;
+ gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
+ }
+ `,
+ fragmentShader:
+ `
+ precision mediump float;
+ uniform sampler2D texture1;
+ uniform sampler2D texture2;
+ uniform sampler2D texture3;
+ uniform float lerp;
+ varying vec2 vUv;
+ void main() {
+ if(lerp > 1.0) {
+ vec4 col1 = texture2D(texture2, vUv);
+ vec4 col2 = texture2D(texture3, vUv);
+ gl_FragColor = mix(col1, col2, lerp - 1.0);
+ } else {
+ vec4 col1 = texture2D(texture1, vUv);
+ vec4 col2 = texture2D(texture2, vUv);
+ gl_FragColor = mix(col1, col2, lerp);
+ }
+ // I don't know how GLSL works: why do I need to do this to match the textures?
+ gl_FragColor = mix(gl_FragColor, vec4(1.0, 1.0, 1.0, gl_FragColor.a), 0.5);
+ }
+ `
+ };
+ }
+ for(let i = 0; i < 6; i++) {
+ if(game.settings['graphics'] <= 2) {
+ game.view.materials['cloud' + i] = new THREE.ShaderMaterial({
+ uniforms: THREE.UniformsUtils.merge([{
+ texture1: null,
+ texture2: null,
+ texture3: null,
+ lerp: null,
+ }]),
+ ...cloudShaders,
+ });
+ game.view.materials['cloud' + i].uniforms.texture1.value = game.assets.textures['cloud' + i + 'a'];
+ game.view.materials['cloud' + i].uniforms.texture2.value = game.assets.textures['cloud' + i + 'b'];
+ game.view.materials['cloud' + i].uniforms.texture3.value = game.assets.textures['cloud' + i + 'c'];
+ game.view.materials['cloud' + i].uniforms.lerp.value = 0.0;
+ game.view.materials['cloud' + i].transparent = true;
+ } else {
+ for(let variant of ['a', 'b', 'c']) {
+ game.view.materials['cloud' + i + variant] = new THREE.MeshBasicMaterial({
+ map: game.assets.textures['cloud' + i + variant],
+ transparent: true,
+ alphaTest: 0.5,
+ });
+ game.view.materials['cloud' + i + variant].name = 'cloud' + i + variant;
+ }
+ }
+ }
+ game.objects.clouds = new THREE.Group();
+ let textureVariantSuffix = '';
+ if(game.settings['graphics'] > 2) {
+ textureVariantSuffix = 'a';
+ }
+ if(game.settings['graphics'] <= 2) {
+ let numClouds = 300;
+ if(game.settings['graphics'] == 2) {
+ numClouds = 75;
+ }
+ for(let i = 0; i < numClouds; i++) {
+ let randomAngle = Math.random() * 2 * Math.PI;
+ let randomCameraX = game.courseRadius * Math.sin(randomAngle);
+ let randomCameraY = game.courseRadius * Math.cos(randomAngle);
+ let cloud = new THREE.Mesh(cloudGeometry, game.view.materials['cloud' + (i % 5 + 1) + textureVariantSuffix]);
+ cloud.position.z = -10 - Math.round(Math.random() * 40);
+ const vFOV = THREE.MathUtils.degToRad(game.view.camera.fov);
+ let maxCameraDistance = 2 * Math.tan(vFOV / 2) * Math.abs(cloud.position.z - game.view.camera.position.z);
+ cloud.position.x = randomCameraX + maxCameraDistance * 2 * (Math.random() - 0.5);
+ cloud.position.y = randomCameraY + maxCameraDistance * 2 * (Math.random() - 0.5);
+ let scale = 20 + (Math.random() * 0.5 + 0.5) * Math.abs(cloud.position.z);
+ cloud.scale.set(scale, scale, scale);
+ game.objects.clouds.add(cloud);
+ }
+ } else {
+ for(let r = 0; r < game.courseRadius / 3; r++) {
+ let angle = THREE.MathUtils.degToRad(360 * 3 * r / game.courseRadius);
+ let cameraX = game.courseRadius * Math.sin(angle);
+ let cameraY = game.courseRadius * Math.cos(angle);
+ const vFOV = THREE.MathUtils.degToRad(game.view.camera.fov);
+ let z = -15;
+ let maxCameraDistance = Math.tan(vFOV / 2) * Math.abs(z - game.view.camera.position.z);
+ let axis = [-1, 0, 1];
+ if(r % 2 == 0) {
+ axis = [-0.5, 0.5];
+ }
+ for(let step of axis) {
+ let shape = 1 + Math.floor(Math.random() * 5);
+ let cloud = new THREE.Mesh(cloudGeometry, game.view.materials['cloud' + shape + textureVariantSuffix]);
+ cloud.position.x = cameraX + step * maxCameraDistance * Math.sin(angle);
+ cloud.position.y = cameraY + step * maxCameraDistance * Math.cos(angle);
+ cloud.position.z = z;
+ let scale = 15 + 0.5 * Math.abs(cloud.position.z);
+ cloud.scale.set(scale, scale, scale);
+ game.objects.clouds.add(cloud);
+ }
+ }
+ }
+ game.view.scene.add(game.objects.clouds);
+ game.objects.backdrop = new THREE.Mesh(new THREE.PlaneGeometry(350, 350), game.view.materials['cloud0' + textureVariantSuffix]);
+ game.objects.backdrop.position.setZ(-100);
+ } else {
+ const highContrastBackdropMaterial = new THREE.MeshBasicMaterial({
+ map: game.assets.textures['highcontrast-backdrop'],
+ });
+ game.objects.backdrop = new THREE.Mesh(new THREE.PlaneGeometry(150, 150), highContrastBackdropMaterial);
+ game.objects.backdrop.position.setZ(-10);
+ }
+ game.view.scene.add(game.objects.backdrop);
+}
+
window['game'] = {
state: 'loadingAssets',
ui: {
});
});
game.ui.root.querySelectorAll('.options .controls input, .options .graphics input, .options .feather input, .options .accessibility input, .options .accessibility select').forEach((elem) => {
- elem.addEventListener('change', () => applySettings(game));
+ elem.addEventListener('change', () => {
+ applySettings(game);
+ if(elem.value == 'highcontrast' || elem.name == 'upInTheAirGame-graphics') {
+ createMeshes(game);
+ }
+ });
});
game.ui.root.querySelectorAll('.ui-page .audio input[type=range]').forEach((elem) => {
elem.addEventListener('input', (e) => {