import { FontLoader } from 'three/addons/loaders/FontLoader.js';
import { TextGeometry } from 'three/addons/geometries/TextGeometry.js';
-function getRandomWord() {
- let words = [
- "Alpaca", "Bat", "Butterfly", "Deer", "Duck", "Elk", "Giraffe", "Horse",
- "Lynx", "Llama", "Owl", "Ocelot", "Pig", "Sheep", "Tapir", "Walrus",
- ];
- return words[Math.floor(Math.random() * words.length)];
+function getRandomWord(game) {
+ return game.assets.words[Math.floor(Math.random() * game.assets.words.length)];
}
function easeInOut(val) {
function loadAllAssets(game, renderProgressCallback) {
game.assets = {};
+ game.assets.words = [
+ "Alpaca", "Bat", "Butterfly", "Deer", "Duck", "Elk", "Giraffe", "Horse",
+ "Lynx", "Llama", "Owl", "Ocelot", "Pig", "Sheep", "Tapir", "Walrus",
+ ];
return new Promise((resolve, reject) => {
let todoList = {
'audio/music.ogg': 1636930,
renderer.setAnimationLoop(() => { animate(game, renderer, scene); });
}
+function prepareWordMesh(game, word) {
+ while(word.children.length > 0) {
+ word.remove(word.children[0]);
+ }
+ if(game.settings['graphics'] <= 2 && !game.settings['highcontrast']) {
+ for(let letter of word.text) {
+ let geometry = game.assets.fonts.geometry[letter];
+ let material = game.view.materials.letter;
+ if(geometry.customMaterial) {
+ material = geometry.customMaterial;
+ }
+ let mesh = new THREE.Mesh(geometry, material);
+ // We wrap each letter in a surrounding group in order to move the center point
+ // from the corner of the letter to its center. This makes rotations easier.
+ let container = new THREE.Group();
+ mesh.position.set(-game.assets.fonts.geometry[letter].dx, -game.assets.fonts.geometry[letter].dy, 0);
+ container.add(mesh);
+ word.add(container);
+ }
+ } else if(game.settings['graphics'] == 3 || game.settings['highcontrast']) {
+ let mesh = new THREE.Mesh(Object.values(game.assets.fonts.geometry)[0], game.view.materials.letter);
+ word.add(mesh);
+ }
+}
+
function reset(game) {
game.ui.reachedEnd = false;
if(game.settings['graphics'] <= 2 && !game.settings['highcontrast']) {
}
game.objects.words = [];
game.objects.words.collectedCount = 0;
- game.assets.fonts.geometry = {};
- let letterMaterial = new THREE.MeshStandardMaterial({
- color: 0xffffff,
- emissive: 0x606060,
- roughness: 0.4,
- metalness: 1,
- });
const interWordDistance = new THREE.Vector3();
let placementSuccess;
for(let i = 0; i < 100; i++) {
let randomCameraX = game.courseRadius * Math.sin(angleInCourse * 2 * Math.PI);
let randomCameraY = game.courseRadius * -Math.cos(angleInCourse * 2 * Math.PI);
let word = new THREE.Group();
- word.text = getRandomWord();
- for(let letter of word.text) {
- if(!(letter in game.assets.fonts.geometry)) {
- game.assets.fonts.geometry[letter] = new TextGeometry(letter, {
- font: game.assets.fonts.cookie,
- size: 0.2,
- depth: 0.03,
- curveSegments: 2,
- bevelEnabled: false,
- });
- game.assets.fonts.geometry[letter].computeBoundingBox();
- let bbox = game.assets.fonts.geometry[letter].boundingBox;
- // Add these to local 0,0 later to get the letter's center rotation point
- game.assets.fonts.geometry[letter].dx = (bbox.max.x - bbox.min.x) / 2;
- game.assets.fonts.geometry[letter].dy = (bbox.max.y - bbox.min.y) / 2;
- }
- let mesh = new THREE.Mesh(game.assets.fonts.geometry[letter], letterMaterial);
- let container = new THREE.Group();
- mesh.position.set(-game.assets.fonts.geometry[letter].dx, -game.assets.fonts.geometry[letter].dy, 0);
- container.add(mesh);
- word.add(container);
- }
+ word.text = getRandomWord(game);
+ prepareWordMesh(game, word);
word.randomAnimOffset = Math.random();
const vFOV = THREE.MathUtils.degToRad(game.view.camera.fov);
let attempts = 0;
let collectionAnimationDuration = 1.0;
for(let j = 0; j < word.children.length; j++) {
let letter = word.children[j];
- let wordProgress = j / word.children.length;
let animationProgress = (((game.timeProgress + 5 * word.randomAnimOffset) % 5) / 5 + j / 37) % 1;
x = game.objects.feather.scale.x * 0.5 * Math.cos(animationProgress * 1 * Math.PI * 2);
y = 0.2 * Math.sin(animationProgress * 7 * Math.PI * 2);
game.objects.feather.twistSpeed = Math.min(0.13, game.objects.feather.twistSpeed) * game.settings['difficulty']['speed'] / 100;
game.objects.feather.rotation.x = (game.objects.feather.rotation.x + game.objects.feather.twistSpeed) % (2 * Math.PI);
- const collectedScale = lerp(0.6, 0.3, 1 - Math.pow(1 - game.objects.words.collectedCount / game.objects.words.length, 2));
+ let collectedScale = 0.0;
+ if(!game.settings['highcontrast'] && game.settings['graphics'] <= 2) {
+ collectedScale = lerp(0.6, 0.3, 1 - Math.pow(1 - game.objects.words.collectedCount / game.objects.words.length, 2));
+ } else if(!game.settings['highcontrast'] && game.settings['graphics'] == 3) {
+ collectedScale = 0.3;
+ }
const collectingRadius = - 0.5 + 1.5 * game.settings['difficulty']['collectingradius'];
for(let i = 0; i < game.objects.words.length; i++) {
let word = game.objects.words[i];
word.collected = game.view.clock.getElapsedTime();
game.objects.words.collectedCount += 1;
}
+ if(word.parent != game.view.scene) {
+ // All that happens in here is the positional animation for the word, which
+ // we can skip if it is no longer visible.
+ continue;
+ }
let x, y, z;
let collectionAnimationDuration = 1.0;
for(let j = 0; j < word.children.length; j++) {
game.var.notCollectedPos.set(0, 0, 0);
game.var.collectedPos.set(0, 0, 0);
let letter = word.children[j];
- let wordProgress = j / word.children.length;
let animationProgress = (((game.timeProgress + 5 * word.randomAnimOffset) % 5) / 5 + j / 37) % 1;
if(!word.collected || game.view.clock.getElapsedTime() - word.collected <= collectionAnimationDuration) {
- const wordAnimationRadius = 0.2;
- x = word.position.x + wordAnimationRadius * Math.cos(animationProgress * 5 * Math.PI * 2);
- y = word.position.y + wordAnimationRadius * Math.sin(animationProgress * 4 * Math.PI * 2);
- z = wordAnimationRadius * Math.sin(animationProgress * 6 * Math.PI * 2);
+ x = word.position.x;
+ y = word.position.y;
+ z = 0;
+ if(game.settings['graphics'] <= 2 && !game.settings['highcontrast']) {
+ const wordAnimationRadius = 0.2;
+ x += wordAnimationRadius * Math.cos(animationProgress * 5 * Math.PI * 2);
+ y += wordAnimationRadius * Math.sin(animationProgress * 4 * Math.PI * 2);
+ z += wordAnimationRadius * Math.sin(animationProgress * 6 * Math.PI * 2);
+ }
game.var.notCollectedPos.set(x, y, z);
}
if(word.collected) {
- x = game.objects.feather.scale.x * 0.5 * Math.cos(animationProgress * 1 * Math.PI * 2);
- y = 0.2 * Math.sin(animationProgress * 7 * Math.PI * 2);
- z = 0.2 * Math.cos(animationProgress * 7 * Math.PI * 2);
- x = x * Math.cos(game.objects.feather.rotation.z) - y * Math.sin(game.objects.feather.rotation.z);
- y = y * Math.cos(game.objects.feather.rotation.z) + x * Math.sin(game.objects.feather.rotation.z);
- x += game.objects.feather.position.x;
- y += game.objects.feather.position.y;
- z += game.objects.feather.position.z;
+ if(!game.settings['highcontrast']) {
+ x = game.objects.feather.scale.x * 0.5 * Math.cos(animationProgress * 1 * Math.PI * 2);
+ y = 0.2 * Math.sin(animationProgress * 7 * Math.PI * 2);
+ z = 0.2 * Math.cos(animationProgress * 7 * Math.PI * 2);
+ x = x * Math.cos(game.objects.feather.rotation.z) - y * Math.sin(game.objects.feather.rotation.z);
+ y = y * Math.cos(game.objects.feather.rotation.z) + x * Math.sin(game.objects.feather.rotation.z);
+ x += game.objects.feather.position.x;
+ y += game.objects.feather.position.y;
+ z += game.objects.feather.position.z;
+ } else {
+ x = game.objects.feather.position.x;
+ y = game.objects.feather.position.y;
+ z = game.objects.feather.position.z;
+ }
game.var.collectedPos.set(x, y, z);
}
if(game.var.notCollectedPos.length() > 0 && game.var.collectedPos.length() > 0) {
letter.position.set(game.var.notCollectedPos.x, game.var.notCollectedPos.y, game.var.notCollectedPos.z);
} else if(game.var.collectedPos.length() > 0) {
letter.position.set(game.var.collectedPos.x, game.var.collectedPos.y, game.var.collectedPos.z);
+ if(game.settings['highcontrast']) {
+ // Special case because in high contrast mode, collected words vanish entirely.
+ game.view.scene.remove(word);
+ }
}
letter.position.sub(word.position);
- let rotation = (game.timeProgress * 3) % (2 * Math.PI);
- letter.rotation.set(rotation + j, rotation + 2*j, rotation + 3*j);
+ if(!game.settings['highcontrast']) {
+ let rotation = (game.timeProgress * 3 + 2 * Math.PI * word.randomAnimOffset) % (2 * Math.PI);
+ letter.rotation.set(rotation + j, rotation + 2*j, rotation + 3*j);
+ }
}
}
game.objects.backdrop.material.dispose();
game.objects.backdrop.geometry.dispose();
}
- game.view.materials = {};
+ if(game.assets.fonts.geometry) {
+ for(let geom of Object.values(game.assets.fonts.geometry)) {
+ if(geom.customMaterial) {
+ geom.customMaterial.dispose();
+ }
+ geom.dispose();
+ }
+ delete game.assets.fonts.geometry;
+ }
+ if(game.view.materials) {
+ for(let material of Object.values(game.view.materials)) {
+ material.dispose();
+ }
+ delete game.view.materials;
+ }
+ game.view.materials = {};
if(!game.settings['highcontrast']) {
let cloudShaders;
let cloudGeometry = new THREE.PlaneGeometry(1, 200 / 350);
textureVariantSuffix = 'a';
}
}
+ game.view.materials.letter = new THREE.MeshStandardMaterial({
+ color: 0xffffff,
+ emissive: 0x606060,
+ roughness: 0.4,
+ metalness: 1,
+ });
if(game.settings['graphics'] <= 2) {
+ game.assets.fonts.geometry = {};
+ for(let letter of [...new Set(game.assets.words.join(''))]) {
+ if(game.settings['graphics'] == 1) {
+ game.assets.fonts.geometry[letter] = new TextGeometry(letter, {
+ font: game.assets.fonts.cookie,
+ size: 0.2,
+ depth: 0.03,
+ curveSegments: 2,
+ bevelEnabled: false,
+ });
+ game.assets.fonts.geometry[letter].computeBoundingBox();
+ let bbox = game.assets.fonts.geometry[letter].boundingBox;
+ // Add these to local 0,0 later to get the letter's center rotation point
+ game.assets.fonts.geometry[letter].dx = (bbox.max.x - bbox.min.x) / 2;
+ game.assets.fonts.geometry[letter].dy = (bbox.max.y - bbox.min.y) / 2;
+ } else {
+ let letterCanvas = document.createElement('canvas');
+ letterCanvas.width = 64;
+ letterCanvas.height = 64;
+ let letterCanvasContext = letterCanvas.getContext('2d');
+ letterCanvasContext.font = '60px Cookie';
+ letterCanvasContext.fillStyle = '#000';
+ letterCanvasContext.fillRect(0, 0, letterCanvas.width, letterCanvas.height);
+ letterCanvasContext.fillStyle = '#fff';
+ let bbox = letterCanvasContext.measureText(letter);
+ let vOffset = bbox.actualBoundingBoxAscent - 0.5 * (bbox.actualBoundingBoxAscent + bbox.actualBoundingBoxDescent);
+ letterCanvasContext.fillText(letter, Math.round((letterCanvas.width - bbox.width) / 2), (letterCanvas.height / 2) + vOffset);
+ let alphaMap = new THREE.CanvasTexture(letterCanvas);
+ alphaMap.needsUpdate = true;
+ let letterMaterial = new THREE.MeshStandardMaterial({
+ color: game.view.materials.letter.color,
+ emissive: 0x303030,
+ roughness: game.view.materials.letter.roughness,
+ metalness: game.view.materials.letter.metalness,
+ side: THREE.DoubleSide,
+ alphaMap: alphaMap,
+ transparent: true,
+ });
+ game.assets.fonts.geometry[letter] = new THREE.PlaneGeometry(0.3, 0.3);
+ game.assets.fonts.geometry[letter].dx = 0;
+ game.assets.fonts.geometry[letter].dy = 0;
+ game.assets.fonts.geometry[letter].customMaterial = letterMaterial;
+ }
+ }
let numClouds = 300;
if(game.settings['graphics'] == 2) {
numClouds = 75;
game.objects.clouds.add(cloud);
}
} else {
+ const minimalLetterGeometry = new THREE.BoxGeometry(0.2, 0.2, 0.2);
+ minimalLetterGeometry.dx = 0;
+ minimalLetterGeometry.dy = 0;
+ game.assets.fonts.geometry = {};
+ for(let letter of [...new Set(game.assets.words.join(''))]) {
+ game.assets.fonts.geometry[letter] = minimalLetterGeometry;
+ }
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);
game.objects.backdrop = new THREE.Mesh(new THREE.PlaneGeometry(350, 350), game.view.materials['cloud0' + textureVariantSuffix]);
game.objects.backdrop.position.setZ(-100);
} else {
+ game.view.materials.letter = new THREE.MeshStandardMaterial({
+ color: 0x00ff00,
+ emissive: 0x00ff00,
+ });
+ const highcontrastLetterGeometry = new THREE.SphereGeometry(0.1, 16, 16);
+ highcontrastLetterGeometry.dx = 0;
+ highcontrastLetterGeometry.dy = 0;
+ game.assets.fonts.geometry = {};
+ for(let letter of [...new Set(game.assets.words.join(''))]) {
+ game.assets.fonts.geometry[letter] = highcontrastLetterGeometry;
+ }
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);
}
+ if(game.objects.words) {
+ for(let word of game.objects.words) {
+ prepareWordMesh(game, word);
+ }
+ }
game.view.scene.add(game.objects.backdrop);
}