Up-in-the-Air – commitdiff

You can use Git to clone the repository via the web URL. Download snapshot (zip)
High contrast mode and graphics quality respected by clouds and backdrop
authorJulian Fietkau <git@fietkau.software>
Tue, 24 Sep 2024 16:09:05 +0000 (18:09 +0200)
committerJulian Fietkau <git@fietkau.software>
Tue, 24 Sep 2024 16:09:05 +0000 (18:09 +0200)
main.js
textures/highcontrast-backdrop.png [new file with mode: 0644]

diff --git a/main.js b/main.js
index de926dba98c265bfc40b14cffebc649ba76119fa..e64028c9aebd5814e4d751172bf0453e9237ca9b 100644 (file)
--- a/main.js
+++ b/main.js
@@ -45,6 +45,7 @@ function loadAllAssets(game, renderProgressCallback) {
       '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,
@@ -70,6 +71,11 @@ function loadAllAssets(game, renderProgressCallback) {
         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];
@@ -164,78 +170,6 @@ function init(game, canvas) {
   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,
@@ -259,6 +193,7 @@ function init(game, canvas) {
   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) {
@@ -294,8 +229,10 @@ function init(game, canvas) {
 
 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);
@@ -405,6 +342,9 @@ function animate(game, renderer, scene) {
 
   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) {
@@ -417,7 +357,7 @@ function animate(game, renderer, scene) {
       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)));
@@ -430,7 +370,7 @@ function animate(game, renderer, scene) {
         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)));
@@ -497,12 +437,31 @@ function animate(game, renderer, scene) {
   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) {
@@ -736,6 +695,151 @@ function applySettings(game) {
   }
 }
 
+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: {
@@ -760,7 +864,12 @@ game.ui.root.querySelectorAll('button.goto').forEach((btn) => {
   });
 });
 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) => {
diff --git a/textures/highcontrast-backdrop.png b/textures/highcontrast-backdrop.png
new file mode 100644 (file)
index 0000000..74c4a1a
Binary files /dev/null and b/textures/highcontrast-backdrop.png differ