JackGreenEarth

joined 1 year ago
[–] JackGreenEarth@lemm.ee 6 points 18 hours ago (1 children)
[–] JackGreenEarth@lemm.ee 32 points 1 day ago (7 children)

Your point's ok, but your habit of speaking about yourself in the third person is really grating and it comes across as snobby.

[–] JackGreenEarth@lemm.ee 3 points 1 day ago (1 children)

Free speech and protection from censorship is the most important right to ensure democracy. Any erosion of that right is both bad in itself, as adolescents are kept in their real life bubble and not exposed to other viewpoints online - which was crucial for me personally - but it also paves the way for further erosion of that right. They'll keep pushing the age up and banning more information.

[–] JackGreenEarth@lemm.ee 8 points 1 day ago (2 children)

What's that weird thing in comics where random words are emboldened and italicised? It's quite disconcerting.

[–] JackGreenEarth@lemm.ee 13 points 1 day ago (1 children)

Tankie is a derogatory term for Marxist Leninists, people that call themselves communist but actually are in favour of dictatorships and oligarchies. The name come from the fact that they supported the tanks that were killing peaceful protesters in the Tianamen Square massacre in China. And asking a genuine question is fine, shouldn't judge people for that.

[–] JackGreenEarth@lemm.ee 5 points 1 day ago (2 children)

I hate to quote Glinda, but

I hope you're happy
I hope you're happy now
I hope you're happy now you hurt your cause forever
I hope you think you're clever

[–] JackGreenEarth@lemm.ee 8 points 1 day ago (8 children)

What does the K stand for?

[–] JackGreenEarth@lemm.ee 6 points 1 day ago

Sweden, I'm learning Swedish and its political and social system seem better than that of the UK, although everywhere is trending right.

[–] JackGreenEarth@lemm.ee 3 points 1 day ago (3 children)

What browser are you currently using? Is that Chrome? How did you get it working for that?

[–] JackGreenEarth@lemm.ee 20 points 2 days ago (1 children)

It means hi or hey

[–] JackGreenEarth@lemm.ee 24 points 3 days ago

Firefox for Android as it's one of the only mobile browsers with comprehensive addon support and helps to keep Google's WebKit/Blink monopoly at bay.

[–] JackGreenEarth@lemm.ee 6 points 3 days ago (1 children)

Pavlov was a primitive psychologist who investigated conditioning by conditioning his dogs to salivate whenever they heard the sound of a bell (since they had been trained to associate the bell with being given food)

 

cross-posted from: https://lemm.ee/post/46352654

I love the game civ6, that's why the problems with it make me troubled and I want to suggest improvements. I've played through multiple playthroughs and here are some issues that stand out to me.

TLDR: Civ6's gameplay forces you to play in a very imperialist, chauvinistic, racist, and religious way to win.

Problsms

'Barbarians' (indigenous or native people, obviously stereotyped as always warlike), the tech tree that forces you to discover things in the way the Europeans discovered them, not just making you unlock actually necessary technologies before you unlock new ones, even more so with the civic tree as any policy or government can be developed in isolation, it doesn't need the preceding civ6 ones, also you can't have a democracy from the beginning if you want what's best for your people. You have to conquer other continents displacing or killing the natives, and the way it frames certain governments such as communism is disingenuous when they're really describing authoritarianism, and making that misunderstanding more widespread.

My suggested solutions/improvements

Abolish the civic tree. Let you establish any government or any policy ( you can still have policy slots but it's the same across all governments) at any time without culture cost. Governments could affect things like the happiness and productivity of your people, but not more than that. Rename 'barbarians' to natives or indigenous people and create more peaceful options for interacting with them, don't make them aggressive by default. Encourage cooperation with civilisations on other continents (such as lasting trade agreements that you don't have to renew every 30 turns) rather than encouraging settling everywhere on the map. Allow individual cities to get upgrades that allows producing multiple things at once rather than the only way to do so being creating multiple cities. Allow agnosticism or atheism as a 'religion' option and rename religion to 'philosophy' or 'ideology' or something like that. Having real historical cultures and leaders on a generated world doesn't really make sense so there should be fictional or generated civilisations and leaders as options too.

I don't mind if they keep the victory conditions they've got, scientific, religious (although ideological is a better term), military, although not cultural. If people want to play as murderous colonial maniacs they should be allowed to, they just shouldn't be forced into it and should have more peaceful options to play. The scientific victory should be collaborative, with multiple civilisations working together to advance scientific knowledge and improve human conditions (although the current goal of space travel is overly simplistic). Convincing the world of your ideology isn't a bad measure for success either, and conquering the world military is a kind of success, if not a very moral one. Culture victory is just a thinly veiled way of saying that only European culture is good and the way of proving your culture is best is attracting tourists, which is again overly simplistic.

 

I love the game civ6, that's why the problems with it make me troubled and I want to suggest improvements. I've played through multiple playthroughs and here are some issues that stand out to me.

TLDR: Civ6's gameplay forces you to play in a very imperialist, chauvinistic, racist, and religious way to win.

Problsms

'Barbarians' (indigenous or native people, obviously stereotyped as always warlike), the tech tree that forces you to discover things in the way the Europeans discovered them, not just making you unlock actually necessary technologies before you unlock new ones, even more so with the civic tree as any policy or government can be developed in isolation, it doesn't need the preceding civ6 ones, also you can't have a democracy from the beginning if you want what's best for your people. You have to conquer other continents displacing or killing the natives, and the way it frames certain governments such as communism is disingenuous when they're really describing authoritarianism, and making that misunderstanding more widespread.

My suggested solutions/improvements

Abolish the civic tree. Let you establish any government or any policy ( you can still have policy slots but it's the same across all governments) at any time without culture cost. Governments could affect things like the happiness and productivity of your people, but not more than that. Rename 'barbarians' to natives or indigenous people and create more peaceful options for interacting with them, don't make them aggressive by default. Encourage cooperation with civilisations on other continents (such as lasting trade agreements that you don't have to renew every 30 turns) rather than encouraging settling everywhere on the map. Allow individual cities to get upgrades that allows producing multiple things at once rather than the only way to do so being creating multiple cities. Allow agnosticism or atheism as a 'religion' option and rename religion to 'philosophy' or 'ideology' or something like that. Having real historical cultures and leaders on a generated world doesn't really make sense so there should be fictional or generated civilisations and leaders as options too.

I don't mind if they keep the victory conditions they've got, scientific, religious (although ideological is a better term), military, although not cultural. If people want to play as murderous colonial maniacs they should be allowed to, they just shouldn't be forced into it and should have more peaceful options to play. The scientific victory should be collaborative, with multiple civilisations working together to advance scientific knowledge and improve human conditions (although the current goal of space travel is overly simplistic). Convincing the world of your ideology isn't a bad measure for success either, and conquering the world military is a kind of success, if not a very moral one. Culture victory is just a thinly veiled way of saying that only European culture is good and the way of proving your culture is best is attracting tourists, which is again overly simplistic.

 

cross-posted from: https://lemm.ee/post/46067136

I'm designing a webapp that is supposed to be an AR environment on your phone, to be viewed with something like Google Cardboard, but I am having an issue that the segmentPointer object that is meant to appear when clicking on an object is not.

I've checked the geometry displays correctly in a sandbox, and when I change it to a 3d object rather than shapeGeometry it does display, but I cannot figure out why it is not displaying how I want it to.

The project is at https://voxelverse.jackgreenearth.org, and the code is quite long, but it is here to read in its totality below as it might need the whole context to discover the error. I've tried myself looking through the code, and I've tried searching the web and asking LLMs, but I couldn't figure it out, so please help me, fellow humans.

Tap for code

"use strict";

import \* as THREE from 'three';

import {GLTFLoader} from 'three/addons/loaders/GLTFLoader.js';

\


const loader = new GLTFLoader();

const textureLoader = new THREE.TextureLoader();

const manager = THREE.DefaultLoadingManager;

\


// Basic functions

\


function ls(id) {

return(localStorage.getItem(id));

};

\


function setLs(id, val) {

localStorage.setItem(id, val);

};

\


function byId(id) {

return(document.getElementById(id));

};

\


function bySel(sel) {

return(document.querySelector(sel));

};

\


function byClass(id) {

return(document.getElementsByClassName(id));

};

\


function toTitleCase(str) {

return str.replace(

/\w\S\*/g,

function(txt) {

return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();

}

);

};

\


function randInt(max) {

return Math.floor(Math.random() \* (max));

};

\


function getRandomFloat(min, max, decimals) {

return(parseFloat((Math.random() \* (max - min) + min).toFixed(decimals)));

};

\


function confine(value, min, max) {

if(value < min) {

return(min);

} else if(value > max) {

return(max);

} else {

return(value);

};

};

\


function wrap(value, min, max) {

const range = max - min;

\


if(value < min) {

return(wrap(value + range, min, max));

} else if(value > max) {

return(wrap(value - range, min, max));

} else {

return(value);

};

};

\


function removeFromArray(array, forDeletion) {

return(array.filter(item => !forDeletion.includes(item)));

};

\


function radToDeg(radians) {

return radians \* (180 / PI);

}

\


function range(start, stop, step = 1) {

if (stop === undefined) {

stop = start;

start = 0

}

return Array.from({ length: (stop - start) / step }, (\_, i) => start + (i \* step));

}

\


function between(variable, min, max, inclusive='min') {

switch(inclusive) {

case 'none':

return((variable > min) && (variable < max));

break;

case 'both':

return((variable >= min) && (variable <= max));

break;

case 'min':

return((variable >= min) && (variable < max));

break;

case 'max':

return((variable > min) && (variable <= max));

break;

}

}

\


function download(data, filename, type) {

var file = new Blob(\[data], {type: type});

if (window\.navigator.msSaveOrOpenBlob) // IE10+

window\.navigator.msSaveOrOpenBlob(file, filename);

else { // Others

var a = document.createElement("a"),

url = URL.createObjectURL(file);

a.href = url;

a.download = filename;

document.body.appendChild(a);

a.click();

setTimeout(function() {

document.body.removeChild(a);

window\.URL.revokeObjectURL(url);

}, 0);

};

};

\


function log(text) {

console.log(text);

};

\


function distance2d(x1, y1, x2, y2) {

return(Math.sqrt(

(Math.abs(x1 - x2) \*\* 2) +

(Math.abs(y1 - y2) \*\* 2)

));

};

\


function distance3d(p1 = new THREE.Vector3(0, 0, 0), p2 = new THREE.Vector3(0, 0, 0)) {

return(Math.sqrt((distance2d(p1.x, p1.y, p2.x, p2.y) \*\* 2) + (Math.abs(p1.z - p2.z) \*\* 2)));

};

\


let totalElementsToLoad = 0;

let numberOfElementsLoaded = 0;

\


function onAllElementsLoaded() {

\


}

\


function load(path, type, functionOnLoad) {

totalElementsToLoad += 1;

\


if(type == 'html') {

fetch(path)

.then(response => response.text())

.then(html => {

let doc = new DOMParser().parseFromString(html, "text/html");

\


functionOnLoad(doc);

\


// If all elements to load have been loaded, execute the relevant function

numberOfElementsLoaded += 1;

if(numberOfElementsLoaded == totalElementsToLoad) {

onAllElementsLoaded();

}

})

.catch(error => {

console.error(error);

});

} else if(type == 'json') {

fetch(path)

.then(response => response.json()) // parse the response as JSON

.then(json => {

functionOnLoad(json);

\


// If all elements to load have been loaded, execute the relevant function

numberOfElementsLoaded += 1;

if(numberOfElementsLoaded == totalElementsToLoad) {

onAllElementsLoaded();

}

})

.catch(error => {

console.error(error);

});

}

}

\


// Setup

\


const PI = 3.1415926535897932384626433832795028841971;

\


// Objects

\


let orientation = {

'absolute': false,

'alpha': 0,

'beta': 0,

'gamma': 0

}

\


// vars

const fps = 60;

\


let keysDown = \[];

let pointerPosition = {'x': 0, 'y': 0, 'positions': \[{'clientX': 0, 'clientY': 0}], 'type': 'mouse'};

\


// Camera

let cameraRotation = new THREE.Euler(0, 0, 0, 'YXZ');

let cameraTargetRotation = {'x': 0, 'y': 0, 'z': 0};

const cameraRotationSensitivity = 0.002;

\


// Other variables

let logicInterval;

\


// Load default settings

let defaultSettings;

\


load("/assets/json/default-settings.json", 'json', function(defset) {

defaultSettings = defset;

\


// Create custom settings

if(!Object.keys(localStorage).includes('settings')) {

setLs('settings', JSON.stringify({}));

};

\


onSettingsLoad();

});

\


function settingURL(url, addValue=true) {

return('children/' + url.split('/').join('/children/') + (addValue ? '/value' : ''));

}

\


function customiseSetting(url, value) {

url = settingURL(url).split('/');

\


let newSettings;

\


function recursiveSet(object, list, index, setTo) {

// If the current component is the last one, assign the value

if(index == list.length - 1) {

object\[list\[index]] = setTo;

return(object);

} else {

// Check if it already contains the value

if(object.hasOwnProperty(list\[index])) {

object\[list\[index]] = recursiveSet(object\[list\[index]], list, index + 1, setTo);

} else {

object\[list\[index]] = recursiveSet({}, list, index + 1, setTo);

}

return(object);

}

};

\


newSettings = recursiveSet(JSON.parse(ls('settings')), url, 0, value);

\


setLs('settings', JSON.stringify(newSettings));

}

\


function getSetting(url, addValue) {

url = settingURL(url, addValue).split('/');

\


function recursiveGet(object, list, index) {

// If the current component is the last one, return the value

if (index == list.length - 1) {

return object\[list\[index]];

} else {

// Check if it contains the value

if (object.hasOwnProperty(list\[index])) {

return recursiveGet(object\[list\[index]], list, index + 1);

} else {

return null; // No such setting

}

}

}

\


// Try to find it in local settings first, otherwise get it from defaultSettings

const localGet = recursiveGet(JSON.parse(ls('settings')), url, 0);

if(localGet == null) {

return(recursiveGet(defaultSettings, url, 0));

} else {

return(localGet);

}

}

\


// First, lets define some functions

// Rendering functions

\


// Thanks, https\://discourse.threejs.org/t/roundedrectangle-squircle/28645!

function roundRectangleGeometry(w, h, r, s) { // width, height, radius corner, smoothness

// helper const's

const wi = w / 2 - r; // inner width

const hi = h / 2 - r; // inner height

const w2 = w / 2; // half width

const h2 = h / 2; // half height

const ul = r / w; // u left

const ur = ( w - r ) / w; // u right

const vl = r / h; // v low

const vh = ( h - r ) / h; // v high

let positions = \[

-wi, -h2, 0, wi, -h2, 0, wi, h2, 0,

-wi, -h2, 0, wi, h2, 0, -wi, h2, 0,

-w2, -hi, 0, -wi, -hi, 0, -wi, hi, 0,

-w2, -hi, 0, -wi, hi, 0, -w2, hi, 0,

wi, -hi, 0, w2, -hi, 0, w2, hi, 0,

wi, -hi, 0, w2, hi, 0, wi, hi, 0

];

let uvs = \[

ul, 0, ur, 0, ur, 1,

ul, 0, ur, 1, ul, 1,

0, vl, ul, vl, ul, vh,

0, vl, ul, vh, 0, vh,

ur, vl, 1, vl, 1, vh,

ur, vl, 1, vh, ur, vh

];

let phia = 0;

let phib, xc, yc, uc, vc, cosa, sina, cosb, sinb;

for (let i = 0; i < s \* 4; i ++) {

phib = Math.PI \* 2 \* ( i + 1 ) / ( 4 \* s );

cosa = Math.cos( phia );

sina = Math.sin( phia );

cosb = Math.cos( phib );

sinb = Math.sin( phib );

xc = i < s || i >= 3 \* s ? wi : - wi;

yc = i < 2 \* s ? hi : -hi;

positions.push( xc, yc, 0, xc + r \* cosa, yc + r \* sina, 0, xc + r \* cosb, yc + r \* sinb, 0 );

uc = i < s || i >= 3 \* s ? ur : ul;

vc = i < 2 \* s ? vh : vl;

uvs.push( uc, vc, uc + ul \* cosa, vc + vl \* sina, uc + ul \* cosb, vc + vl \* sinb );

phia = phib;

}

const geometry = new THREE.BufferGeometry( );

geometry.setAttribute( 'position', new THREE.BufferAttribute( new Float32Array( positions ), 3 ) );

geometry.setAttribute( 'uv', new THREE.BufferAttribute( new Float32Array( uvs ), 2 ) );

return geometry;

}

\


// Render

function render() {

requestAnimationFrame(render);

leftRenderer.render(scene, leftCamera);

rightRenderer.render(scene, rightCamera);

\


framesSoFar++;

};

\


// Functions

function setCameraRotation() {

// Calculate drag

cameraRotation.x = Number((cameraRotation.x + ((cameraTargetRotation.x - cameraRotation.x) / getSetting('Input/Mouse/Camera Rotation Drag'))).toFixed(5));

cameraRotation.y = Number((cameraRotation.y + ((cameraTargetRotation.y - cameraRotation.y) / getSetting('Input/Mouse/Camera Rotation Drag'))).toFixed(5));

cameraRotation.z = Number((cameraRotation.z + ((cameraTargetRotation.z - cameraRotation.z) / getSetting('Input/Mouse/Camera Rotation Drag'))).toFixed(5));

// Update cameras

for(let camera of \[leftCamera, rightCamera]) {

camera.rotation.set(cameraRotation.x, cameraRotation.y, cameraRotation.z, 'YXZ');

}

\


const eyeGap = getSetting('Quick Settings/Eye Gap');

\


// Set camera positions

leftCamera.position.x = -1 \* eyeGap \* Math.sin(cameraRotation.y);

leftCamera.position.z = -1 \* eyeGap \* Math.cos(cameraRotation.y);

rightCamera.position.x = eyeGap \* Math.sin(cameraRotation.y);

rightCamera.position.z = eyeGap \* Math.cos(cameraRotation.y);

\


byId('camera-target-rot-x').innerHTML = cameraTargetRotation.x.toFixed(2);

byId('camera-target-rot-y').innerHTML = cameraTargetRotation.y.toFixed(2);

byId('camera-target-rot-z').innerHTML = cameraTargetRotation.z.toFixed(2);

byId('camera-rot-x').innerHTML = cameraRotation.x.toFixed(2);

byId('camera-rot-y').innerHTML = cameraRotation.y.toFixed(2);

byId('camera-rot-z').innerHTML = cameraRotation.z.toFixed(2);

\


byId('camera-left-rot-x').innerHTML = leftCamera.rotation.x.toFixed(2);

byId('camera-left-rot-y').innerHTML = leftCamera.rotation.y.toFixed(2);

byId('camera-left-rot-z').innerHTML = leftCamera.rotation.z.toFixed(2);

}

\


function takeScreenshot() {

downloadCanvasImage(document.getElementById('game-canvas'), gameName + ' screenshot');

sendAlert('Screenshot Taken!', 'tick');

};

\


function takePanorama() {

const canvas = document.getElementById('game-canvas');

const height = canvas.height;

const width = canvas.width \* (360 / (camera.fov \* camera.aspect));

let newCanvas = document.createElement('canvas');

newCanvas.height = height;

newCanvas.width = width;

newCanvas.style.display = 'none';

let context = newCanvas.getContext("2d");

document.body.appendChild(newCanvas);

for(let x = 0; x < width; x++) {

// Rotate

cameraRotation.y += ((2 \* PI) / width);

let calculatedRotation = rotationToAbsolute(playerPosition, cameraRotation);

camera.rotation.set(calculatedRotation.x, calculatedRotation.y, calculatedRotation.z, 'YXZ');

renderer.render(scene, camera);

const gl = renderer.getContext();

// Get canvas data

const pixelData = new Uint8ClampedArray(1 \* height \* 4);

const reversedPixelData = new Uint8ClampedArray(1 \* height \* 4);

gl.readPixels((canvas.width / 2), 0, 1, height, gl.RGBA, gl.UNSIGNED\_BYTE, pixelData);

for (let i = 0; i < height; i++) {

for (let j = 0; j < 4; j++) {

reversedPixelData\[i\*4 + j] = pixelData\[(height - i - 1)\*4 + j];

};

};

const imageData = new ImageData(reversedPixelData, 1, height);

context.putImageData(imageData, x, 0);

};

downloadCanvasImage(newCanvas, gameName + ' panorama');

newCanvas.remove();

sendAlert('Panoramic screenshot taken!', 'tick');

};

\


function setRotation(object, rotation) {

object.rotation.set(rotation.x, rotation.y, rotation.z);

};

\


function downloadCanvasImage(canvas, name) {

let canvasImage = canvas.toDataURL('image/png');

// this can be used to download any image from webpage to local disk

let xhr = new XMLHttpRequest();

xhr.responseType = 'blob';

xhr.onload = function () {

let a = document.createElement('a');

a.href = window\.URL.createObjectURL(xhr.response);

a.download = name;

a.style.display = 'none';

document.body.appendChild(a);

a.click();

a.remove();

};

xhr.open('GET', canvasImage); // This is to download the canvas image

xhr.send();

};

\


function xyToRealPosRot(x, y, distance) {

let realX, realY, realZ, rotX, rotY, rotZ;

\


// Position is an object {x: x, y: y} x determines which face it will be on horizontally, and y determines if it will be on the top or the bottom

// Beyond 400, x position wraps

x = wrap(x, 0, 400);

log('x before: ' + x)

const horizontalFace = (x / 100) % 4;

//rotY = (x / 400) \* (1) // horizontalFace);

\


// The top of the screen is y 100, the bottom is y -100, and the horizontals are between -50 and 50

realY = confine(y, -100, 100);

\


// Calculate real position

const unit = getSetting('Display/UI/Distance') / 50;

\


let forward = getSetting('Display/UI/Distance');

\


const bevel = getSetting('Display/UI/Bevel');

\


rotX = 0;

\


// If it is horizontal...

if(between(y, -50 + bevel, 50 - bevel)) {

realY = y;

rotX = 0;

} else if(y < -50 - bevel) {

// If it is on the lower face

realY = -50;

forward = (y + 100) \* unit;

rotX = -(PI / 2);

} else if(y >= 50 + bevel) {

// If it is on the upper face

realY = 50;

forward = (y - 100) \* unit;

//side = unit \* (((x - 50) % 100) + 50);

rotX = (PI / 2);

} else if(between(y, -50 - bevel, -50 + bevel)) {

// If it is on the lower bevel

realY = -50 - ((y + 50) / 2);

rotX = (PI / 4);

} else if(between(y, 50 - bevel, 50 + bevel)) {

// If it is on the upper bevel

realY = 50 + ((y - 50) / 2) ;

rotX = -(PI / 4);

}

\


realY = realY \* unit;

\


let flip = false;

\


/\*if(

(horizontalFace >= 0 && horizontalFace < 0.5) ||

(horizontalFace >= 1.5 && horizontalFace < 2.5) ||

(horizontalFace >= 3.5 && horizontalFace < 4)

) {

flip = true;

}\*/

\


let angle = (x / 400) \* (PI \* 2);

realX = Math.sin(angle) \* forward;

realZ = Math.cos(angle) \* forward;

rotY = angle;

log('rot y: ' + rotY)

\


log({

'x': realX,

'y': realY,

'forward': forward,

})

\


// Take distance into account

realX \*= distance;

realY \*= distance;

realZ \*= distance;

\


return({

'position': new THREE.Vector3(realX, realY, realZ),

'rotation': new THREE.Euler(rotX, rotY, rotZ, 'YXZ'),

'flip': flip

});

}

\


function addWidget({

name = '',

position = {'x': 0, 'y': 0},

rotation = {'x': 0, 'y': 0, 'z': 0},

distance = 1,

size = {'x': 10, 'y': 10},

radius = 3,

shape = 'rRect',

background = '#000000',

opacity,

textStyle = {

'align': 'center',

'weight': 0, // Range is 0 to 10

'font': 'DINRoundPro,arial,sans-serif',

'color': '#b0b0b0',

'vertical-align': 'center',

'font-size': 1 // Uses the same sizing system as the rest of the UI, so one unit of text is also one unit of object

},

textContent = '',

onclick = function() {},

onlongpress = function() {},

onhover = function() {},

onhoverexit = function() {},

ontruehover = function() {}

}) {

const realPosRot = xyToRealPosRot(position.x, position.y, distance);

log(realPosRot)

const realPos = realPosRot.position;

let realRot = realPosRot.rotation;

\


realRot.x += rotation.x;

realRot.y += rotation.y;

realRot.z = rotation.z;

\


// Calculate real size

const unit = getSetting('Display/UI/Distance') / 100;

\


let width = unit \* size.x;

let height = unit \* size.y;

radius \*= unit;

const scale = getSetting('Display/UI/Scale/General');

width \*= scale;

height \*= scale;

radius \*= scale;

\


// Set mesh geometry

let geometry;

switch(shape) {

case 'rRect':

geometry = roundRectangleGeometry(width, height, radius, 10);

break;

case 'rect':

geometry = new THREE.PlaneGeometry(width, height);

break;

case 'circle':

geometry = new THREE.CircleGeometry((width + height) / 2, 32);

break;

}

let material;

\


if(opacity == undefined) {

opacity = 1;

}

\


if(textContent == '') {

if(background\[0] == '/') {

loadTexture(background, function(texture) {

material = new THREE.MeshBasicMaterial({

map: texture,

side: THREE.DoubleSide,

opacity: opacity,

transparent: true

});

onTextureLoad(material);

})

} else {

material = new THREE.MeshBasicMaterial({

color: background,

side: THREE.DoubleSide,

opacity: opacity,

transparent: true

});

onTextureLoad(material);

}

} else {

function prepareText(canvas) {

// Proceed to prepare the canvas with the text

ctx.font = \`${textStyle\["font-size"]}em ${textStyle\["font"]}\`;

ctx.textAlign = textStyle\["align"];

ctx.fillStyle = textStyle\["color"];

ctx.fillText(textContent, 0, 0);

// Compose the text onto the background

const composedTexture = new THREE.CanvasTexture(canvas);

\


// Generate the material

material = new THREE.MeshBasicMaterial({

map: composedTexture,

side: THREE.DoubleSide,

transparent: true,

alphaTest: 0.5

});

\


onTextureLoad(material);

}

\


// Initialize tmpcanvas only when needed

const tmpcanvas = document.createElement('canvas');

tmpcanvas.width = width;

tmpcanvas.height = height;

const ctx = tmpcanvas.getContext('2d');

\
\


// Fill the background first

if (background\[0] == '/') {

loadTexture(background, function(texture) {

ctx.fillStyle = texture;

ctx.fillRect(0, 0, width, height);

\


prepareText(tmpcanvas);

})

} else {

ctx.fillStyle = background;

ctx.fillRect(0, 0, width, height);

\


prepareText(tmpcanvas);

}

}

function onTextureLoad(material) {

// Create a mesh with the geometry and the material

let mesh = new THREE.Mesh(geometry, material);

\


mesh.name = name;

\


mesh.position.set(realPos.x, realPos.y, realPos.z );

mesh.rotation.set(realRot.x, realRot.y, realRot.z);

\


if(realPosRot.flip) {

mesh.scale.x = -1;

}

\


mesh.onclick = onclick;

mesh.onlongpress = onlongpress;

mesh.onhoverexit = onhoverexit;

mesh.ontruehover = ontruehover;

mesh.onchover = onhover;

\


scene.add(mesh);

};

}

\


function transitionWidget(name, property, newProperty, time, condition) {

if(condition != null) {

}

}

\


// three.js Scene setup

const scene = new THREE.Scene();

\


// three functions

\


function loadTexture(path, onload) {

textureLoader.load(path, function (texture) {

onload(texture);

}, undefined, function (error) {

console.error(error);

});

};

\


// Define objects to make them global, they will mostly be only added to the scene when settings are loaded

let sun;

let wallpaper;

let cameraStream;

let pointer;

\


let pointerMaterial = new THREE.MeshBasicMaterial({

color: "hsl(0, 100%, 50%)",

side: THREE.DoubleSide

});

\


let segmentShape = new THREE.Shape();

let segmentGeometry = new THREE.ShapeGeometry(segmentShape);

let segmentPointer = new THREE.Mesh(segmentGeometry, pointerMaterial);

segmentPointer.name = 'segmentPointer';

\


function setSegmentPointer(angle = 0, radius = 0.1, rotation = new THREE.Euler(0, 0, 0), clockwise=true) {

let oldGeometry = segmentPointer.geometry;

\


let segmentShape = new THREE.Shape();

segmentShape.moveTo(0, 0);

segmentShape.arc(0, 0, radius, 0, angle, clockwise);

segmentShape.lineTo(0, 0);

\


let extrudeSettings = {

steps: 1,

depth: 0.1,

bevelEnabled: false

};

\


let segmentGeometry = new THREE.ExtrudeGeometry(segmentShape, extrudeSettings);

segmentPointer.geometry = segmentGeometry;

\


oldGeometry.dispose();

\


segmentPointer.rotation.set(rotation);

}

\


// Camera stuff

let cameraViewDistance;

\


// Setup cameras

let leftCamera;

let rightCamera;

let leftRenderer;

let rightRenderer;

\


function setRendererSize() {

for(let renderer of \[leftRenderer, rightRenderer]) {

let canvas = renderer.domElement;

renderer.setSize(

canvas.offsetWidth \* getSetting('Display/Anti-alias'),

canvas.offsetHeight \* getSetting('Display/Anti-alias'),

false

);

}

}

\


function updateCameraAspectRatio() {-0.2

for(let camera of \[leftCamera, rightCamera]) {

let canvas = leftRenderer.domElement;

camera.aspect = canvas.offsetWidth / canvas.offsetHeight;

camera.updateProjectionMatrix();

}

}

\


// temp

function startAssistant() {

log('assisstant')

}

\


// When settings are loaded, start settings stuff up

function onSettingsLoad() {

// Add sun

sun = new THREE.PointLight(0xffffff, getSetting('Quick Settings/Brightness'));

scene.add(sun);

\


// Pointers

pointer = new THREE.Mesh(new THREE.IcosahedronGeometry(getSetting('Display/UI/Pointer/Size'), 1), pointerMaterial);

pointer.name = 'pointer';

scene.add(pointer);

\


pointerMaterial = new THREE.MeshBasicMaterial(

{color: "hsl(" + (getSetting('Quick Settings/Theme Hue') + getSetting('Display/UI/Pointer/Hue Shift')) + ", 100%, 50%)"}

);

pointer.material = pointerMaterial;

segmentPointer.material = pointerMaterial;

\


// Add wallpaper

let wallpaperURL;

if(getSetting('Display/UI/Wallpaper/Use Direct Link') == true) {

wallpaperURL = getSetting('Display/UI/Wallpaper/Direct Link');

} else {

wallpaperURL = getSetting('Display/UI/Wallpaper/Wallpapers Folder') + '/' + getSetting('Display/UI/Wallpaper/Wallpaper');

}

\


loadTexture(wallpaperURL, function(texture) {

let material = new THREE.MeshStandardMaterial({

map: texture,

side: THREE.BackSide,

transparent: true,

opacity: getSetting('Display/UI/Wallpaper/Opacity')

});

\


wallpaper = new THREE.Mesh(

new THREE.IcosahedronGeometry(

getSetting('Display/UI/Distance') \* getSetting('Display/UI/Wallpaper/Distance') \* getSetting('Display/UI/Testing Size Multiplier'), 4),

material

);

wallpaper.scale.x = -1;

wallpaper.name = "wallpaper";

scene.add(wallpaper);

});

\


// Setup cameras

cameraViewDistance = getSetting('Display/UI/Distance') \* getSetting('Display/UI/Testing Size Multiplier') \* 2; // Keep this down to destroy lag

leftCamera = new THREE.PerspectiveCamera(80, 1, 0.001, cameraViewDistance);

rightCamera = new THREE.PerspectiveCamera(80, 1, 0.001, cameraViewDistance);

\


// Setup renderers

leftRenderer = new THREE.WebGLRenderer({canvas: byId('left-canvas'), antialias: true, logarithmicDepthBuffer: true, preserveDrawingBuffer: true, alpha: true});

rightRenderer = new THREE.WebGLRenderer({canvas: byId('right-canvas'), antialias: true, logarithmicDepthBuffer: true, preserveDrawingBuffer: true, alpha: true});

\


updateCameraAspectRatio();

setRendererSize();

\


window\.addEventListener('resize', function() {

updateCameraAspectRatio();

setRendererSize();

});

\


// Setup control center

const baseY = getSetting('Display/Control Center/Switch To Bottom') ? -100 : 100;

const backgroundFolder = getSetting('Display/Control Center/Icon Folder');

function createTileGrid(scale, x, y, farness, tiles, name) {

let counter = 0;

log(tiles)

addWidget({

position: {'x': x, 'y': baseY + y},

size: {'x': 3 \* scale, 'y': 3 \* scale},

background: "hsl(" + getSetting('Quick Settings/Theme Hue') + ", 50%, 80%)",

name: name + ' background',

radius: 0.5 \* scale,

onhover: openTileGroup(name)

});

for(let widgetY = 1; widgetY >= -1; widgetY--) {

for(let widgetX = -1; widgetX <=1; widgetX++) {

if(counter < tiles.length) {

if(tiles\[counter].folder) {

createTileGrid(scale / 3, (widgetX \* scale), (widgetY \* scale), farness \* 0.99, tiles\[counter].children);

} else {

log('scale' + scale)

addWidget({

position: {'x': x + (widgetX \* scale), 'y': baseY + y + (widgetY \* scale)},

size: {'x': scale, 'y': scale},

background: backgroundFolder + '/' + tiles\[counter].icon + '.svg',

name: tiles\[counter].name,

group: name,

radius: scale / 3,

distance: farness \* 0.99

});

log('added widget control center')

};

} else {

break;

};

counter++;

};

if(counter >= tiles.length) {

break;

};

};

};

\


createTileGrid(

getSetting('Display/UI/Scale/Control Center') \* 1.5,

0,

baseY,

1,

getSetting('Display/Control Center/Tiles'),

getSetting('Display/Control Center/Tiles', false)\['name']

);

// Quick function

let quickFunction = getSetting('Display/Control Center/Quick Function', false);

\


addWidget({

position: {'x': 0, 'y': getSetting('Display/Control Center/Switch To Bottom') ? 100 : -100},

background: '/assets/images/icons/control\_center/' + quickFunction.icon + '.svg',

name: quickFunction.name,

onclick: quickFunction.onclick

});

\


// testing

addWidget({

position: {'x': 0, 'y': -50},

background: '/assets/images/icons/control\_center/torch.svg',

name: "torch"

});

addWidget({

position: {'x': 200, 'y': 10},

background: '/assets/images/icons/control\_center/screencast.svg',

name: "screencast"

});

for(let i of range(16)) {

addWidget({

position: {'x': i \* 25, 'y': 0},

background: 'hsl(' + getSetting('Quick Settings/Theme Hue') + ', 100%, ' + ((i / 16) \* 100) + '%)',

name: "test" + i,

textContent: '',//i.toString()

'onclick': function() {

log('click' + i);

},

'onhover': function() {

log('hover' + i);

}

});

}

};

\


function updateSetting(url, value) {

customiseSetting(url, value);

\


switch(url) {

case 'Display/UI/Wallpaper/Opacity':

wallpaper.material.opacity = value;

break;

};

};

\


// Start

\


// Setup the camera stream

function setupCameraStream() {

function handleSuccess(stream) {

cameraStream = document.createElement('video');

cameraStream.style.transform = 'rotate(270deg)';

cameraStream.srcObject = stream;

cameraStream.play();

\


let texture = new THREE.VideoTexture(cameraStream);

texture.minFilter = THREE.LinearFilter;

texture.magFilter = THREE.LinearFilter;

scene.background = texture;

\


customiseSetting('Display/UI/Wallpaper/Opacity', 0); // Temporary until GUI settings are introduced

}

\


function handleError(error) {

// Set wallpaper opacity to 1

updateSetting('Display/UI/Wallpaper/Opacity', 1);

\


log('Unable to access the camera/webcam.');

}

\


navigator.mediaDevices.getUserMedia({video: {facingMode: "environment"}})

.then(handleSuccess)

.catch(function(error) {

if (error.name === 'OverconstrainedError') {

// Fallback to default video settings

navigator.mediaDevices.getUserMedia({video: true})

.then(handleSuccess)

.catch(handleError);

} else {

// Handle other errors

handleError(error);

}

});

};

\


// Fullscreen and pointer lock, request fullscreen mode for the element

function openFullscreen(elem, then) {

if (elem.requestFullscreen) {

elem.requestFullscreen().then(then);

} else if (elem.webkitRequestFullscreen) { /\* Safari \*/

elem.webkitRequestFullscreen().then(then);

} else if (elem.msRequestFullscreen) { /\* IE11 \*/

elem.msRequestFullscreen().then(then);

}

}

// Request pointer lock

function requestPointerLock(myTargetElement) {

const promise = myTargetElement.requestPointerLock({

unadjustedMovement: true,

});

\


if (!promise) {

log("disabling mouse acceleration is not supported");

return;

}

\


return promise

.then(() => log("pointer is locked"))

.catch((error) => {

if (error.name === "NotSupportedError") {

// Some platforms may not support unadjusted movement.

// You can request again a regular pointer lock.

return myTargetElement.requestPointerLock();

}

});

}

\


function lockPointer() {

requestPointerLock(byId('body'));

document.addEventListener("pointerlockchange", lockChangeAlert, false);

};

\


function lockChangeAlert() {

if (document.pointerLockElement === byId('body')) {

document.addEventListener("mousemove", updatePosition, false);

} else {

document.removeEventListener("mousemove", updatePosition, false);

}

}

\


function updatePosition(e) {

onLockedMouseMove(e.movementX, e.movementY);

};

\


function fullscreenAndPointerLock() {

openFullscreen(byId('body'), function() {

lockPointer();

});

}

\


function permission() {

// Check if the device supports deviceorientation and requestPermission

if (typeof(DeviceMotionEvent) !== "undefined" && typeof(DeviceMotionEvent.requestPermission) === "function") {

// Request permission

DeviceMotionEvent.requestPermission()

.then(response => {

// Check the response

if (response == "granted") {};

})

.catch(console.error); // Handle errors

} else {

// Device does not support deviceorientation

log("DeviceOrientationEvent is not defined");

}

}

\


async function keepScreenAwake() {

// Create a reference for the Wake Lock.

let wakeLock = null;

\


// create an async function to request a wake lock

try {

wakeLock = await navigator.wakeLock.request("screen");

log("Wake Lock is active!");

} catch (err) {

// The Wake Lock request has failed - usually system related, such as battery.

log(\`${err.name}, ${err.message}\`);

}

}

\


let framesSoFar = 0;

\


function startFPSCount() {

byId('logic-fps').innerHTML = fps.toString();

\


let renderFPSInterval = setInterval(function() {

byId('render-fps').innerHTML = framesSoFar.toString();

framesSoFar = 0;

}, 1000);

}

\


function start() {

byId('loading-screen').style.display = 'none';

setupCameraStream();

startLogic();

startFPSCount();

render();

permission();

fullscreenAndPointerLock();

keepScreenAwake();

\


byId('left-canvas').addEventListener('click', fullscreenAndPointerLock);

byId('right-canvas').addEventListener('click', fullscreenAndPointerLock);

};

\


// Loading

byId('loading-bar').style.display = 'block';

\


manager.onProgress = function (url, itemsLoaded, itemsTotal) {

//log('Loading file: ' + url + '.\nLoaded ' + itemsLoaded + ' of ' + itemsTotal + ' files.');

\


byId('loading-bar-fg').style.setProperty('--size', ((itemsLoaded / itemsTotal) \* 100) + '%');

};

\


manager.onError = function (url) {

log('There was an error loading ' + url);

};

\


manager.onLoad = function ( ) {

setTimeout(function() {

byId('loading-bar').style.display = 'none';

byId('play').style.display = 'block';

}, 500);

byId('play').addEventListener('click', start);

};

\


function openTileGroup(group) {

}

\


// Logic

let pointerRaycast = new THREE.Raycaster();

let previousIntersection = pointerRaycast.intersectObjects(scene.children);

let clicking = false;

\


function logic() {

// Set camera rotation

if(cameraTargetRotation.x != cameraRotation.x || cameraTargetRotation.y != cameraRotation.y) {

setCameraRotation();

};

\


// Update pointer

pointerRaycast.set(

new THREE.Vector3(0, 0, 0),

leftCamera.getWorldDirection(new THREE.Vector3())

);

\


// Check if the pointer is itersecting with any object

\


const intersections = pointerRaycast.intersectObjects(scene.children);

if (intersections.length > 0) {

for(let intersection of intersections) {

// If it's intersecting with itself, don't do anything

if(intersection.object.name == 'pointer' || intersection.object.name == 'segmentPointer') {

return;

} else {

// If it's intersecting with an object, copy that intersection's position, and start to click

const point = intersections\[0].point;

pointer.position.copy(point);

\


// Truehover

if(Object.keys(intersection.object).includes('ontruehover')) {

// Prevent hover being continuously trigggered

if(previousIntersection.uuid != intersections\[0].uuid) {

intersection.object.ontruehover();

}

}

log('truehover')

\


// Start click after grace period, if object is clickable

if(

Object.keys(intersection.object).includes('onclick') ||

Object.keys(intersection.object).includes('onhover') ||

Object.keys(intersection.object).includes('onhoverexit') ||

Object.keys(intersection.object).includes('onlongpress')

) {

let gracePeriod = setTimeout(function() {

// onhover

if(Object.keys(intersection.object).includes('onhover')) {

intersection.object.onhover();

}

log('hover')

\


// Start click

if(Object.keys(intersection.object).includes('onclick') && (!clicking)) {

clicking = true;

\


let fullness = 0;

\


// Manage pointers

scene.add(segmentPointer);

segmentPointer.position.copy(pointer);

scene.remove(pointer);

let startClick = setInterval(function() {

fullness += (PI \* 2) / (fps \* getSetting('Input/Eye Click/Duration/Pre-click duration'));

setSegmentPointer(

fullness,

getSetting('Display/UI/Pointer/Size') \* getSetting('Display/UI/Pointer/Clicking Size'),

intersection.object.rotation

);

\


byId('pointer-angle').innerHTML = fullness.toFixed(4);

\


// On forfeit

let forfeitDistance = distance3d(point, pointerRaycast.intersectObjects(scene.children)\[0].point);

if(forfeitDistance > getSetting('Input/Eye Click/Movement limit')) {

log('forfeit ' + forfeitDistance)

clearInterval(startClick);

afterClick();

}

\


if(fullness >= PI \* 2) {

log('click')

intersection.object.onclick();

clearInterval(startClick);

\


if(Object.keys(intersection.object).includes('onlongpress')) {

// Start longpress

fullness = 0;

let startLongPress = setInterval(function() {

fullness += (PI \* 2) / (fps \* getSetting('Input/Eye Click/Duration/Long-press duration'));

setSegmentPointer(

fullness,

getSetting('Display/UI/Pointer/Size') \* getSetting('Display/UI/Pointer/Clicking Size'),

intersection.object.rotation,

false

);

byId('pointer-angle').innerHTML = fullness.toFixed(4);

\


let forfeitDistance = distance3d(point, pointerRaycast.intersectObjects(scene.children)\[0].point);

if(forfeitDistance > getSetting('Input/Eye Click/Movement limit')) {

log('forfeitlongpress')

clearInterval(startLongPress);

afterClick();

};

\


// On click

if(fullness >= PI \* 2) {

intersection.object.onlongpress();

log('longpress')

clearInterval(startLongPress);

afterClick();

}

}, 1000 / fps);

} else {

afterClick();

}

}

}, 1000 / fps);

};

}, getSetting('Input/Eye Click/Delayed hover duration') \* 1000)

return;

} else {

afterClick();

}

\


function afterClick() {

// Update previous intersection

previousIntersection = intersections;

previousIntersection.intersection = intersection;

\


// Onhoverexit

if(intersection.object.uuid != previousIntersection.intersection.object.uuid) {

previousIntersection.object.onhoverexit();

}

\


clicking = false;

log('afterclick')

\


// Change back pointers

scene.remove(segmentPointer);

scene.add(pointer);

\


return;

}

}

}

};

};

\


function startLogic() {

logicInterval = setInterval(logic, 1000 / fps);

};

\


function stopLogic() {

clearInterval(logicInterval);

cameraStream.pause();

};

\


// Input

function onLockedMouseMove(xMotion, yMotion) {

cameraTargetRotation.x = confine(cameraTargetRotation.x - (yMotion \* cameraRotationSensitivity), -0.5 \* PI, 0.6 \* PI);

if(wrap(cameraTargetRotation.y - (xMotion \* cameraRotationSensitivity), -PI, PI) != (cameraTargetRotation.y - (xMotion \* cameraRotationSensitivity))) {

cameraRotation.y = wrap(cameraTargetRotation.y - (xMotion \* cameraRotationSensitivity), -PI, PI);

};

cameraTargetRotation.y = wrap(cameraTargetRotation.y - (xMotion \* cameraRotationSensitivity), -PI, PI);

setCameraRotation();

};

\


// Setup buttons

byId('toggle-debug').addEventListener('click', function(event) {

if(byId('debug-menu').style.display == 'block') {

byId('debug-menu').style.display = 'none';

} else {

byId('debug-menu').style.display = 'block';

}

});

\


// Keypress manager

const keysToPreventDefault = \['alt', '/', 'f1', 'f2', 'f3'];

\


function putKeyDown(key) {

if(!keysDown.includes(key.toLowerCase())) {

keysDown.push(key.toLowerCase());

};

};

\


function putKeyUp(key) {

keysDown = removeFromArray(keysDown, \[key.toLowerCase()]);

};

\


document.addEventListener('keydown', function(e) {

if(keysToPreventDefault.includes(e.key.toLowerCase())) {

e.preventDefault();

};

putKeyDown(e.key);

});

\


document.addEventListener('keyup', function(e) {

putKeyUp(e.key);

});

\


// Pointer position

document.addEventListener('mousemove', function(e) {

pointerPosition = {'x': e.clientX, 'y': e.clientY, 'positions': \[{'clientX': e.clientX, 'clientY': e.clientY}], 'type': 'mouse'};

});

\


document.addEventListener('touchmove', function(e) {

pointerPosition = {'x': e.touches\[0].clientX, 'y': e.touches\[0].clientY, 'positions': e.touches, 'type': 'touch'};

});

\


// Gyrometer

window\.addEventListener("deviceorientation", function(event) {

orientation = {

'absolute': event.absolute,

'alpha': event.alpha,

'beta': event.beta,

'gamma': event.gamma

};

\


byId('gyro-absolute').innerHTML = orientation.absolute;

byId('gyro-alpha').innerHTML = orientation.alpha.toFixed(2);

byId('gyro-beta').innerHTML = orientation.beta.toFixed(2);

byId('gyro-gamma').innerHTML = orientation.gamma.toFixed(2);

const theOrientation = (window\.offsetWidth > window\.offsetHeight) ? 'landscape' : 'portrait';

\


// If orientation is logged correctly

if(!Object.values(orientation).includes(null)) {

// Subtract 90deg if in portrait mode

if(theOrientation == 'portrait') {

orientation.alpha = wrap(orientation.alpha + 90, 0, 360);

}

\


// Offset y

const offsetY = 89.5; // I have no idea why this works

\


if(Math.abs(orientation.beta) < 100) {

cameraTargetRotation.x = (-orientation.gamma \* (PI / 180) % 180) - offsetY;

} else {

cameraTargetRotation.x = (orientation.gamma \* (PI / 180) % 180) + offsetY;

}

cameraTargetRotation.y = orientation.alpha \* (PI / 180);

cameraTargetRotation.z = (-orientation.beta \* (PI / 180)) + offsetY;

\


cameraRotation.x = cameraTargetRotation.x;

cameraRotation.y = cameraTargetRotation.y;

cameraRotation.z = cameraTargetRotation.z;

\


if(theOrientation == 'landscape') {

}

\


setCameraRotation();

};

}, true);

 

cross-posted from: https://lemm.ee/post/46067136

I'm designing a webapp that is supposed to be an AR environment on your phone, to be viewed with something like Google Cardboard, but I am having an issue that the segmentPointer object that is meant to appear when clicking on an object is not.

I've checked the geometry displays correctly in a sandbox, and when I change it to a 3d object rather than shapeGeometry it does display, but I cannot figure out why it is not displaying how I want it to.

The project is at https://voxelverse.jackgreenearth.org, and the code is quite long, but it is here to read in its totality below as it might need the whole context to discover the error. I've tried myself looking through the code, and I've tried searching the web and asking LLMs, but I couldn't figure it out, so please help me, fellow humans.

Tap for code

"use strict";

import \* as THREE from 'three';

import {GLTFLoader} from 'three/addons/loaders/GLTFLoader.js';

\


const loader = new GLTFLoader();

const textureLoader = new THREE.TextureLoader();

const manager = THREE.DefaultLoadingManager;

\


// Basic functions

\


function ls(id) {

return(localStorage.getItem(id));

};

\


function setLs(id, val) {

localStorage.setItem(id, val);

};

\


function byId(id) {

return(document.getElementById(id));

};

\


function bySel(sel) {

return(document.querySelector(sel));

};

\


function byClass(id) {

return(document.getElementsByClassName(id));

};

\


function toTitleCase(str) {

return str.replace(

/\w\S\*/g,

function(txt) {

return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();

}

);

};

\


function randInt(max) {

return Math.floor(Math.random() \* (max));

};

\


function getRandomFloat(min, max, decimals) {

return(parseFloat((Math.random() \* (max - min) + min).toFixed(decimals)));

};

\


function confine(value, min, max) {

if(value < min) {

return(min);

} else if(value > max) {

return(max);

} else {

return(value);

};

};

\


function wrap(value, min, max) {

const range = max - min;

\


if(value < min) {

return(wrap(value + range, min, max));

} else if(value > max) {

return(wrap(value - range, min, max));

} else {

return(value);

};

};

\


function removeFromArray(array, forDeletion) {

return(array.filter(item => !forDeletion.includes(item)));

};

\


function radToDeg(radians) {

return radians \* (180 / PI);

}

\


function range(start, stop, step = 1) {

if (stop === undefined) {

stop = start;

start = 0

}

return Array.from({ length: (stop - start) / step }, (\_, i) => start + (i \* step));

}

\


function between(variable, min, max, inclusive='min') {

switch(inclusive) {

case 'none':

return((variable > min) && (variable < max));

break;

case 'both':

return((variable >= min) && (variable <= max));

break;

case 'min':

return((variable >= min) && (variable < max));

break;

case 'max':

return((variable > min) && (variable <= max));

break;

}

}

\


function download(data, filename, type) {

var file = new Blob(\[data], {type: type});

if (window\.navigator.msSaveOrOpenBlob) // IE10+

window\.navigator.msSaveOrOpenBlob(file, filename);

else { // Others

var a = document.createElement("a"),

url = URL.createObjectURL(file);

a.href = url;

a.download = filename;

document.body.appendChild(a);

a.click();

setTimeout(function() {

document.body.removeChild(a);

window\.URL.revokeObjectURL(url);

}, 0);

};

};

\


function log(text) {

console.log(text);

};

\


function distance2d(x1, y1, x2, y2) {

return(Math.sqrt(

(Math.abs(x1 - x2) \*\* 2) +

(Math.abs(y1 - y2) \*\* 2)

));

};

\


function distance3d(p1 = new THREE.Vector3(0, 0, 0), p2 = new THREE.Vector3(0, 0, 0)) {

return(Math.sqrt((distance2d(p1.x, p1.y, p2.x, p2.y) \*\* 2) + (Math.abs(p1.z - p2.z) \*\* 2)));

};

\


let totalElementsToLoad = 0;

let numberOfElementsLoaded = 0;

\


function onAllElementsLoaded() {

\


}

\


function load(path, type, functionOnLoad) {

totalElementsToLoad += 1;

\


if(type == 'html') {

fetch(path)

.then(response => response.text())

.then(html => {

let doc = new DOMParser().parseFromString(html, "text/html");

\


functionOnLoad(doc);

\


// If all elements to load have been loaded, execute the relevant function

numberOfElementsLoaded += 1;

if(numberOfElementsLoaded == totalElementsToLoad) {

onAllElementsLoaded();

}

})

.catch(error => {

console.error(error);

});

} else if(type == 'json') {

fetch(path)

.then(response => response.json()) // parse the response as JSON

.then(json => {

functionOnLoad(json);

\


// If all elements to load have been loaded, execute the relevant function

numberOfElementsLoaded += 1;

if(numberOfElementsLoaded == totalElementsToLoad) {

onAllElementsLoaded();

}

})

.catch(error => {

console.error(error);

});

}

}

\


// Setup

\


const PI = 3.1415926535897932384626433832795028841971;

\


// Objects

\


let orientation = {

'absolute': false,

'alpha': 0,

'beta': 0,

'gamma': 0

}

\


// vars

const fps = 60;

\


let keysDown = \[];

let pointerPosition = {'x': 0, 'y': 0, 'positions': \[{'clientX': 0, 'clientY': 0}], 'type': 'mouse'};

\


// Camera

let cameraRotation = new THREE.Euler(0, 0, 0, 'YXZ');

let cameraTargetRotation = {'x': 0, 'y': 0, 'z': 0};

const cameraRotationSensitivity = 0.002;

\


// Other variables

let logicInterval;

\


// Load default settings

let defaultSettings;

\


load("/assets/json/default-settings.json", 'json', function(defset) {

defaultSettings = defset;

\


// Create custom settings

if(!Object.keys(localStorage).includes('settings')) {

setLs('settings', JSON.stringify({}));

};

\


onSettingsLoad();

});

\


function settingURL(url, addValue=true) {

return('children/' + url.split('/').join('/children/') + (addValue ? '/value' : ''));

}

\


function customiseSetting(url, value) {

url = settingURL(url).split('/');

\


let newSettings;

\


function recursiveSet(object, list, index, setTo) {

// If the current component is the last one, assign the value

if(index == list.length - 1) {

object\[list\[index]] = setTo;

return(object);

} else {

// Check if it already contains the value

if(object.hasOwnProperty(list\[index])) {

object\[list\[index]] = recursiveSet(object\[list\[index]], list, index + 1, setTo);

} else {

object\[list\[index]] = recursiveSet({}, list, index + 1, setTo);

}

return(object);

}

};

\


newSettings = recursiveSet(JSON.parse(ls('settings')), url, 0, value);

\


setLs('settings', JSON.stringify(newSettings));

}

\


function getSetting(url, addValue) {

url = settingURL(url, addValue).split('/');

\


function recursiveGet(object, list, index) {

// If the current component is the last one, return the value

if (index == list.length - 1) {

return object\[list\[index]];

} else {

// Check if it contains the value

if (object.hasOwnProperty(list\[index])) {

return recursiveGet(object\[list\[index]], list, index + 1);

} else {

return null; // No such setting

}

}

}

\


// Try to find it in local settings first, otherwise get it from defaultSettings

const localGet = recursiveGet(JSON.parse(ls('settings')), url, 0);

if(localGet == null) {

return(recursiveGet(defaultSettings, url, 0));

} else {

return(localGet);

}

}

\


// First, lets define some functions

// Rendering functions

\


// Thanks, https\://discourse.threejs.org/t/roundedrectangle-squircle/28645!

function roundRectangleGeometry(w, h, r, s) { // width, height, radius corner, smoothness

// helper const's

const wi = w / 2 - r; // inner width

const hi = h / 2 - r; // inner height

const w2 = w / 2; // half width

const h2 = h / 2; // half height

const ul = r / w; // u left

const ur = ( w - r ) / w; // u right

const vl = r / h; // v low

const vh = ( h - r ) / h; // v high

let positions = \[

-wi, -h2, 0, wi, -h2, 0, wi, h2, 0,

-wi, -h2, 0, wi, h2, 0, -wi, h2, 0,

-w2, -hi, 0, -wi, -hi, 0, -wi, hi, 0,

-w2, -hi, 0, -wi, hi, 0, -w2, hi, 0,

wi, -hi, 0, w2, -hi, 0, w2, hi, 0,

wi, -hi, 0, w2, hi, 0, wi, hi, 0

];

let uvs = \[

ul, 0, ur, 0, ur, 1,

ul, 0, ur, 1, ul, 1,

0, vl, ul, vl, ul, vh,

0, vl, ul, vh, 0, vh,

ur, vl, 1, vl, 1, vh,

ur, vl, 1, vh, ur, vh

];

let phia = 0;

let phib, xc, yc, uc, vc, cosa, sina, cosb, sinb;

for (let i = 0; i < s \* 4; i ++) {

phib = Math.PI \* 2 \* ( i + 1 ) / ( 4 \* s );

cosa = Math.cos( phia );

sina = Math.sin( phia );

cosb = Math.cos( phib );

sinb = Math.sin( phib );

xc = i < s || i >= 3 \* s ? wi : - wi;

yc = i < 2 \* s ? hi : -hi;

positions.push( xc, yc, 0, xc + r \* cosa, yc + r \* sina, 0, xc + r \* cosb, yc + r \* sinb, 0 );

uc = i < s || i >= 3 \* s ? ur : ul;

vc = i < 2 \* s ? vh : vl;

uvs.push( uc, vc, uc + ul \* cosa, vc + vl \* sina, uc + ul \* cosb, vc + vl \* sinb );

phia = phib;

}

const geometry = new THREE.BufferGeometry( );

geometry.setAttribute( 'position', new THREE.BufferAttribute( new Float32Array( positions ), 3 ) );

geometry.setAttribute( 'uv', new THREE.BufferAttribute( new Float32Array( uvs ), 2 ) );

return geometry;

}

\


// Render

function render() {

requestAnimationFrame(render);

leftRenderer.render(scene, leftCamera);

rightRenderer.render(scene, rightCamera);

\


framesSoFar++;

};

\


// Functions

function setCameraRotation() {

// Calculate drag

cameraRotation.x = Number((cameraRotation.x + ((cameraTargetRotation.x - cameraRotation.x) / getSetting('Input/Mouse/Camera Rotation Drag'))).toFixed(5));

cameraRotation.y = Number((cameraRotation.y + ((cameraTargetRotation.y - cameraRotation.y) / getSetting('Input/Mouse/Camera Rotation Drag'))).toFixed(5));

cameraRotation.z = Number((cameraRotation.z + ((cameraTargetRotation.z - cameraRotation.z) / getSetting('Input/Mouse/Camera Rotation Drag'))).toFixed(5));

// Update cameras

for(let camera of \[leftCamera, rightCamera]) {

camera.rotation.set(cameraRotation.x, cameraRotation.y, cameraRotation.z, 'YXZ');

}

\


const eyeGap = getSetting('Quick Settings/Eye Gap');

\


// Set camera positions

leftCamera.position.x = -1 \* eyeGap \* Math.sin(cameraRotation.y);

leftCamera.position.z = -1 \* eyeGap \* Math.cos(cameraRotation.y);

rightCamera.position.x = eyeGap \* Math.sin(cameraRotation.y);

rightCamera.position.z = eyeGap \* Math.cos(cameraRotation.y);

\


byId('camera-target-rot-x').innerHTML = cameraTargetRotation.x.toFixed(2);

byId('camera-target-rot-y').innerHTML = cameraTargetRotation.y.toFixed(2);

byId('camera-target-rot-z').innerHTML = cameraTargetRotation.z.toFixed(2);

byId('camera-rot-x').innerHTML = cameraRotation.x.toFixed(2);

byId('camera-rot-y').innerHTML = cameraRotation.y.toFixed(2);

byId('camera-rot-z').innerHTML = cameraRotation.z.toFixed(2);

\


byId('camera-left-rot-x').innerHTML = leftCamera.rotation.x.toFixed(2);

byId('camera-left-rot-y').innerHTML = leftCamera.rotation.y.toFixed(2);

byId('camera-left-rot-z').innerHTML = leftCamera.rotation.z.toFixed(2);

}

\


function takeScreenshot() {

downloadCanvasImage(document.getElementById('game-canvas'), gameName + ' screenshot');

sendAlert('Screenshot Taken!', 'tick');

};

\


function takePanorama() {

const canvas = document.getElementById('game-canvas');

const height = canvas.height;

const width = canvas.width \* (360 / (camera.fov \* camera.aspect));

let newCanvas = document.createElement('canvas');

newCanvas.height = height;

newCanvas.width = width;

newCanvas.style.display = 'none';

let context = newCanvas.getContext("2d");

document.body.appendChild(newCanvas);

for(let x = 0; x < width; x++) {

// Rotate

cameraRotation.y += ((2 \* PI) / width);

let calculatedRotation = rotationToAbsolute(playerPosition, cameraRotation);

camera.rotation.set(calculatedRotation.x, calculatedRotation.y, calculatedRotation.z, 'YXZ');

renderer.render(scene, camera);

const gl = renderer.getContext();

// Get canvas data

const pixelData = new Uint8ClampedArray(1 \* height \* 4);

const reversedPixelData = new Uint8ClampedArray(1 \* height \* 4);

gl.readPixels((canvas.width / 2), 0, 1, height, gl.RGBA, gl.UNSIGNED\_BYTE, pixelData);

for (let i = 0; i < height; i++) {

for (let j = 0; j < 4; j++) {

reversedPixelData\[i\*4 + j] = pixelData\[(height - i - 1)\*4 + j];

};

};

const imageData = new ImageData(reversedPixelData, 1, height);

context.putImageData(imageData, x, 0);

};

downloadCanvasImage(newCanvas, gameName + ' panorama');

newCanvas.remove();

sendAlert('Panoramic screenshot taken!', 'tick');

};

\


function setRotation(object, rotation) {

object.rotation.set(rotation.x, rotation.y, rotation.z);

};

\


function downloadCanvasImage(canvas, name) {

let canvasImage = canvas.toDataURL('image/png');

// this can be used to download any image from webpage to local disk

let xhr = new XMLHttpRequest();

xhr.responseType = 'blob';

xhr.onload = function () {

let a = document.createElement('a');

a.href = window\.URL.createObjectURL(xhr.response);

a.download = name;

a.style.display = 'none';

document.body.appendChild(a);

a.click();

a.remove();

};

xhr.open('GET', canvasImage); // This is to download the canvas image

xhr.send();

};

\


function xyToRealPosRot(x, y, distance) {

let realX, realY, realZ, rotX, rotY, rotZ;

\


// Position is an object {x: x, y: y} x determines which face it will be on horizontally, and y determines if it will be on the top or the bottom

// Beyond 400, x position wraps

x = wrap(x, 0, 400);

log('x before: ' + x)

const horizontalFace = (x / 100) % 4;

//rotY = (x / 400) \* (1) // horizontalFace);

\


// The top of the screen is y 100, the bottom is y -100, and the horizontals are between -50 and 50

realY = confine(y, -100, 100);

\


// Calculate real position

const unit = getSetting('Display/UI/Distance') / 50;

\


let forward = getSetting('Display/UI/Distance');

\


const bevel = getSetting('Display/UI/Bevel');

\


rotX = 0;

\


// If it is horizontal...

if(between(y, -50 + bevel, 50 - bevel)) {

realY = y;

rotX = 0;

} else if(y < -50 - bevel) {

// If it is on the lower face

realY = -50;

forward = (y + 100) \* unit;

rotX = -(PI / 2);

} else if(y >= 50 + bevel) {

// If it is on the upper face

realY = 50;

forward = (y - 100) \* unit;

//side = unit \* (((x - 50) % 100) + 50);

rotX = (PI / 2);

} else if(between(y, -50 - bevel, -50 + bevel)) {

// If it is on the lower bevel

realY = -50 - ((y + 50) / 2);

rotX = (PI / 4);

} else if(between(y, 50 - bevel, 50 + bevel)) {

// If it is on the upper bevel

realY = 50 + ((y - 50) / 2) ;

rotX = -(PI / 4);

}

\


realY = realY \* unit;

\


let flip = false;

\


/\*if(

(horizontalFace >= 0 && horizontalFace < 0.5) ||

(horizontalFace >= 1.5 && horizontalFace < 2.5) ||

(horizontalFace >= 3.5 && horizontalFace < 4)

) {

flip = true;

}\*/

\


let angle = (x / 400) \* (PI \* 2);

realX = Math.sin(angle) \* forward;

realZ = Math.cos(angle) \* forward;

rotY = angle;

log('rot y: ' + rotY)

\


log({

'x': realX,

'y': realY,

'forward': forward,

})

\


// Take distance into account

realX \*= distance;

realY \*= distance;

realZ \*= distance;

\


return({

'position': new THREE.Vector3(realX, realY, realZ),

'rotation': new THREE.Euler(rotX, rotY, rotZ, 'YXZ'),

'flip': flip

});

}

\


function addWidget({

name = '',

position = {'x': 0, 'y': 0},

rotation = {'x': 0, 'y': 0, 'z': 0},

distance = 1,

size = {'x': 10, 'y': 10},

radius = 3,

shape = 'rRect',

background = '#000000',

opacity,

textStyle = {

'align': 'center',

'weight': 0, // Range is 0 to 10

'font': 'DINRoundPro,arial,sans-serif',

'color': '#b0b0b0',

'vertical-align': 'center',

'font-size': 1 // Uses the same sizing system as the rest of the UI, so one unit of text is also one unit of object

},

textContent = '',

onclick = function() {},

onlongpress = function() {},

onhover = function() {},

onhoverexit = function() {},

ontruehover = function() {}

}) {

const realPosRot = xyToRealPosRot(position.x, position.y, distance);

log(realPosRot)

const realPos = realPosRot.position;

let realRot = realPosRot.rotation;

\


realRot.x += rotation.x;

realRot.y += rotation.y;

realRot.z = rotation.z;

\


// Calculate real size

const unit = getSetting('Display/UI/Distance') / 100;

\


let width = unit \* size.x;

let height = unit \* size.y;

radius \*= unit;

const scale = getSetting('Display/UI/Scale/General');

width \*= scale;

height \*= scale;

radius \*= scale;

\


// Set mesh geometry

let geometry;

switch(shape) {

case 'rRect':

geometry = roundRectangleGeometry(width, height, radius, 10);

break;

case 'rect':

geometry = new THREE.PlaneGeometry(width, height);

break;

case 'circle':

geometry = new THREE.CircleGeometry((width + height) / 2, 32);

break;

}

let material;

\


if(opacity == undefined) {

opacity = 1;

}

\


if(textContent == '') {

if(background\[0] == '/') {

loadTexture(background, function(texture) {

material = new THREE.MeshBasicMaterial({

map: texture,

side: THREE.DoubleSide,

opacity: opacity,

transparent: true

});

onTextureLoad(material);

})

} else {

material = new THREE.MeshBasicMaterial({

color: background,

side: THREE.DoubleSide,

opacity: opacity,

transparent: true

});

onTextureLoad(material);

}

} else {

function prepareText(canvas) {

// Proceed to prepare the canvas with the text

ctx.font = \`${textStyle\["font-size"]}em ${textStyle\["font"]}\`;

ctx.textAlign = textStyle\["align"];

ctx.fillStyle = textStyle\["color"];

ctx.fillText(textContent, 0, 0);

// Compose the text onto the background

const composedTexture = new THREE.CanvasTexture(canvas);

\


// Generate the material

material = new THREE.MeshBasicMaterial({

map: composedTexture,

side: THREE.DoubleSide,

transparent: true,

alphaTest: 0.5

});

\


onTextureLoad(material);

}

\


// Initialize tmpcanvas only when needed

const tmpcanvas = document.createElement('canvas');

tmpcanvas.width = width;

tmpcanvas.height = height;

const ctx = tmpcanvas.getContext('2d');

\
\


// Fill the background first

if (background\[0] == '/') {

loadTexture(background, function(texture) {

ctx.fillStyle = texture;

ctx.fillRect(0, 0, width, height);

\


prepareText(tmpcanvas);

})

} else {

ctx.fillStyle = background;

ctx.fillRect(0, 0, width, height);

\


prepareText(tmpcanvas);

}

}

function onTextureLoad(material) {

// Create a mesh with the geometry and the material

let mesh = new THREE.Mesh(geometry, material);

\


mesh.name = name;

\


mesh.position.set(realPos.x, realPos.y, realPos.z );

mesh.rotation.set(realRot.x, realRot.y, realRot.z);

\


if(realPosRot.flip) {

mesh.scale.x = -1;

}

\


mesh.onclick = onclick;

mesh.onlongpress = onlongpress;

mesh.onhoverexit = onhoverexit;

mesh.ontruehover = ontruehover;

mesh.onchover = onhover;

\


scene.add(mesh);

};

}

\


function transitionWidget(name, property, newProperty, time, condition) {

if(condition != null) {

}

}

\


// three.js Scene setup

const scene = new THREE.Scene();

\


// three functions

\


function loadTexture(path, onload) {

textureLoader.load(path, function (texture) {

onload(texture);

}, undefined, function (error) {

console.error(error);

});

};

\


// Define objects to make them global, they will mostly be only added to the scene when settings are loaded

let sun;

let wallpaper;

let cameraStream;

let pointer;

\


let pointerMaterial = new THREE.MeshBasicMaterial({

color: "hsl(0, 100%, 50%)",

side: THREE.DoubleSide

});

\


let segmentShape = new THREE.Shape();

let segmentGeometry = new THREE.ShapeGeometry(segmentShape);

let segmentPointer = new THREE.Mesh(segmentGeometry, pointerMaterial);

segmentPointer.name = 'segmentPointer';

\


function setSegmentPointer(angle = 0, radius = 0.1, rotation = new THREE.Euler(0, 0, 0), clockwise=true) {

let oldGeometry = segmentPointer.geometry;

\


let segmentShape = new THREE.Shape();

segmentShape.moveTo(0, 0);

segmentShape.arc(0, 0, radius, 0, angle, clockwise);

segmentShape.lineTo(0, 0);

\


let extrudeSettings = {

steps: 1,

depth: 0.1,

bevelEnabled: false

};

\


let segmentGeometry = new THREE.ExtrudeGeometry(segmentShape, extrudeSettings);

segmentPointer.geometry = segmentGeometry;

\


oldGeometry.dispose();

\


segmentPointer.rotation.set(rotation);

}

\


// Camera stuff

let cameraViewDistance;

\


// Setup cameras

let leftCamera;

let rightCamera;

let leftRenderer;

let rightRenderer;

\


function setRendererSize() {

for(let renderer of \[leftRenderer, rightRenderer]) {

let canvas = renderer.domElement;

renderer.setSize(

canvas.offsetWidth \* getSetting('Display/Anti-alias'),

canvas.offsetHeight \* getSetting('Display/Anti-alias'),

false

);

}

}

\


function updateCameraAspectRatio() {-0.2

for(let camera of \[leftCamera, rightCamera]) {

let canvas = leftRenderer.domElement;

camera.aspect = canvas.offsetWidth / canvas.offsetHeight;

camera.updateProjectionMatrix();

}

}

\


// temp

function startAssistant() {

log('assisstant')

}

\


// When settings are loaded, start settings stuff up

function onSettingsLoad() {

// Add sun

sun = new THREE.PointLight(0xffffff, getSetting('Quick Settings/Brightness'));

scene.add(sun);

\


// Pointers

pointer = new THREE.Mesh(new THREE.IcosahedronGeometry(getSetting('Display/UI/Pointer/Size'), 1), pointerMaterial);

pointer.name = 'pointer';

scene.add(pointer);

\


pointerMaterial = new THREE.MeshBasicMaterial(

{color: "hsl(" + (getSetting('Quick Settings/Theme Hue') + getSetting('Display/UI/Pointer/Hue Shift')) + ", 100%, 50%)"}

);

pointer.material = pointerMaterial;

segmentPointer.material = pointerMaterial;

\


// Add wallpaper

let wallpaperURL;

if(getSetting('Display/UI/Wallpaper/Use Direct Link') == true) {

wallpaperURL = getSetting('Display/UI/Wallpaper/Direct Link');

} else {

wallpaperURL = getSetting('Display/UI/Wallpaper/Wallpapers Folder') + '/' + getSetting('Display/UI/Wallpaper/Wallpaper');

}

\


loadTexture(wallpaperURL, function(texture) {

let material = new THREE.MeshStandardMaterial({

map: texture,

side: THREE.BackSide,

transparent: true,

opacity: getSetting('Display/UI/Wallpaper/Opacity')

});

\


wallpaper = new THREE.Mesh(

new THREE.IcosahedronGeometry(

getSetting('Display/UI/Distance') \* getSetting('Display/UI/Wallpaper/Distance') \* getSetting('Display/UI/Testing Size Multiplier'), 4),

material

);

wallpaper.scale.x = -1;

wallpaper.name = "wallpaper";

scene.add(wallpaper);

});

\


// Setup cameras

cameraViewDistance = getSetting('Display/UI/Distance') \* getSetting('Display/UI/Testing Size Multiplier') \* 2; // Keep this down to destroy lag

leftCamera = new THREE.PerspectiveCamera(80, 1, 0.001, cameraViewDistance);

rightCamera = new THREE.PerspectiveCamera(80, 1, 0.001, cameraViewDistance);

\


// Setup renderers

leftRenderer = new THREE.WebGLRenderer({canvas: byId('left-canvas'), antialias: true, logarithmicDepthBuffer: true, preserveDrawingBuffer: true, alpha: true});

rightRenderer = new THREE.WebGLRenderer({canvas: byId('right-canvas'), antialias: true, logarithmicDepthBuffer: true, preserveDrawingBuffer: true, alpha: true});

\


updateCameraAspectRatio();

setRendererSize();

\


window\.addEventListener('resize', function() {

updateCameraAspectRatio();

setRendererSize();

});

\


// Setup control center

const baseY = getSetting('Display/Control Center/Switch To Bottom') ? -100 : 100;

const backgroundFolder = getSetting('Display/Control Center/Icon Folder');

function createTileGrid(scale, x, y, farness, tiles, name) {

let counter = 0;

log(tiles)

addWidget({

position: {'x': x, 'y': baseY + y},

size: {'x': 3 \* scale, 'y': 3 \* scale},

background: "hsl(" + getSetting('Quick Settings/Theme Hue') + ", 50%, 80%)",

name: name + ' background',

radius: 0.5 \* scale,

onhover: openTileGroup(name)

});

for(let widgetY = 1; widgetY >= -1; widgetY--) {

for(let widgetX = -1; widgetX <=1; widgetX++) {

if(counter < tiles.length) {

if(tiles\[counter].folder) {

createTileGrid(scale / 3, (widgetX \* scale), (widgetY \* scale), farness \* 0.99, tiles\[counter].children);

} else {

log('scale' + scale)

addWidget({

position: {'x': x + (widgetX \* scale), 'y': baseY + y + (widgetY \* scale)},

size: {'x': scale, 'y': scale},

background: backgroundFolder + '/' + tiles\[counter].icon + '.svg',

name: tiles\[counter].name,

group: name,

radius: scale / 3,

distance: farness \* 0.99

});

log('added widget control center')

};

} else {

break;

};

counter++;

};

if(counter >= tiles.length) {

break;

};

};

};

\


createTileGrid(

getSetting('Display/UI/Scale/Control Center') \* 1.5,

0,

baseY,

1,

getSetting('Display/Control Center/Tiles'),

getSetting('Display/Control Center/Tiles', false)\['name']

);

// Quick function

let quickFunction = getSetting('Display/Control Center/Quick Function', false);

\


addWidget({

position: {'x': 0, 'y': getSetting('Display/Control Center/Switch To Bottom') ? 100 : -100},

background: '/assets/images/icons/control\_center/' + quickFunction.icon + '.svg',

name: quickFunction.name,

onclick: quickFunction.onclick

});

\


// testing

addWidget({

position: {'x': 0, 'y': -50},

background: '/assets/images/icons/control\_center/torch.svg',

name: "torch"

});

addWidget({

position: {'x': 200, 'y': 10},

background: '/assets/images/icons/control\_center/screencast.svg',

name: "screencast"

});

for(let i of range(16)) {

addWidget({

position: {'x': i \* 25, 'y': 0},

background: 'hsl(' + getSetting('Quick Settings/Theme Hue') + ', 100%, ' + ((i / 16) \* 100) + '%)',

name: "test" + i,

textContent: '',//i.toString()

'onclick': function() {

log('click' + i);

},

'onhover': function() {

log('hover' + i);

}

});

}

};

\


function updateSetting(url, value) {

customiseSetting(url, value);

\


switch(url) {

case 'Display/UI/Wallpaper/Opacity':

wallpaper.material.opacity = value;

break;

};

};

\


// Start

\


// Setup the camera stream

function setupCameraStream() {

function handleSuccess(stream) {

cameraStream = document.createElement('video');

cameraStream.style.transform = 'rotate(270deg)';

cameraStream.srcObject = stream;

cameraStream.play();

\


let texture = new THREE.VideoTexture(cameraStream);

texture.minFilter = THREE.LinearFilter;

texture.magFilter = THREE.LinearFilter;

scene.background = texture;

\


customiseSetting('Display/UI/Wallpaper/Opacity', 0); // Temporary until GUI settings are introduced

}

\


function handleError(error) {

// Set wallpaper opacity to 1

updateSetting('Display/UI/Wallpaper/Opacity', 1);

\


log('Unable to access the camera/webcam.');

}

\


navigator.mediaDevices.getUserMedia({video: {facingMode: "environment"}})

.then(handleSuccess)

.catch(function(error) {

if (error.name === 'OverconstrainedError') {

// Fallback to default video settings

navigator.mediaDevices.getUserMedia({video: true})

.then(handleSuccess)

.catch(handleError);

} else {

// Handle other errors

handleError(error);

}

});

};

\


// Fullscreen and pointer lock, request fullscreen mode for the element

function openFullscreen(elem, then) {

if (elem.requestFullscreen) {

elem.requestFullscreen().then(then);

} else if (elem.webkitRequestFullscreen) { /\* Safari \*/

elem.webkitRequestFullscreen().then(then);

} else if (elem.msRequestFullscreen) { /\* IE11 \*/

elem.msRequestFullscreen().then(then);

}

}

// Request pointer lock

function requestPointerLock(myTargetElement) {

const promise = myTargetElement.requestPointerLock({

unadjustedMovement: true,

});

\


if (!promise) {

log("disabling mouse acceleration is not supported");

return;

}

\


return promise

.then(() => log("pointer is locked"))

.catch((error) => {

if (error.name === "NotSupportedError") {

// Some platforms may not support unadjusted movement.

// You can request again a regular pointer lock.

return myTargetElement.requestPointerLock();

}

});

}

\


function lockPointer() {

requestPointerLock(byId('body'));

document.addEventListener("pointerlockchange", lockChangeAlert, false);

};

\


function lockChangeAlert() {

if (document.pointerLockElement === byId('body')) {

document.addEventListener("mousemove", updatePosition, false);

} else {

document.removeEventListener("mousemove", updatePosition, false);

}

}

\


function updatePosition(e) {

onLockedMouseMove(e.movementX, e.movementY);

};

\


function fullscreenAndPointerLock() {

openFullscreen(byId('body'), function() {

lockPointer();

});

}

\


function permission() {

// Check if the device supports deviceorientation and requestPermission

if (typeof(DeviceMotionEvent) !== "undefined" && typeof(DeviceMotionEvent.requestPermission) === "function") {

// Request permission

DeviceMotionEvent.requestPermission()

.then(response => {

// Check the response

if (response == "granted") {};

})

.catch(console.error); // Handle errors

} else {

// Device does not support deviceorientation

log("DeviceOrientationEvent is not defined");

}

}

\


async function keepScreenAwake() {

// Create a reference for the Wake Lock.

let wakeLock = null;

\


// create an async function to request a wake lock

try {

wakeLock = await navigator.wakeLock.request("screen");

log("Wake Lock is active!");

} catch (err) {

// The Wake Lock request has failed - usually system related, such as battery.

log(\`${err.name}, ${err.message}\`);

}

}

\


let framesSoFar = 0;

\


function startFPSCount() {

byId('logic-fps').innerHTML = fps.toString();

\


let renderFPSInterval = setInterval(function() {

byId('render-fps').innerHTML = framesSoFar.toString();

framesSoFar = 0;

}, 1000);

}

\


function start() {

byId('loading-screen').style.display = 'none';

setupCameraStream();

startLogic();

startFPSCount();

render();

permission();

fullscreenAndPointerLock();

keepScreenAwake();

\


byId('left-canvas').addEventListener('click', fullscreenAndPointerLock);

byId('right-canvas').addEventListener('click', fullscreenAndPointerLock);

};

\


// Loading

byId('loading-bar').style.display = 'block';

\


manager.onProgress = function (url, itemsLoaded, itemsTotal) {

//log('Loading file: ' + url + '.\nLoaded ' + itemsLoaded + ' of ' + itemsTotal + ' files.');

\


byId('loading-bar-fg').style.setProperty('--size', ((itemsLoaded / itemsTotal) \* 100) + '%');

};

\


manager.onError = function (url) {

log('There was an error loading ' + url);

};

\


manager.onLoad = function ( ) {

setTimeout(function() {

byId('loading-bar').style.display = 'none';

byId('play').style.display = 'block';

}, 500);

byId('play').addEventListener('click', start);

};

\


function openTileGroup(group) {

}

\


// Logic

let pointerRaycast = new THREE.Raycaster();

let previousIntersection = pointerRaycast.intersectObjects(scene.children);

let clicking = false;

\


function logic() {

// Set camera rotation

if(cameraTargetRotation.x != cameraRotation.x || cameraTargetRotation.y != cameraRotation.y) {

setCameraRotation();

};

\


// Update pointer

pointerRaycast.set(

new THREE.Vector3(0, 0, 0),

leftCamera.getWorldDirection(new THREE.Vector3())

);

\


// Check if the pointer is itersecting with any object

\


const intersections = pointerRaycast.intersectObjects(scene.children);

if (intersections.length > 0) {

for(let intersection of intersections) {

// If it's intersecting with itself, don't do anything

if(intersection.object.name == 'pointer' || intersection.object.name == 'segmentPointer') {

return;

} else {

// If it's intersecting with an object, copy that intersection's position, and start to click

const point = intersections\[0].point;

pointer.position.copy(point);

\


// Truehover

if(Object.keys(intersection.object).includes('ontruehover')) {

// Prevent hover being continuously trigggered

if(previousIntersection.uuid != intersections\[0].uuid) {

intersection.object.ontruehover();

}

}

log('truehover')

\


// Start click after grace period, if object is clickable

if(

Object.keys(intersection.object).includes('onclick') ||

Object.keys(intersection.object).includes('onhover') ||

Object.keys(intersection.object).includes('onhoverexit') ||

Object.keys(intersection.object).includes('onlongpress')

) {

let gracePeriod = setTimeout(function() {

// onhover

if(Object.keys(intersection.object).includes('onhover')) {

intersection.object.onhover();

}

log('hover')

\


// Start click

if(Object.keys(intersection.object).includes('onclick') && (!clicking)) {

clicking = true;

\


let fullness = 0;

\


// Manage pointers

scene.add(segmentPointer);

segmentPointer.position.copy(pointer);

scene.remove(pointer);

let startClick = setInterval(function() {

fullness += (PI \* 2) / (fps \* getSetting('Input/Eye Click/Duration/Pre-click duration'));

setSegmentPointer(

fullness,

getSetting('Display/UI/Pointer/Size') \* getSetting('Display/UI/Pointer/Clicking Size'),

intersection.object.rotation

);

\


byId('pointer-angle').innerHTML = fullness.toFixed(4);

\


// On forfeit

let forfeitDistance = distance3d(point, pointerRaycast.intersectObjects(scene.children)\[0].point);

if(forfeitDistance > getSetting('Input/Eye Click/Movement limit')) {

log('forfeit ' + forfeitDistance)

clearInterval(startClick);

afterClick();

}

\


if(fullness >= PI \* 2) {

log('click')

intersection.object.onclick();

clearInterval(startClick);

\


if(Object.keys(intersection.object).includes('onlongpress')) {

// Start longpress

fullness = 0;

let startLongPress = setInterval(function() {

fullness += (PI \* 2) / (fps \* getSetting('Input/Eye Click/Duration/Long-press duration'));

setSegmentPointer(

fullness,

getSetting('Display/UI/Pointer/Size') \* getSetting('Display/UI/Pointer/Clicking Size'),

intersection.object.rotation,

false

);

byId('pointer-angle').innerHTML = fullness.toFixed(4);

\


let forfeitDistance = distance3d(point, pointerRaycast.intersectObjects(scene.children)\[0].point);

if(forfeitDistance > getSetting('Input/Eye Click/Movement limit')) {

log('forfeitlongpress')

clearInterval(startLongPress);

afterClick();

};

\


// On click

if(fullness >= PI \* 2) {

intersection.object.onlongpress();

log('longpress')

clearInterval(startLongPress);

afterClick();

}

}, 1000 / fps);

} else {

afterClick();

}

}

}, 1000 / fps);

};

}, getSetting('Input/Eye Click/Delayed hover duration') \* 1000)

return;

} else {

afterClick();

}

\


function afterClick() {

// Update previous intersection

previousIntersection = intersections;

previousIntersection.intersection = intersection;

\


// Onhoverexit

if(intersection.object.uuid != previousIntersection.intersection.object.uuid) {

previousIntersection.object.onhoverexit();

}

\


clicking = false;

log('afterclick')

\


// Change back pointers

scene.remove(segmentPointer);

scene.add(pointer);

\


return;

}

}

}

};

};

\


function startLogic() {

logicInterval = setInterval(logic, 1000 / fps);

};

\


function stopLogic() {

clearInterval(logicInterval);

cameraStream.pause();

};

\


// Input

function onLockedMouseMove(xMotion, yMotion) {

cameraTargetRotation.x = confine(cameraTargetRotation.x - (yMotion \* cameraRotationSensitivity), -0.5 \* PI, 0.6 \* PI);

if(wrap(cameraTargetRotation.y - (xMotion \* cameraRotationSensitivity), -PI, PI) != (cameraTargetRotation.y - (xMotion \* cameraRotationSensitivity))) {

cameraRotation.y = wrap(cameraTargetRotation.y - (xMotion \* cameraRotationSensitivity), -PI, PI);

};

cameraTargetRotation.y = wrap(cameraTargetRotation.y - (xMotion \* cameraRotationSensitivity), -PI, PI);

setCameraRotation();

};

\


// Setup buttons

byId('toggle-debug').addEventListener('click', function(event) {

if(byId('debug-menu').style.display == 'block') {

byId('debug-menu').style.display = 'none';

} else {

byId('debug-menu').style.display = 'block';

}

});

\


// Keypress manager

const keysToPreventDefault = \['alt', '/', 'f1', 'f2', 'f3'];

\


function putKeyDown(key) {

if(!keysDown.includes(key.toLowerCase())) {

keysDown.push(key.toLowerCase());

};

};

\


function putKeyUp(key) {

keysDown = removeFromArray(keysDown, \[key.toLowerCase()]);

};

\


document.addEventListener('keydown', function(e) {

if(keysToPreventDefault.includes(e.key.toLowerCase())) {

e.preventDefault();

};

putKeyDown(e.key);

});

\


document.addEventListener('keyup', function(e) {

putKeyUp(e.key);

});

\


// Pointer position

document.addEventListener('mousemove', function(e) {

pointerPosition = {'x': e.clientX, 'y': e.clientY, 'positions': \[{'clientX': e.clientX, 'clientY': e.clientY}], 'type': 'mouse'};

});

\


document.addEventListener('touchmove', function(e) {

pointerPosition = {'x': e.touches\[0].clientX, 'y': e.touches\[0].clientY, 'positions': e.touches, 'type': 'touch'};

});

\


// Gyrometer

window\.addEventListener("deviceorientation", function(event) {

orientation = {

'absolute': event.absolute,

'alpha': event.alpha,

'beta': event.beta,

'gamma': event.gamma

};

\


byId('gyro-absolute').innerHTML = orientation.absolute;

byId('gyro-alpha').innerHTML = orientation.alpha.toFixed(2);

byId('gyro-beta').innerHTML = orientation.beta.toFixed(2);

byId('gyro-gamma').innerHTML = orientation.gamma.toFixed(2);

const theOrientation = (window\.offsetWidth > window\.offsetHeight) ? 'landscape' : 'portrait';

\


// If orientation is logged correctly

if(!Object.values(orientation).includes(null)) {

// Subtract 90deg if in portrait mode

if(theOrientation == 'portrait') {

orientation.alpha = wrap(orientation.alpha + 90, 0, 360);

}

\


// Offset y

const offsetY = 89.5; // I have no idea why this works

\


if(Math.abs(orientation.beta) < 100) {

cameraTargetRotation.x = (-orientation.gamma \* (PI / 180) % 180) - offsetY;

} else {

cameraTargetRotation.x = (orientation.gamma \* (PI / 180) % 180) + offsetY;

}

cameraTargetRotation.y = orientation.alpha \* (PI / 180);

cameraTargetRotation.z = (-orientation.beta \* (PI / 180)) + offsetY;

\


cameraRotation.x = cameraTargetRotation.x;

cameraRotation.y = cameraTargetRotation.y;

cameraRotation.z = cameraTargetRotation.z;

\


if(theOrientation == 'landscape') {

}

\


setCameraRotation();

};

}, true);

 

cross-posted from: https://lemm.ee/post/46067136

I'm designing a webapp that is supposed to be an AR environment on your phone, to be viewed with something like Google Cardboard, but I am having an issue that the segmentPointer object that is meant to appear when clicking on an object is not.

I've checked the geometry displays correctly in a sandbox, and when I change it to a 3d object rather than shapeGeometry it does display, but I cannot figure out why it is not displaying how I want it to.

The project is at https://voxelverse.jackgreenearth.org, and the code is quite long, but it is here to read in its totality below as it might need the whole context to discover the error. I've tried myself looking through the code, and I've tried searching the web and asking LLMs, but I couldn't figure it out, so please help me, fellow humans.

Tap for code

"use strict";

import \* as THREE from 'three';

import {GLTFLoader} from 'three/addons/loaders/GLTFLoader.js';

\


const loader = new GLTFLoader();

const textureLoader = new THREE.TextureLoader();

const manager = THREE.DefaultLoadingManager;

\


// Basic functions

\


function ls(id) {

return(localStorage.getItem(id));

};

\


function setLs(id, val) {

localStorage.setItem(id, val);

};

\


function byId(id) {

return(document.getElementById(id));

};

\


function bySel(sel) {

return(document.querySelector(sel));

};

\


function byClass(id) {

return(document.getElementsByClassName(id));

};

\


function toTitleCase(str) {

return str.replace(

/\w\S\*/g,

function(txt) {

return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();

}

);

};

\


function randInt(max) {

return Math.floor(Math.random() \* (max));

};

\


function getRandomFloat(min, max, decimals) {

return(parseFloat((Math.random() \* (max - min) + min).toFixed(decimals)));

};

\


function confine(value, min, max) {

if(value < min) {

return(min);

} else if(value > max) {

return(max);

} else {

return(value);

};

};

\


function wrap(value, min, max) {

const range = max - min;

\


if(value < min) {

return(wrap(value + range, min, max));

} else if(value > max) {

return(wrap(value - range, min, max));

} else {

return(value);

};

};

\


function removeFromArray(array, forDeletion) {

return(array.filter(item => !forDeletion.includes(item)));

};

\


function radToDeg(radians) {

return radians \* (180 / PI);

}

\


function range(start, stop, step = 1) {

if (stop === undefined) {

stop = start;

start = 0

}

return Array.from({ length: (stop - start) / step }, (\_, i) => start + (i \* step));

}

\


function between(variable, min, max, inclusive='min') {

switch(inclusive) {

case 'none':

return((variable > min) && (variable < max));

break;

case 'both':

return((variable >= min) && (variable <= max));

break;

case 'min':

return((variable >= min) && (variable < max));

break;

case 'max':

return((variable > min) && (variable <= max));

break;

}

}

\


function download(data, filename, type) {

var file = new Blob(\[data], {type: type});

if (window\.navigator.msSaveOrOpenBlob) // IE10+

window\.navigator.msSaveOrOpenBlob(file, filename);

else { // Others

var a = document.createElement("a"),

url = URL.createObjectURL(file);

a.href = url;

a.download = filename;

document.body.appendChild(a);

a.click();

setTimeout(function() {

document.body.removeChild(a);

window\.URL.revokeObjectURL(url);

}, 0);

};

};

\


function log(text) {

console.log(text);

};

\


function distance2d(x1, y1, x2, y2) {

return(Math.sqrt(

(Math.abs(x1 - x2) \*\* 2) +

(Math.abs(y1 - y2) \*\* 2)

));

};

\


function distance3d(p1 = new THREE.Vector3(0, 0, 0), p2 = new THREE.Vector3(0, 0, 0)) {

return(Math.sqrt((distance2d(p1.x, p1.y, p2.x, p2.y) \*\* 2) + (Math.abs(p1.z - p2.z) \*\* 2)));

};

\


let totalElementsToLoad = 0;

let numberOfElementsLoaded = 0;

\


function onAllElementsLoaded() {

\


}

\


function load(path, type, functionOnLoad) {

totalElementsToLoad += 1;

\


if(type == 'html') {

fetch(path)

.then(response => response.text())

.then(html => {

let doc = new DOMParser().parseFromString(html, "text/html");

\


functionOnLoad(doc);

\


// If all elements to load have been loaded, execute the relevant function

numberOfElementsLoaded += 1;

if(numberOfElementsLoaded == totalElementsToLoad) {

onAllElementsLoaded();

}

})

.catch(error => {

console.error(error);

});

} else if(type == 'json') {

fetch(path)

.then(response => response.json()) // parse the response as JSON

.then(json => {

functionOnLoad(json);

\


// If all elements to load have been loaded, execute the relevant function

numberOfElementsLoaded += 1;

if(numberOfElementsLoaded == totalElementsToLoad) {

onAllElementsLoaded();

}

})

.catch(error => {

console.error(error);

});

}

}

\


// Setup

\


const PI = 3.1415926535897932384626433832795028841971;

\


// Objects

\


let orientation = {

'absolute': false,

'alpha': 0,

'beta': 0,

'gamma': 0

}

\


// vars

const fps = 60;

\


let keysDown = \[];

let pointerPosition = {'x': 0, 'y': 0, 'positions': \[{'clientX': 0, 'clientY': 0}], 'type': 'mouse'};

\


// Camera

let cameraRotation = new THREE.Euler(0, 0, 0, 'YXZ');

let cameraTargetRotation = {'x': 0, 'y': 0, 'z': 0};

const cameraRotationSensitivity = 0.002;

\


// Other variables

let logicInterval;

\


// Load default settings

let defaultSettings;

\


load("/assets/json/default-settings.json", 'json', function(defset) {

defaultSettings = defset;

\


// Create custom settings

if(!Object.keys(localStorage).includes('settings')) {

setLs('settings', JSON.stringify({}));

};

\


onSettingsLoad();

});

\


function settingURL(url, addValue=true) {

return('children/' + url.split('/').join('/children/') + (addValue ? '/value' : ''));

}

\


function customiseSetting(url, value) {

url = settingURL(url).split('/');

\


let newSettings;

\


function recursiveSet(object, list, index, setTo) {

// If the current component is the last one, assign the value

if(index == list.length - 1) {

object\[list\[index]] = setTo;

return(object);

} else {

// Check if it already contains the value

if(object.hasOwnProperty(list\[index])) {

object\[list\[index]] = recursiveSet(object\[list\[index]], list, index + 1, setTo);

} else {

object\[list\[index]] = recursiveSet({}, list, index + 1, setTo);

}

return(object);

}

};

\


newSettings = recursiveSet(JSON.parse(ls('settings')), url, 0, value);

\


setLs('settings', JSON.stringify(newSettings));

}

\


function getSetting(url, addValue) {

url = settingURL(url, addValue).split('/');

\


function recursiveGet(object, list, index) {

// If the current component is the last one, return the value

if (index == list.length - 1) {

return object\[list\[index]];

} else {

// Check if it contains the value

if (object.hasOwnProperty(list\[index])) {

return recursiveGet(object\[list\[index]], list, index + 1);

} else {

return null; // No such setting

}

}

}

\


// Try to find it in local settings first, otherwise get it from defaultSettings

const localGet = recursiveGet(JSON.parse(ls('settings')), url, 0);

if(localGet == null) {

return(recursiveGet(defaultSettings, url, 0));

} else {

return(localGet);

}

}

\


// First, lets define some functions

// Rendering functions

\


// Thanks, https\://discourse.threejs.org/t/roundedrectangle-squircle/28645!

function roundRectangleGeometry(w, h, r, s) { // width, height, radius corner, smoothness

// helper const's

const wi = w / 2 - r; // inner width

const hi = h / 2 - r; // inner height

const w2 = w / 2; // half width

const h2 = h / 2; // half height

const ul = r / w; // u left

const ur = ( w - r ) / w; // u right

const vl = r / h; // v low

const vh = ( h - r ) / h; // v high

let positions = \[

-wi, -h2, 0, wi, -h2, 0, wi, h2, 0,

-wi, -h2, 0, wi, h2, 0, -wi, h2, 0,

-w2, -hi, 0, -wi, -hi, 0, -wi, hi, 0,

-w2, -hi, 0, -wi, hi, 0, -w2, hi, 0,

wi, -hi, 0, w2, -hi, 0, w2, hi, 0,

wi, -hi, 0, w2, hi, 0, wi, hi, 0

];

let uvs = \[

ul, 0, ur, 0, ur, 1,

ul, 0, ur, 1, ul, 1,

0, vl, ul, vl, ul, vh,

0, vl, ul, vh, 0, vh,

ur, vl, 1, vl, 1, vh,

ur, vl, 1, vh, ur, vh

];

let phia = 0;

let phib, xc, yc, uc, vc, cosa, sina, cosb, sinb;

for (let i = 0; i < s \* 4; i ++) {

phib = Math.PI \* 2 \* ( i + 1 ) / ( 4 \* s );

cosa = Math.cos( phia );

sina = Math.sin( phia );

cosb = Math.cos( phib );

sinb = Math.sin( phib );

xc = i < s || i >= 3 \* s ? wi : - wi;

yc = i < 2 \* s ? hi : -hi;

positions.push( xc, yc, 0, xc + r \* cosa, yc + r \* sina, 0, xc + r \* cosb, yc + r \* sinb, 0 );

uc = i < s || i >= 3 \* s ? ur : ul;

vc = i < 2 \* s ? vh : vl;

uvs.push( uc, vc, uc + ul \* cosa, vc + vl \* sina, uc + ul \* cosb, vc + vl \* sinb );

phia = phib;

}

const geometry = new THREE.BufferGeometry( );

geometry.setAttribute( 'position', new THREE.BufferAttribute( new Float32Array( positions ), 3 ) );

geometry.setAttribute( 'uv', new THREE.BufferAttribute( new Float32Array( uvs ), 2 ) );

return geometry;

}

\


// Render

function render() {

requestAnimationFrame(render);

leftRenderer.render(scene, leftCamera);

rightRenderer.render(scene, rightCamera);

\


framesSoFar++;

};

\


// Functions

function setCameraRotation() {

// Calculate drag

cameraRotation.x = Number((cameraRotation.x + ((cameraTargetRotation.x - cameraRotation.x) / getSetting('Input/Mouse/Camera Rotation Drag'))).toFixed(5));

cameraRotation.y = Number((cameraRotation.y + ((cameraTargetRotation.y - cameraRotation.y) / getSetting('Input/Mouse/Camera Rotation Drag'))).toFixed(5));

cameraRotation.z = Number((cameraRotation.z + ((cameraTargetRotation.z - cameraRotation.z) / getSetting('Input/Mouse/Camera Rotation Drag'))).toFixed(5));

// Update cameras

for(let camera of \[leftCamera, rightCamera]) {

camera.rotation.set(cameraRotation.x, cameraRotation.y, cameraRotation.z, 'YXZ');

}

\


const eyeGap = getSetting('Quick Settings/Eye Gap');

\


// Set camera positions

leftCamera.position.x = -1 \* eyeGap \* Math.sin(cameraRotation.y);

leftCamera.position.z = -1 \* eyeGap \* Math.cos(cameraRotation.y);

rightCamera.position.x = eyeGap \* Math.sin(cameraRotation.y);

rightCamera.position.z = eyeGap \* Math.cos(cameraRotation.y);

\


byId('camera-target-rot-x').innerHTML = cameraTargetRotation.x.toFixed(2);

byId('camera-target-rot-y').innerHTML = cameraTargetRotation.y.toFixed(2);

byId('camera-target-rot-z').innerHTML = cameraTargetRotation.z.toFixed(2);

byId('camera-rot-x').innerHTML = cameraRotation.x.toFixed(2);

byId('camera-rot-y').innerHTML = cameraRotation.y.toFixed(2);

byId('camera-rot-z').innerHTML = cameraRotation.z.toFixed(2);

\


byId('camera-left-rot-x').innerHTML = leftCamera.rotation.x.toFixed(2);

byId('camera-left-rot-y').innerHTML = leftCamera.rotation.y.toFixed(2);

byId('camera-left-rot-z').innerHTML = leftCamera.rotation.z.toFixed(2);

}

\


function takeScreenshot() {

downloadCanvasImage(document.getElementById('game-canvas'), gameName + ' screenshot');

sendAlert('Screenshot Taken!', 'tick');

};

\


function takePanorama() {

const canvas = document.getElementById('game-canvas');

const height = canvas.height;

const width = canvas.width \* (360 / (camera.fov \* camera.aspect));

let newCanvas = document.createElement('canvas');

newCanvas.height = height;

newCanvas.width = width;

newCanvas.style.display = 'none';

let context = newCanvas.getContext("2d");

document.body.appendChild(newCanvas);

for(let x = 0; x < width; x++) {

// Rotate

cameraRotation.y += ((2 \* PI) / width);

let calculatedRotation = rotationToAbsolute(playerPosition, cameraRotation);

camera.rotation.set(calculatedRotation.x, calculatedRotation.y, calculatedRotation.z, 'YXZ');

renderer.render(scene, camera);

const gl = renderer.getContext();

// Get canvas data

const pixelData = new Uint8ClampedArray(1 \* height \* 4);

const reversedPixelData = new Uint8ClampedArray(1 \* height \* 4);

gl.readPixels((canvas.width / 2), 0, 1, height, gl.RGBA, gl.UNSIGNED\_BYTE, pixelData);

for (let i = 0; i < height; i++) {

for (let j = 0; j < 4; j++) {

reversedPixelData\[i\*4 + j] = pixelData\[(height - i - 1)\*4 + j];

};

};

const imageData = new ImageData(reversedPixelData, 1, height);

context.putImageData(imageData, x, 0);

};

downloadCanvasImage(newCanvas, gameName + ' panorama');

newCanvas.remove();

sendAlert('Panoramic screenshot taken!', 'tick');

};

\


function setRotation(object, rotation) {

object.rotation.set(rotation.x, rotation.y, rotation.z);

};

\


function downloadCanvasImage(canvas, name) {

let canvasImage = canvas.toDataURL('image/png');

// this can be used to download any image from webpage to local disk

let xhr = new XMLHttpRequest();

xhr.responseType = 'blob';

xhr.onload = function () {

let a = document.createElement('a');

a.href = window\.URL.createObjectURL(xhr.response);

a.download = name;

a.style.display = 'none';

document.body.appendChild(a);

a.click();

a.remove();

};

xhr.open('GET', canvasImage); // This is to download the canvas image

xhr.send();

};

\


function xyToRealPosRot(x, y, distance) {

let realX, realY, realZ, rotX, rotY, rotZ;

\


// Position is an object {x: x, y: y} x determines which face it will be on horizontally, and y determines if it will be on the top or the bottom

// Beyond 400, x position wraps

x = wrap(x, 0, 400);

log('x before: ' + x)

const horizontalFace = (x / 100) % 4;

//rotY = (x / 400) \* (1) // horizontalFace);

\


// The top of the screen is y 100, the bottom is y -100, and the horizontals are between -50 and 50

realY = confine(y, -100, 100);

\


// Calculate real position

const unit = getSetting('Display/UI/Distance') / 50;

\


let forward = getSetting('Display/UI/Distance');

\


const bevel = getSetting('Display/UI/Bevel');

\


rotX = 0;

\


// If it is horizontal...

if(between(y, -50 + bevel, 50 - bevel)) {

realY = y;

rotX = 0;

} else if(y < -50 - bevel) {

// If it is on the lower face

realY = -50;

forward = (y + 100) \* unit;

rotX = -(PI / 2);

} else if(y >= 50 + bevel) {

// If it is on the upper face

realY = 50;

forward = (y - 100) \* unit;

//side = unit \* (((x - 50) % 100) + 50);

rotX = (PI / 2);

} else if(between(y, -50 - bevel, -50 + bevel)) {

// If it is on the lower bevel

realY = -50 - ((y + 50) / 2);

rotX = (PI / 4);

} else if(between(y, 50 - bevel, 50 + bevel)) {

// If it is on the upper bevel

realY = 50 + ((y - 50) / 2) ;

rotX = -(PI / 4);

}

\


realY = realY \* unit;

\


let flip = false;

\


/\*if(

(horizontalFace >= 0 && horizontalFace < 0.5) ||

(horizontalFace >= 1.5 && horizontalFace < 2.5) ||

(horizontalFace >= 3.5 && horizontalFace < 4)

) {

flip = true;

}\*/

\


let angle = (x / 400) \* (PI \* 2);

realX = Math.sin(angle) \* forward;

realZ = Math.cos(angle) \* forward;

rotY = angle;

log('rot y: ' + rotY)

\


log({

'x': realX,

'y': realY,

'forward': forward,

})

\


// Take distance into account

realX \*= distance;

realY \*= distance;

realZ \*= distance;

\


return({

'position': new THREE.Vector3(realX, realY, realZ),

'rotation': new THREE.Euler(rotX, rotY, rotZ, 'YXZ'),

'flip': flip

});

}

\


function addWidget({

name = '',

position = {'x': 0, 'y': 0},

rotation = {'x': 0, 'y': 0, 'z': 0},

distance = 1,

size = {'x': 10, 'y': 10},

radius = 3,

shape = 'rRect',

background = '#000000',

opacity,

textStyle = {

'align': 'center',

'weight': 0, // Range is 0 to 10

'font': 'DINRoundPro,arial,sans-serif',

'color': '#b0b0b0',

'vertical-align': 'center',

'font-size': 1 // Uses the same sizing system as the rest of the UI, so one unit of text is also one unit of object

},

textContent = '',

onclick = function() {},

onlongpress = function() {},

onhover = function() {},

onhoverexit = function() {},

ontruehover = function() {}

}) {

const realPosRot = xyToRealPosRot(position.x, position.y, distance);

log(realPosRot)

const realPos = realPosRot.position;

let realRot = realPosRot.rotation;

\


realRot.x += rotation.x;

realRot.y += rotation.y;

realRot.z = rotation.z;

\


// Calculate real size

const unit = getSetting('Display/UI/Distance') / 100;

\


let width = unit \* size.x;

let height = unit \* size.y;

radius \*= unit;

const scale = getSetting('Display/UI/Scale/General');

width \*= scale;

height \*= scale;

radius \*= scale;

\


// Set mesh geometry

let geometry;

switch(shape) {

case 'rRect':

geometry = roundRectangleGeometry(width, height, radius, 10);

break;

case 'rect':

geometry = new THREE.PlaneGeometry(width, height);

break;

case 'circle':

geometry = new THREE.CircleGeometry((width + height) / 2, 32);

break;

}

let material;

\


if(opacity == undefined) {

opacity = 1;

}

\


if(textContent == '') {

if(background\[0] == '/') {

loadTexture(background, function(texture) {

material = new THREE.MeshBasicMaterial({

map: texture,

side: THREE.DoubleSide,

opacity: opacity,

transparent: true

});

onTextureLoad(material);

})

} else {

material = new THREE.MeshBasicMaterial({

color: background,

side: THREE.DoubleSide,

opacity: opacity,

transparent: true

});

onTextureLoad(material);

}

} else {

function prepareText(canvas) {

// Proceed to prepare the canvas with the text

ctx.font = \`${textStyle\["font-size"]}em ${textStyle\["font"]}\`;

ctx.textAlign = textStyle\["align"];

ctx.fillStyle = textStyle\["color"];

ctx.fillText(textContent, 0, 0);

// Compose the text onto the background

const composedTexture = new THREE.CanvasTexture(canvas);

\


// Generate the material

material = new THREE.MeshBasicMaterial({

map: composedTexture,

side: THREE.DoubleSide,

transparent: true,

alphaTest: 0.5

});

\


onTextureLoad(material);

}

\


// Initialize tmpcanvas only when needed

const tmpcanvas = document.createElement('canvas');

tmpcanvas.width = width;

tmpcanvas.height = height;

const ctx = tmpcanvas.getContext('2d');

\
\


// Fill the background first

if (background\[0] == '/') {

loadTexture(background, function(texture) {

ctx.fillStyle = texture;

ctx.fillRect(0, 0, width, height);

\


prepareText(tmpcanvas);

})

} else {

ctx.fillStyle = background;

ctx.fillRect(0, 0, width, height);

\


prepareText(tmpcanvas);

}

}

function onTextureLoad(material) {

// Create a mesh with the geometry and the material

let mesh = new THREE.Mesh(geometry, material);

\


mesh.name = name;

\


mesh.position.set(realPos.x, realPos.y, realPos.z );

mesh.rotation.set(realRot.x, realRot.y, realRot.z);

\


if(realPosRot.flip) {

mesh.scale.x = -1;

}

\


mesh.onclick = onclick;

mesh.onlongpress = onlongpress;

mesh.onhoverexit = onhoverexit;

mesh.ontruehover = ontruehover;

mesh.onchover = onhover;

\


scene.add(mesh);

};

}

\


function transitionWidget(name, property, newProperty, time, condition) {

if(condition != null) {

}

}

\


// three.js Scene setup

const scene = new THREE.Scene();

\


// three functions

\


function loadTexture(path, onload) {

textureLoader.load(path, function (texture) {

onload(texture);

}, undefined, function (error) {

console.error(error);

});

};

\


// Define objects to make them global, they will mostly be only added to the scene when settings are loaded

let sun;

let wallpaper;

let cameraStream;

let pointer;

\


let pointerMaterial = new THREE.MeshBasicMaterial({

color: "hsl(0, 100%, 50%)",

side: THREE.DoubleSide

});

\


let segmentShape = new THREE.Shape();

let segmentGeometry = new THREE.ShapeGeometry(segmentShape);

let segmentPointer = new THREE.Mesh(segmentGeometry, pointerMaterial);

segmentPointer.name = 'segmentPointer';

\


function setSegmentPointer(angle = 0, radius = 0.1, rotation = new THREE.Euler(0, 0, 0), clockwise=true) {

let oldGeometry = segmentPointer.geometry;

\


let segmentShape = new THREE.Shape();

segmentShape.moveTo(0, 0);

segmentShape.arc(0, 0, radius, 0, angle, clockwise);

segmentShape.lineTo(0, 0);

\


let extrudeSettings = {

steps: 1,

depth: 0.1,

bevelEnabled: false

};

\


let segmentGeometry = new THREE.ExtrudeGeometry(segmentShape, extrudeSettings);

segmentPointer.geometry = segmentGeometry;

\


oldGeometry.dispose();

\


segmentPointer.rotation.set(rotation);

}

\


// Camera stuff

let cameraViewDistance;

\


// Setup cameras

let leftCamera;

let rightCamera;

let leftRenderer;

let rightRenderer;

\


function setRendererSize() {

for(let renderer of \[leftRenderer, rightRenderer]) {

let canvas = renderer.domElement;

renderer.setSize(

canvas.offsetWidth \* getSetting('Display/Anti-alias'),

canvas.offsetHeight \* getSetting('Display/Anti-alias'),

false

);

}

}

\


function updateCameraAspectRatio() {-0.2

for(let camera of \[leftCamera, rightCamera]) {

let canvas = leftRenderer.domElement;

camera.aspect = canvas.offsetWidth / canvas.offsetHeight;

camera.updateProjectionMatrix();

}

}

\


// temp

function startAssistant() {

log('assisstant')

}

\


// When settings are loaded, start settings stuff up

function onSettingsLoad() {

// Add sun

sun = new THREE.PointLight(0xffffff, getSetting('Quick Settings/Brightness'));

scene.add(sun);

\


// Pointers

pointer = new THREE.Mesh(new THREE.IcosahedronGeometry(getSetting('Display/UI/Pointer/Size'), 1), pointerMaterial);

pointer.name = 'pointer';

scene.add(pointer);

\


pointerMaterial = new THREE.MeshBasicMaterial(

{color: "hsl(" + (getSetting('Quick Settings/Theme Hue') + getSetting('Display/UI/Pointer/Hue Shift')) + ", 100%, 50%)"}

);

pointer.material = pointerMaterial;

segmentPointer.material = pointerMaterial;

\


// Add wallpaper

let wallpaperURL;

if(getSetting('Display/UI/Wallpaper/Use Direct Link') == true) {

wallpaperURL = getSetting('Display/UI/Wallpaper/Direct Link');

} else {

wallpaperURL = getSetting('Display/UI/Wallpaper/Wallpapers Folder') + '/' + getSetting('Display/UI/Wallpaper/Wallpaper');

}

\


loadTexture(wallpaperURL, function(texture) {

let material = new THREE.MeshStandardMaterial({

map: texture,

side: THREE.BackSide,

transparent: true,

opacity: getSetting('Display/UI/Wallpaper/Opacity')

});

\


wallpaper = new THREE.Mesh(

new THREE.IcosahedronGeometry(

getSetting('Display/UI/Distance') \* getSetting('Display/UI/Wallpaper/Distance') \* getSetting('Display/UI/Testing Size Multiplier'), 4),

material

);

wallpaper.scale.x = -1;

wallpaper.name = "wallpaper";

scene.add(wallpaper);

});

\


// Setup cameras

cameraViewDistance = getSetting('Display/UI/Distance') \* getSetting('Display/UI/Testing Size Multiplier') \* 2; // Keep this down to destroy lag

leftCamera = new THREE.PerspectiveCamera(80, 1, 0.001, cameraViewDistance);

rightCamera = new THREE.PerspectiveCamera(80, 1, 0.001, cameraViewDistance);

\


// Setup renderers

leftRenderer = new THREE.WebGLRenderer({canvas: byId('left-canvas'), antialias: true, logarithmicDepthBuffer: true, preserveDrawingBuffer: true, alpha: true});

rightRenderer = new THREE.WebGLRenderer({canvas: byId('right-canvas'), antialias: true, logarithmicDepthBuffer: true, preserveDrawingBuffer: true, alpha: true});

\


updateCameraAspectRatio();

setRendererSize();

\


window\.addEventListener('resize', function() {

updateCameraAspectRatio();

setRendererSize();

});

\


// Setup control center

const baseY = getSetting('Display/Control Center/Switch To Bottom') ? -100 : 100;

const backgroundFolder = getSetting('Display/Control Center/Icon Folder');

function createTileGrid(scale, x, y, farness, tiles, name) {

let counter = 0;

log(tiles)

addWidget({

position: {'x': x, 'y': baseY + y},

size: {'x': 3 \* scale, 'y': 3 \* scale},

background: "hsl(" + getSetting('Quick Settings/Theme Hue') + ", 50%, 80%)",

name: name + ' background',

radius: 0.5 \* scale,

onhover: openTileGroup(name)

});

for(let widgetY = 1; widgetY >= -1; widgetY--) {

for(let widgetX = -1; widgetX <=1; widgetX++) {

if(counter < tiles.length) {

if(tiles\[counter].folder) {

createTileGrid(scale / 3, (widgetX \* scale), (widgetY \* scale), farness \* 0.99, tiles\[counter].children);

} else {

log('scale' + scale)

addWidget({

position: {'x': x + (widgetX \* scale), 'y': baseY + y + (widgetY \* scale)},

size: {'x': scale, 'y': scale},

background: backgroundFolder + '/' + tiles\[counter].icon + '.svg',

name: tiles\[counter].name,

group: name,

radius: scale / 3,

distance: farness \* 0.99

});

log('added widget control center')

};

} else {

break;

};

counter++;

};

if(counter >= tiles.length) {

break;

};

};

};

\


createTileGrid(

getSetting('Display/UI/Scale/Control Center') \* 1.5,

0,

baseY,

1,

getSetting('Display/Control Center/Tiles'),

getSetting('Display/Control Center/Tiles', false)\['name']

);

// Quick function

let quickFunction = getSetting('Display/Control Center/Quick Function', false);

\


addWidget({

position: {'x': 0, 'y': getSetting('Display/Control Center/Switch To Bottom') ? 100 : -100},

background: '/assets/images/icons/control\_center/' + quickFunction.icon + '.svg',

name: quickFunction.name,

onclick: quickFunction.onclick

});

\


// testing

addWidget({

position: {'x': 0, 'y': -50},

background: '/assets/images/icons/control\_center/torch.svg',

name: "torch"

});

addWidget({

position: {'x': 200, 'y': 10},

background: '/assets/images/icons/control\_center/screencast.svg',

name: "screencast"

});

for(let i of range(16)) {

addWidget({

position: {'x': i \* 25, 'y': 0},

background: 'hsl(' + getSetting('Quick Settings/Theme Hue') + ', 100%, ' + ((i / 16) \* 100) + '%)',

name: "test" + i,

textContent: '',//i.toString()

'onclick': function() {

log('click' + i);

},

'onhover': function() {

log('hover' + i);

}

});

}

};

\


function updateSetting(url, value) {

customiseSetting(url, value);

\


switch(url) {

case 'Display/UI/Wallpaper/Opacity':

wallpaper.material.opacity = value;

break;

};

};

\


// Start

\


// Setup the camera stream

function setupCameraStream() {

function handleSuccess(stream) {

cameraStream = document.createElement('video');

cameraStream.style.transform = 'rotate(270deg)';

cameraStream.srcObject = stream;

cameraStream.play();

\


let texture = new THREE.VideoTexture(cameraStream);

texture.minFilter = THREE.LinearFilter;

texture.magFilter = THREE.LinearFilter;

scene.background = texture;

\


customiseSetting('Display/UI/Wallpaper/Opacity', 0); // Temporary until GUI settings are introduced

}

\


function handleError(error) {

// Set wallpaper opacity to 1

updateSetting('Display/UI/Wallpaper/Opacity', 1);

\


log('Unable to access the camera/webcam.');

}

\


navigator.mediaDevices.getUserMedia({video: {facingMode: "environment"}})

.then(handleSuccess)

.catch(function(error) {

if (error.name === 'OverconstrainedError') {

// Fallback to default video settings

navigator.mediaDevices.getUserMedia({video: true})

.then(handleSuccess)

.catch(handleError);

} else {

// Handle other errors

handleError(error);

}

});

};

\


// Fullscreen and pointer lock, request fullscreen mode for the element

function openFullscreen(elem, then) {

if (elem.requestFullscreen) {

elem.requestFullscreen().then(then);

} else if (elem.webkitRequestFullscreen) { /\* Safari \*/

elem.webkitRequestFullscreen().then(then);

} else if (elem.msRequestFullscreen) { /\* IE11 \*/

elem.msRequestFullscreen().then(then);

}

}

// Request pointer lock

function requestPointerLock(myTargetElement) {

const promise = myTargetElement.requestPointerLock({

unadjustedMovement: true,

});

\


if (!promise) {

log("disabling mouse acceleration is not supported");

return;

}

\


return promise

.then(() => log("pointer is locked"))

.catch((error) => {

if (error.name === "NotSupportedError") {

// Some platforms may not support unadjusted movement.

// You can request again a regular pointer lock.

return myTargetElement.requestPointerLock();

}

});

}

\


function lockPointer() {

requestPointerLock(byId('body'));

document.addEventListener("pointerlockchange", lockChangeAlert, false);

};

\


function lockChangeAlert() {

if (document.pointerLockElement === byId('body')) {

document.addEventListener("mousemove", updatePosition, false);

} else {

document.removeEventListener("mousemove", updatePosition, false);

}

}

\


function updatePosition(e) {

onLockedMouseMove(e.movementX, e.movementY);

};

\


function fullscreenAndPointerLock() {

openFullscreen(byId('body'), function() {

lockPointer();

});

}

\


function permission() {

// Check if the device supports deviceorientation and requestPermission

if (typeof(DeviceMotionEvent) !== "undefined" && typeof(DeviceMotionEvent.requestPermission) === "function") {

// Request permission

DeviceMotionEvent.requestPermission()

.then(response => {

// Check the response

if (response == "granted") {};

})

.catch(console.error); // Handle errors

} else {

// Device does not support deviceorientation

log("DeviceOrientationEvent is not defined");

}

}

\


async function keepScreenAwake() {

// Create a reference for the Wake Lock.

let wakeLock = null;

\


// create an async function to request a wake lock

try {

wakeLock = await navigator.wakeLock.request("screen");

log("Wake Lock is active!");

} catch (err) {

// The Wake Lock request has failed - usually system related, such as battery.

log(\`${err.name}, ${err.message}\`);

}

}

\


let framesSoFar = 0;

\


function startFPSCount() {

byId('logic-fps').innerHTML = fps.toString();

\


let renderFPSInterval = setInterval(function() {

byId('render-fps').innerHTML = framesSoFar.toString();

framesSoFar = 0;

}, 1000);

}

\


function start() {

byId('loading-screen').style.display = 'none';

setupCameraStream();

startLogic();

startFPSCount();

render();

permission();

fullscreenAndPointerLock();

keepScreenAwake();

\


byId('left-canvas').addEventListener('click', fullscreenAndPointerLock);

byId('right-canvas').addEventListener('click', fullscreenAndPointerLock);

};

\


// Loading

byId('loading-bar').style.display = 'block';

\


manager.onProgress = function (url, itemsLoaded, itemsTotal) {

//log('Loading file: ' + url + '.\nLoaded ' + itemsLoaded + ' of ' + itemsTotal + ' files.');

\


byId('loading-bar-fg').style.setProperty('--size', ((itemsLoaded / itemsTotal) \* 100) + '%');

};

\


manager.onError = function (url) {

log('There was an error loading ' + url);

};

\


manager.onLoad = function ( ) {

setTimeout(function() {

byId('loading-bar').style.display = 'none';

byId('play').style.display = 'block';

}, 500);

byId('play').addEventListener('click', start);

};

\


function openTileGroup(group) {

}

\


// Logic

let pointerRaycast = new THREE.Raycaster();

let previousIntersection = pointerRaycast.intersectObjects(scene.children);

let clicking = false;

\


function logic() {

// Set camera rotation

if(cameraTargetRotation.x != cameraRotation.x || cameraTargetRotation.y != cameraRotation.y) {

setCameraRotation();

};

\


// Update pointer

pointerRaycast.set(

new THREE.Vector3(0, 0, 0),

leftCamera.getWorldDirection(new THREE.Vector3())

);

\


// Check if the pointer is itersecting with any object

\


const intersections = pointerRaycast.intersectObjects(scene.children);

if (intersections.length > 0) {

for(let intersection of intersections) {

// If it's intersecting with itself, don't do anything

if(intersection.object.name == 'pointer' || intersection.object.name == 'segmentPointer') {

return;

} else {

// If it's intersecting with an object, copy that intersection's position, and start to click

const point = intersections\[0].point;

pointer.position.copy(point);

\


// Truehover

if(Object.keys(intersection.object).includes('ontruehover')) {

// Prevent hover being continuously trigggered

if(previousIntersection.uuid != intersections\[0].uuid) {

intersection.object.ontruehover();

}

}

log('truehover')

\


// Start click after grace period, if object is clickable

if(

Object.keys(intersection.object).includes('onclick') ||

Object.keys(intersection.object).includes('onhover') ||

Object.keys(intersection.object).includes('onhoverexit') ||

Object.keys(intersection.object).includes('onlongpress')

) {

let gracePeriod = setTimeout(function() {

// onhover

if(Object.keys(intersection.object).includes('onhover')) {

intersection.object.onhover();

}

log('hover')

\


// Start click

if(Object.keys(intersection.object).includes('onclick') && (!clicking)) {

clicking = true;

\


let fullness = 0;

\


// Manage pointers

scene.add(segmentPointer);

segmentPointer.position.copy(pointer);

scene.remove(pointer);

let startClick = setInterval(function() {

fullness += (PI \* 2) / (fps \* getSetting('Input/Eye Click/Duration/Pre-click duration'));

setSegmentPointer(

fullness,

getSetting('Display/UI/Pointer/Size') \* getSetting('Display/UI/Pointer/Clicking Size'),

intersection.object.rotation

);

\


byId('pointer-angle').innerHTML = fullness.toFixed(4);

\


// On forfeit

let forfeitDistance = distance3d(point, pointerRaycast.intersectObjects(scene.children)\[0].point);

if(forfeitDistance > getSetting('Input/Eye Click/Movement limit')) {

log('forfeit ' + forfeitDistance)

clearInterval(startClick);

afterClick();

}

\


if(fullness >= PI \* 2) {

log('click')

intersection.object.onclick();

clearInterval(startClick);

\


if(Object.keys(intersection.object).includes('onlongpress')) {

// Start longpress

fullness = 0;

let startLongPress = setInterval(function() {

fullness += (PI \* 2) / (fps \* getSetting('Input/Eye Click/Duration/Long-press duration'));

setSegmentPointer(

fullness,

getSetting('Display/UI/Pointer/Size') \* getSetting('Display/UI/Pointer/Clicking Size'),

intersection.object.rotation,

false

);

byId('pointer-angle').innerHTML = fullness.toFixed(4);

\


let forfeitDistance = distance3d(point, pointerRaycast.intersectObjects(scene.children)\[0].point);

if(forfeitDistance > getSetting('Input/Eye Click/Movement limit')) {

log('forfeitlongpress')

clearInterval(startLongPress);

afterClick();

};

\


// On click

if(fullness >= PI \* 2) {

intersection.object.onlongpress();

log('longpress')

clearInterval(startLongPress);

afterClick();

}

}, 1000 / fps);

} else {

afterClick();

}

}

}, 1000 / fps);

};

}, getSetting('Input/Eye Click/Delayed hover duration') \* 1000)

return;

} else {

afterClick();

}

\


function afterClick() {

// Update previous intersection

previousIntersection = intersections;

previousIntersection.intersection = intersection;

\


// Onhoverexit

if(intersection.object.uuid != previousIntersection.intersection.object.uuid) {

previousIntersection.object.onhoverexit();

}

\


clicking = false;

log('afterclick')

\


// Change back pointers

scene.remove(segmentPointer);

scene.add(pointer);

\


return;

}

}

}

};

};

\


function startLogic() {

logicInterval = setInterval(logic, 1000 / fps);

};

\


function stopLogic() {

clearInterval(logicInterval);

cameraStream.pause();

};

\


// Input

function onLockedMouseMove(xMotion, yMotion) {

cameraTargetRotation.x = confine(cameraTargetRotation.x - (yMotion \* cameraRotationSensitivity), -0.5 \* PI, 0.6 \* PI);

if(wrap(cameraTargetRotation.y - (xMotion \* cameraRotationSensitivity), -PI, PI) != (cameraTargetRotation.y - (xMotion \* cameraRotationSensitivity))) {

cameraRotation.y = wrap(cameraTargetRotation.y - (xMotion \* cameraRotationSensitivity), -PI, PI);

};

cameraTargetRotation.y = wrap(cameraTargetRotation.y - (xMotion \* cameraRotationSensitivity), -PI, PI);

setCameraRotation();

};

\


// Setup buttons

byId('toggle-debug').addEventListener('click', function(event) {

if(byId('debug-menu').style.display == 'block') {

byId('debug-menu').style.display = 'none';

} else {

byId('debug-menu').style.display = 'block';

}

});

\


// Keypress manager

const keysToPreventDefault = \['alt', '/', 'f1', 'f2', 'f3'];

\


function putKeyDown(key) {

if(!keysDown.includes(key.toLowerCase())) {

keysDown.push(key.toLowerCase());

};

};

\


function putKeyUp(key) {

keysDown = removeFromArray(keysDown, \[key.toLowerCase()]);

};

\


document.addEventListener('keydown', function(e) {

if(keysToPreventDefault.includes(e.key.toLowerCase())) {

e.preventDefault();

};

putKeyDown(e.key);

});

\


document.addEventListener('keyup', function(e) {

putKeyUp(e.key);

});

\


// Pointer position

document.addEventListener('mousemove', function(e) {

pointerPosition = {'x': e.clientX, 'y': e.clientY, 'positions': \[{'clientX': e.clientX, 'clientY': e.clientY}], 'type': 'mouse'};

});

\


document.addEventListener('touchmove', function(e) {

pointerPosition = {'x': e.touches\[0].clientX, 'y': e.touches\[0].clientY, 'positions': e.touches, 'type': 'touch'};

});

\


// Gyrometer

window\.addEventListener("deviceorientation", function(event) {

orientation = {

'absolute': event.absolute,

'alpha': event.alpha,

'beta': event.beta,

'gamma': event.gamma

};

\


byId('gyro-absolute').innerHTML = orientation.absolute;

byId('gyro-alpha').innerHTML = orientation.alpha.toFixed(2);

byId('gyro-beta').innerHTML = orientation.beta.toFixed(2);

byId('gyro-gamma').innerHTML = orientation.gamma.toFixed(2);

const theOrientation = (window\.offsetWidth > window\.offsetHeight) ? 'landscape' : 'portrait';

\


// If orientation is logged correctly

if(!Object.values(orientation).includes(null)) {

// Subtract 90deg if in portrait mode

if(theOrientation == 'portrait') {

orientation.alpha = wrap(orientation.alpha + 90, 0, 360);

}

\


// Offset y

const offsetY = 89.5; // I have no idea why this works

\


if(Math.abs(orientation.beta) < 100) {

cameraTargetRotation.x = (-orientation.gamma \* (PI / 180) % 180) - offsetY;

} else {

cameraTargetRotation.x = (orientation.gamma \* (PI / 180) % 180) + offsetY;

}

cameraTargetRotation.y = orientation.alpha \* (PI / 180);

cameraTargetRotation.z = (-orientation.beta \* (PI / 180)) + offsetY;

\


cameraRotation.x = cameraTargetRotation.x;

cameraRotation.y = cameraTargetRotation.y;

cameraRotation.z = cameraTargetRotation.z;

\


if(theOrientation == 'landscape') {

}

\


setCameraRotation();

};

}, true);

 

cross-posted from: https://lemm.ee/post/46066494

I followed the recommended processes for adding images to my app, and it is being displayed correctly on the layout preview, but not at all on the app. I have vector assets, webp, png images, but none are being displayed.

The project is too big to put here in its entirety, but please ask for any snippets that could help you solve the issue. I've tried searching the web and asking LLMs and neither could help, so please help me, fellow humans.

 

cross-posted from: https://lemm.ee/post/46066494

I followed the recommended processes for adding images to my app, and it is being displayed correctly on the layout preview, but not at all on the app. I have vector assets, webp, png images, but none are being displayed.

The project is too big to put here in its entirety, but please ask for any snippets that could help you solve the issue. I've tried searching the web and asking LLMs and neither could help, so please help me, fellow humans.

 

cross-posted from: https://lemm.ee/post/46066494

I followed the recommended processes for adding images to my app, and it is being displayed correctly on the layout preview, but not at all on the app. I have vector assets, webp, png images, but none are being displayed.

The project is too big to put here in its entirety, but please ask for any snippets that could help you solve the issue. I've tried searching the web and asking LLMs and neither could help, so please help me, fellow humans.

 

cross-posted from: https://lemm.ee/post/46066494

I followed the recommended processes for adding images to my app, and it is being displayed correctly on the layout preview, but not at all on the app. I have vector assets, webp, png images, but none are being displayed.

The project is too big to put here in its entirety, but please ask for any snippets that could help you solve the issue. I've tried searching the web and asking LLMs and neither could help, so please help me, fellow humans.

 

cross-posted from: https://lemm.ee/post/46066494

I followed the recommended processes for adding images to my app, and it is being displayed correctly on the layout preview, but not at all on the app. I have vector assets, webp, png images, but none are being displayed.

The project is too big to put here in its entirety, but please ask for any snippets that could help you solve the issue. I've tried searching the web and asking LLMs and neither could help, so please help me, fellow humans.

 

cross-posted from: https://lemm.ee/post/46066494

I followed the recommended processes for adding images to my app, and it is being displayed correctly on the layout preview, but not at all on the app. I have vector assets, webp, png images, but none are being displayed.

The project is too big to put here in its entirety, but please ask for any snippets that could help you solve the issue. I've tried searching the web and asking LLMs and neither could help, so please help me, fellow humans.

 

I'm designing a webapp that is supposed to be an AR environment on your phone, to be viewed with something like Google Cardboard, but I am having an issue that the segmentPointer object that is meant to appear when clicking on an object is not.

I've checked the geometry displays correctly in a sandbox, and when I change it to a 3d object rather than shapeGeometry it does display, but I cannot figure out why it is not displaying how I want it to.

The project is at https://voxelverse.jackgreenearth.org, and the code is quite long, but it is here to read in its totality below as it might need the whole context to discover the error. I've tried myself looking through the code, and I've tried searching the web and asking LLMs, but I couldn't figure it out, so please help me, fellow humans.

Tap for code

"use strict";

import \* as THREE from 'three';

import {GLTFLoader} from 'three/addons/loaders/GLTFLoader.js';

\


const loader = new GLTFLoader();

const textureLoader = new THREE.TextureLoader();

const manager = THREE.DefaultLoadingManager;

\


// Basic functions

\


function ls(id) {

return(localStorage.getItem(id));

};

\


function setLs(id, val) {

localStorage.setItem(id, val);

};

\


function byId(id) {

return(document.getElementById(id));

};

\


function bySel(sel) {

return(document.querySelector(sel));

};

\


function byClass(id) {

return(document.getElementsByClassName(id));

};

\


function toTitleCase(str) {

return str.replace(

/\w\S\*/g,

function(txt) {

return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();

}

);

};

\


function randInt(max) {

return Math.floor(Math.random() \* (max));

};

\


function getRandomFloat(min, max, decimals) {

return(parseFloat((Math.random() \* (max - min) + min).toFixed(decimals)));

};

\


function confine(value, min, max) {

if(value < min) {

return(min);

} else if(value > max) {

return(max);

} else {

return(value);

};

};

\


function wrap(value, min, max) {

const range = max - min;

\


if(value < min) {

return(wrap(value + range, min, max));

} else if(value > max) {

return(wrap(value - range, min, max));

} else {

return(value);

};

};

\


function removeFromArray(array, forDeletion) {

return(array.filter(item => !forDeletion.includes(item)));

};

\


function radToDeg(radians) {

return radians \* (180 / PI);

}

\


function range(start, stop, step = 1) {

if (stop === undefined) {

stop = start;

start = 0

}

return Array.from({ length: (stop - start) / step }, (\_, i) => start + (i \* step));

}

\


function between(variable, min, max, inclusive='min') {

switch(inclusive) {

case 'none':

return((variable > min) && (variable < max));

break;

case 'both':

return((variable >= min) && (variable <= max));

break;

case 'min':

return((variable >= min) && (variable < max));

break;

case 'max':

return((variable > min) && (variable <= max));

break;

}

}

\


function download(data, filename, type) {

var file = new Blob(\[data], {type: type});

if (window\.navigator.msSaveOrOpenBlob) // IE10+

window\.navigator.msSaveOrOpenBlob(file, filename);

else { // Others

var a = document.createElement("a"),

url = URL.createObjectURL(file);

a.href = url;

a.download = filename;

document.body.appendChild(a);

a.click();

setTimeout(function() {

document.body.removeChild(a);

window\.URL.revokeObjectURL(url);

}, 0);

};

};

\


function log(text) {

console.log(text);

};

\


function distance2d(x1, y1, x2, y2) {

return(Math.sqrt(

(Math.abs(x1 - x2) \*\* 2) +

(Math.abs(y1 - y2) \*\* 2)

));

};

\


function distance3d(p1 = new THREE.Vector3(0, 0, 0), p2 = new THREE.Vector3(0, 0, 0)) {

return(Math.sqrt((distance2d(p1.x, p1.y, p2.x, p2.y) \*\* 2) + (Math.abs(p1.z - p2.z) \*\* 2)));

};

\


let totalElementsToLoad = 0;

let numberOfElementsLoaded = 0;

\


function onAllElementsLoaded() {

\


}

\


function load(path, type, functionOnLoad) {

totalElementsToLoad += 1;

\


if(type == 'html') {

fetch(path)

.then(response => response.text())

.then(html => {

let doc = new DOMParser().parseFromString(html, "text/html");

\


functionOnLoad(doc);

\


// If all elements to load have been loaded, execute the relevant function

numberOfElementsLoaded += 1;

if(numberOfElementsLoaded == totalElementsToLoad) {

onAllElementsLoaded();

}

})

.catch(error => {

console.error(error);

});

} else if(type == 'json') {

fetch(path)

.then(response => response.json()) // parse the response as JSON

.then(json => {

functionOnLoad(json);

\


// If all elements to load have been loaded, execute the relevant function

numberOfElementsLoaded += 1;

if(numberOfElementsLoaded == totalElementsToLoad) {

onAllElementsLoaded();

}

})

.catch(error => {

console.error(error);

});

}

}

\


// Setup

\


const PI = 3.1415926535897932384626433832795028841971;

\


// Objects

\


let orientation = {

'absolute': false,

'alpha': 0,

'beta': 0,

'gamma': 0

}

\


// vars

const fps = 60;

\


let keysDown = \[];

let pointerPosition = {'x': 0, 'y': 0, 'positions': \[{'clientX': 0, 'clientY': 0}], 'type': 'mouse'};

\


// Camera

let cameraRotation = new THREE.Euler(0, 0, 0, 'YXZ');

let cameraTargetRotation = {'x': 0, 'y': 0, 'z': 0};

const cameraRotationSensitivity = 0.002;

\


// Other variables

let logicInterval;

\


// Load default settings

let defaultSettings;

\


load("/assets/json/default-settings.json", 'json', function(defset) {

defaultSettings = defset;

\


// Create custom settings

if(!Object.keys(localStorage).includes('settings')) {

setLs('settings', JSON.stringify({}));

};

\


onSettingsLoad();

});

\


function settingURL(url, addValue=true) {

return('children/' + url.split('/').join('/children/') + (addValue ? '/value' : ''));

}

\


function customiseSetting(url, value) {

url = settingURL(url).split('/');

\


let newSettings;

\


function recursiveSet(object, list, index, setTo) {

// If the current component is the last one, assign the value

if(index == list.length - 1) {

object\[list\[index]] = setTo;

return(object);

} else {

// Check if it already contains the value

if(object.hasOwnProperty(list\[index])) {

object\[list\[index]] = recursiveSet(object\[list\[index]], list, index + 1, setTo);

} else {

object\[list\[index]] = recursiveSet({}, list, index + 1, setTo);

}

return(object);

}

};

\


newSettings = recursiveSet(JSON.parse(ls('settings')), url, 0, value);

\


setLs('settings', JSON.stringify(newSettings));

}

\


function getSetting(url, addValue) {

url = settingURL(url, addValue).split('/');

\


function recursiveGet(object, list, index) {

// If the current component is the last one, return the value

if (index == list.length - 1) {

return object\[list\[index]];

} else {

// Check if it contains the value

if (object.hasOwnProperty(list\[index])) {

return recursiveGet(object\[list\[index]], list, index + 1);

} else {

return null; // No such setting

}

}

}

\


// Try to find it in local settings first, otherwise get it from defaultSettings

const localGet = recursiveGet(JSON.parse(ls('settings')), url, 0);

if(localGet == null) {

return(recursiveGet(defaultSettings, url, 0));

} else {

return(localGet);

}

}

\


// First, lets define some functions

// Rendering functions

\


// Thanks, https\://discourse.threejs.org/t/roundedrectangle-squircle/28645!

function roundRectangleGeometry(w, h, r, s) { // width, height, radius corner, smoothness

// helper const's

const wi = w / 2 - r; // inner width

const hi = h / 2 - r; // inner height

const w2 = w / 2; // half width

const h2 = h / 2; // half height

const ul = r / w; // u left

const ur = ( w - r ) / w; // u right

const vl = r / h; // v low

const vh = ( h - r ) / h; // v high

let positions = \[

-wi, -h2, 0, wi, -h2, 0, wi, h2, 0,

-wi, -h2, 0, wi, h2, 0, -wi, h2, 0,

-w2, -hi, 0, -wi, -hi, 0, -wi, hi, 0,

-w2, -hi, 0, -wi, hi, 0, -w2, hi, 0,

wi, -hi, 0, w2, -hi, 0, w2, hi, 0,

wi, -hi, 0, w2, hi, 0, wi, hi, 0

];

let uvs = \[

ul, 0, ur, 0, ur, 1,

ul, 0, ur, 1, ul, 1,

0, vl, ul, vl, ul, vh,

0, vl, ul, vh, 0, vh,

ur, vl, 1, vl, 1, vh,

ur, vl, 1, vh, ur, vh

];

let phia = 0;

let phib, xc, yc, uc, vc, cosa, sina, cosb, sinb;

for (let i = 0; i < s \* 4; i ++) {

phib = Math.PI \* 2 \* ( i + 1 ) / ( 4 \* s );

cosa = Math.cos( phia );

sina = Math.sin( phia );

cosb = Math.cos( phib );

sinb = Math.sin( phib );

xc = i < s || i >= 3 \* s ? wi : - wi;

yc = i < 2 \* s ? hi : -hi;

positions.push( xc, yc, 0, xc + r \* cosa, yc + r \* sina, 0, xc + r \* cosb, yc + r \* sinb, 0 );

uc = i < s || i >= 3 \* s ? ur : ul;

vc = i < 2 \* s ? vh : vl;

uvs.push( uc, vc, uc + ul \* cosa, vc + vl \* sina, uc + ul \* cosb, vc + vl \* sinb );

phia = phib;

}

const geometry = new THREE.BufferGeometry( );

geometry.setAttribute( 'position', new THREE.BufferAttribute( new Float32Array( positions ), 3 ) );

geometry.setAttribute( 'uv', new THREE.BufferAttribute( new Float32Array( uvs ), 2 ) );

return geometry;

}

\


// Render

function render() {

requestAnimationFrame(render);

leftRenderer.render(scene, leftCamera);

rightRenderer.render(scene, rightCamera);

\


framesSoFar++;

};

\


// Functions

function setCameraRotation() {

// Calculate drag

cameraRotation.x = Number((cameraRotation.x + ((cameraTargetRotation.x - cameraRotation.x) / getSetting('Input/Mouse/Camera Rotation Drag'))).toFixed(5));

cameraRotation.y = Number((cameraRotation.y + ((cameraTargetRotation.y - cameraRotation.y) / getSetting('Input/Mouse/Camera Rotation Drag'))).toFixed(5));

cameraRotation.z = Number((cameraRotation.z + ((cameraTargetRotation.z - cameraRotation.z) / getSetting('Input/Mouse/Camera Rotation Drag'))).toFixed(5));

// Update cameras

for(let camera of \[leftCamera, rightCamera]) {

camera.rotation.set(cameraRotation.x, cameraRotation.y, cameraRotation.z, 'YXZ');

}

\


const eyeGap = getSetting('Quick Settings/Eye Gap');

\


// Set camera positions

leftCamera.position.x = -1 \* eyeGap \* Math.sin(cameraRotation.y);

leftCamera.position.z = -1 \* eyeGap \* Math.cos(cameraRotation.y);

rightCamera.position.x = eyeGap \* Math.sin(cameraRotation.y);

rightCamera.position.z = eyeGap \* Math.cos(cameraRotation.y);

\


byId('camera-target-rot-x').innerHTML = cameraTargetRotation.x.toFixed(2);

byId('camera-target-rot-y').innerHTML = cameraTargetRotation.y.toFixed(2);

byId('camera-target-rot-z').innerHTML = cameraTargetRotation.z.toFixed(2);

byId('camera-rot-x').innerHTML = cameraRotation.x.toFixed(2);

byId('camera-rot-y').innerHTML = cameraRotation.y.toFixed(2);

byId('camera-rot-z').innerHTML = cameraRotation.z.toFixed(2);

\


byId('camera-left-rot-x').innerHTML = leftCamera.rotation.x.toFixed(2);

byId('camera-left-rot-y').innerHTML = leftCamera.rotation.y.toFixed(2);

byId('camera-left-rot-z').innerHTML = leftCamera.rotation.z.toFixed(2);

}

\


function takeScreenshot() {

downloadCanvasImage(document.getElementById('game-canvas'), gameName + ' screenshot');

sendAlert('Screenshot Taken!', 'tick');

};

\


function takePanorama() {

const canvas = document.getElementById('game-canvas');

const height = canvas.height;

const width = canvas.width \* (360 / (camera.fov \* camera.aspect));

let newCanvas = document.createElement('canvas');

newCanvas.height = height;

newCanvas.width = width;

newCanvas.style.display = 'none';

let context = newCanvas.getContext("2d");

document.body.appendChild(newCanvas);

for(let x = 0; x < width; x++) {

// Rotate

cameraRotation.y += ((2 \* PI) / width);

let calculatedRotation = rotationToAbsolute(playerPosition, cameraRotation);

camera.rotation.set(calculatedRotation.x, calculatedRotation.y, calculatedRotation.z, 'YXZ');

renderer.render(scene, camera);

const gl = renderer.getContext();

// Get canvas data

const pixelData = new Uint8ClampedArray(1 \* height \* 4);

const reversedPixelData = new Uint8ClampedArray(1 \* height \* 4);

gl.readPixels((canvas.width / 2), 0, 1, height, gl.RGBA, gl.UNSIGNED\_BYTE, pixelData);

for (let i = 0; i < height; i++) {

for (let j = 0; j < 4; j++) {

reversedPixelData\[i\*4 + j] = pixelData\[(height - i - 1)\*4 + j];

};

};

const imageData = new ImageData(reversedPixelData, 1, height);

context.putImageData(imageData, x, 0);

};

downloadCanvasImage(newCanvas, gameName + ' panorama');

newCanvas.remove();

sendAlert('Panoramic screenshot taken!', 'tick');

};

\


function setRotation(object, rotation) {

object.rotation.set(rotation.x, rotation.y, rotation.z);

};

\


function downloadCanvasImage(canvas, name) {

let canvasImage = canvas.toDataURL('image/png');

// this can be used to download any image from webpage to local disk

let xhr = new XMLHttpRequest();

xhr.responseType = 'blob';

xhr.onload = function () {

let a = document.createElement('a');

a.href = window\.URL.createObjectURL(xhr.response);

a.download = name;

a.style.display = 'none';

document.body.appendChild(a);

a.click();

a.remove();

};

xhr.open('GET', canvasImage); // This is to download the canvas image

xhr.send();

};

\


function xyToRealPosRot(x, y, distance) {

let realX, realY, realZ, rotX, rotY, rotZ;

\


// Position is an object {x: x, y: y} x determines which face it will be on horizontally, and y determines if it will be on the top or the bottom

// Beyond 400, x position wraps

x = wrap(x, 0, 400);

log('x before: ' + x)

const horizontalFace = (x / 100) % 4;

//rotY = (x / 400) \* (1) // horizontalFace);

\


// The top of the screen is y 100, the bottom is y -100, and the horizontals are between -50 and 50

realY = confine(y, -100, 100);

\


// Calculate real position

const unit = getSetting('Display/UI/Distance') / 50;

\


let forward = getSetting('Display/UI/Distance');

\


const bevel = getSetting('Display/UI/Bevel');

\


rotX = 0;

\


// If it is horizontal...

if(between(y, -50 + bevel, 50 - bevel)) {

realY = y;

rotX = 0;

} else if(y < -50 - bevel) {

// If it is on the lower face

realY = -50;

forward = (y + 100) \* unit;

rotX = -(PI / 2);

} else if(y >= 50 + bevel) {

// If it is on the upper face

realY = 50;

forward = (y - 100) \* unit;

//side = unit \* (((x - 50) % 100) + 50);

rotX = (PI / 2);

} else if(between(y, -50 - bevel, -50 + bevel)) {

// If it is on the lower bevel

realY = -50 - ((y + 50) / 2);

rotX = (PI / 4);

} else if(between(y, 50 - bevel, 50 + bevel)) {

// If it is on the upper bevel

realY = 50 + ((y - 50) / 2) ;

rotX = -(PI / 4);

}

\


realY = realY \* unit;

\


let flip = false;

\


/\*if(

(horizontalFace >= 0 && horizontalFace < 0.5) ||

(horizontalFace >= 1.5 && horizontalFace < 2.5) ||

(horizontalFace >= 3.5 && horizontalFace < 4)

) {

flip = true;

}\*/

\


let angle = (x / 400) \* (PI \* 2);

realX = Math.sin(angle) \* forward;

realZ = Math.cos(angle) \* forward;

rotY = angle;

log('rot y: ' + rotY)

\


log({

'x': realX,

'y': realY,

'forward': forward,

})

\


// Take distance into account

realX \*= distance;

realY \*= distance;

realZ \*= distance;

\


return({

'position': new THREE.Vector3(realX, realY, realZ),

'rotation': new THREE.Euler(rotX, rotY, rotZ, 'YXZ'),

'flip': flip

});

}

\


function addWidget({

name = '',

position = {'x': 0, 'y': 0},

rotation = {'x': 0, 'y': 0, 'z': 0},

distance = 1,

size = {'x': 10, 'y': 10},

radius = 3,

shape = 'rRect',

background = '#000000',

opacity,

textStyle = {

'align': 'center',

'weight': 0, // Range is 0 to 10

'font': 'DINRoundPro,arial,sans-serif',

'color': '#b0b0b0',

'vertical-align': 'center',

'font-size': 1 // Uses the same sizing system as the rest of the UI, so one unit of text is also one unit of object

},

textContent = '',

onclick = function() {},

onlongpress = function() {},

onhover = function() {},

onhoverexit = function() {},

ontruehover = function() {}

}) {

const realPosRot = xyToRealPosRot(position.x, position.y, distance);

log(realPosRot)

const realPos = realPosRot.position;

let realRot = realPosRot.rotation;

\


realRot.x += rotation.x;

realRot.y += rotation.y;

realRot.z = rotation.z;

\


// Calculate real size

const unit = getSetting('Display/UI/Distance') / 100;

\


let width = unit \* size.x;

let height = unit \* size.y;

radius \*= unit;

const scale = getSetting('Display/UI/Scale/General');

width \*= scale;

height \*= scale;

radius \*= scale;

\


// Set mesh geometry

let geometry;

switch(shape) {

case 'rRect':

geometry = roundRectangleGeometry(width, height, radius, 10);

break;

case 'rect':

geometry = new THREE.PlaneGeometry(width, height);

break;

case 'circle':

geometry = new THREE.CircleGeometry((width + height) / 2, 32);

break;

}

let material;

\


if(opacity == undefined) {

opacity = 1;

}

\


if(textContent == '') {

if(background\[0] == '/') {

loadTexture(background, function(texture) {

material = new THREE.MeshBasicMaterial({

map: texture,

side: THREE.DoubleSide,

opacity: opacity,

transparent: true

});

onTextureLoad(material);

})

} else {

material = new THREE.MeshBasicMaterial({

color: background,

side: THREE.DoubleSide,

opacity: opacity,

transparent: true

});

onTextureLoad(material);

}

} else {

function prepareText(canvas) {

// Proceed to prepare the canvas with the text

ctx.font = \`${textStyle\["font-size"]}em ${textStyle\["font"]}\`;

ctx.textAlign = textStyle\["align"];

ctx.fillStyle = textStyle\["color"];

ctx.fillText(textContent, 0, 0);

// Compose the text onto the background

const composedTexture = new THREE.CanvasTexture(canvas);

\


// Generate the material

material = new THREE.MeshBasicMaterial({

map: composedTexture,

side: THREE.DoubleSide,

transparent: true,

alphaTest: 0.5

});

\


onTextureLoad(material);

}

\


// Initialize tmpcanvas only when needed

const tmpcanvas = document.createElement('canvas');

tmpcanvas.width = width;

tmpcanvas.height = height;

const ctx = tmpcanvas.getContext('2d');

\
\


// Fill the background first

if (background\[0] == '/') {

loadTexture(background, function(texture) {

ctx.fillStyle = texture;

ctx.fillRect(0, 0, width, height);

\


prepareText(tmpcanvas);

})

} else {

ctx.fillStyle = background;

ctx.fillRect(0, 0, width, height);

\


prepareText(tmpcanvas);

}

}

function onTextureLoad(material) {

// Create a mesh with the geometry and the material

let mesh = new THREE.Mesh(geometry, material);

\


mesh.name = name;

\


mesh.position.set(realPos.x, realPos.y, realPos.z );

mesh.rotation.set(realRot.x, realRot.y, realRot.z);

\


if(realPosRot.flip) {

mesh.scale.x = -1;

}

\


mesh.onclick = onclick;

mesh.onlongpress = onlongpress;

mesh.onhoverexit = onhoverexit;

mesh.ontruehover = ontruehover;

mesh.onchover = onhover;

\


scene.add(mesh);

};

}

\


function transitionWidget(name, property, newProperty, time, condition) {

if(condition != null) {

}

}

\


// three.js Scene setup

const scene = new THREE.Scene();

\


// three functions

\


function loadTexture(path, onload) {

textureLoader.load(path, function (texture) {

onload(texture);

}, undefined, function (error) {

console.error(error);

});

};

\


// Define objects to make them global, they will mostly be only added to the scene when settings are loaded

let sun;

let wallpaper;

let cameraStream;

let pointer;

\


let pointerMaterial = new THREE.MeshBasicMaterial({

color: "hsl(0, 100%, 50%)",

side: THREE.DoubleSide

});

\


let segmentShape = new THREE.Shape();

let segmentGeometry = new THREE.ShapeGeometry(segmentShape);

let segmentPointer = new THREE.Mesh(segmentGeometry, pointerMaterial);

segmentPointer.name = 'segmentPointer';

\


function setSegmentPointer(angle = 0, radius = 0.1, rotation = new THREE.Euler(0, 0, 0), clockwise=true) {

let oldGeometry = segmentPointer.geometry;

\


let segmentShape = new THREE.Shape();

segmentShape.moveTo(0, 0);

segmentShape.arc(0, 0, radius, 0, angle, clockwise);

segmentShape.lineTo(0, 0);

\


let extrudeSettings = {

steps: 1,

depth: 0.1,

bevelEnabled: false

};

\


let segmentGeometry = new THREE.ExtrudeGeometry(segmentShape, extrudeSettings);

segmentPointer.geometry = segmentGeometry;

\


oldGeometry.dispose();

\


segmentPointer.rotation.set(rotation);

}

\


// Camera stuff

let cameraViewDistance;

\


// Setup cameras

let leftCamera;

let rightCamera;

let leftRenderer;

let rightRenderer;

\


function setRendererSize() {

for(let renderer of \[leftRenderer, rightRenderer]) {

let canvas = renderer.domElement;

renderer.setSize(

canvas.offsetWidth \* getSetting('Display/Anti-alias'),

canvas.offsetHeight \* getSetting('Display/Anti-alias'),

false

);

}

}

\


function updateCameraAspectRatio() {-0.2

for(let camera of \[leftCamera, rightCamera]) {

let canvas = leftRenderer.domElement;

camera.aspect = canvas.offsetWidth / canvas.offsetHeight;

camera.updateProjectionMatrix();

}

}

\


// temp

function startAssistant() {

log('assisstant')

}

\


// When settings are loaded, start settings stuff up

function onSettingsLoad() {

// Add sun

sun = new THREE.PointLight(0xffffff, getSetting('Quick Settings/Brightness'));

scene.add(sun);

\


// Pointers

pointer = new THREE.Mesh(new THREE.IcosahedronGeometry(getSetting('Display/UI/Pointer/Size'), 1), pointerMaterial);

pointer.name = 'pointer';

scene.add(pointer);

\


pointerMaterial = new THREE.MeshBasicMaterial(

{color: "hsl(" + (getSetting('Quick Settings/Theme Hue') + getSetting('Display/UI/Pointer/Hue Shift')) + ", 100%, 50%)"}

);

pointer.material = pointerMaterial;

segmentPointer.material = pointerMaterial;

\


// Add wallpaper

let wallpaperURL;

if(getSetting('Display/UI/Wallpaper/Use Direct Link') == true) {

wallpaperURL = getSetting('Display/UI/Wallpaper/Direct Link');

} else {

wallpaperURL = getSetting('Display/UI/Wallpaper/Wallpapers Folder') + '/' + getSetting('Display/UI/Wallpaper/Wallpaper');

}

\


loadTexture(wallpaperURL, function(texture) {

let material = new THREE.MeshStandardMaterial({

map: texture,

side: THREE.BackSide,

transparent: true,

opacity: getSetting('Display/UI/Wallpaper/Opacity')

});

\


wallpaper = new THREE.Mesh(

new THREE.IcosahedronGeometry(

getSetting('Display/UI/Distance') \* getSetting('Display/UI/Wallpaper/Distance') \* getSetting('Display/UI/Testing Size Multiplier'), 4),

material

);

wallpaper.scale.x = -1;

wallpaper.name = "wallpaper";

scene.add(wallpaper);

});

\


// Setup cameras

cameraViewDistance = getSetting('Display/UI/Distance') \* getSetting('Display/UI/Testing Size Multiplier') \* 2; // Keep this down to destroy lag

leftCamera = new THREE.PerspectiveCamera(80, 1, 0.001, cameraViewDistance);

rightCamera = new THREE.PerspectiveCamera(80, 1, 0.001, cameraViewDistance);

\


// Setup renderers

leftRenderer = new THREE.WebGLRenderer({canvas: byId('left-canvas'), antialias: true, logarithmicDepthBuffer: true, preserveDrawingBuffer: true, alpha: true});

rightRenderer = new THREE.WebGLRenderer({canvas: byId('right-canvas'), antialias: true, logarithmicDepthBuffer: true, preserveDrawingBuffer: true, alpha: true});

\


updateCameraAspectRatio();

setRendererSize();

\


window\.addEventListener('resize', function() {

updateCameraAspectRatio();

setRendererSize();

});

\


// Setup control center

const baseY = getSetting('Display/Control Center/Switch To Bottom') ? -100 : 100;

const backgroundFolder = getSetting('Display/Control Center/Icon Folder');

function createTileGrid(scale, x, y, farness, tiles, name) {

let counter = 0;

log(tiles)

addWidget({

position: {'x': x, 'y': baseY + y},

size: {'x': 3 \* scale, 'y': 3 \* scale},

background: "hsl(" + getSetting('Quick Settings/Theme Hue') + ", 50%, 80%)",

name: name + ' background',

radius: 0.5 \* scale,

onhover: openTileGroup(name)

});

for(let widgetY = 1; widgetY >= -1; widgetY--) {

for(let widgetX = -1; widgetX <=1; widgetX++) {

if(counter < tiles.length) {

if(tiles\[counter].folder) {

createTileGrid(scale / 3, (widgetX \* scale), (widgetY \* scale), farness \* 0.99, tiles\[counter].children);

} else {

log('scale' + scale)

addWidget({

position: {'x': x + (widgetX \* scale), 'y': baseY + y + (widgetY \* scale)},

size: {'x': scale, 'y': scale},

background: backgroundFolder + '/' + tiles\[counter].icon + '.svg',

name: tiles\[counter].name,

group: name,

radius: scale / 3,

distance: farness \* 0.99

});

log('added widget control center')

};

} else {

break;

};

counter++;

};

if(counter >= tiles.length) {

break;

};

};

};

\


createTileGrid(

getSetting('Display/UI/Scale/Control Center') \* 1.5,

0,

baseY,

1,

getSetting('Display/Control Center/Tiles'),

getSetting('Display/Control Center/Tiles', false)\['name']

);

// Quick function

let quickFunction = getSetting('Display/Control Center/Quick Function', false);

\


addWidget({

position: {'x': 0, 'y': getSetting('Display/Control Center/Switch To Bottom') ? 100 : -100},

background: '/assets/images/icons/control\_center/' + quickFunction.icon + '.svg',

name: quickFunction.name,

onclick: quickFunction.onclick

});

\


// testing

addWidget({

position: {'x': 0, 'y': -50},

background: '/assets/images/icons/control\_center/torch.svg',

name: "torch"

});

addWidget({

position: {'x': 200, 'y': 10},

background: '/assets/images/icons/control\_center/screencast.svg',

name: "screencast"

});

for(let i of range(16)) {

addWidget({

position: {'x': i \* 25, 'y': 0},

background: 'hsl(' + getSetting('Quick Settings/Theme Hue') + ', 100%, ' + ((i / 16) \* 100) + '%)',

name: "test" + i,

textContent: '',//i.toString()

'onclick': function() {

log('click' + i);

},

'onhover': function() {

log('hover' + i);

}

});

}

};

\


function updateSetting(url, value) {

customiseSetting(url, value);

\


switch(url) {

case 'Display/UI/Wallpaper/Opacity':

wallpaper.material.opacity = value;

break;

};

};

\


// Start

\


// Setup the camera stream

function setupCameraStream() {

function handleSuccess(stream) {

cameraStream = document.createElement('video');

cameraStream.style.transform = 'rotate(270deg)';

cameraStream.srcObject = stream;

cameraStream.play();

\


let texture = new THREE.VideoTexture(cameraStream);

texture.minFilter = THREE.LinearFilter;

texture.magFilter = THREE.LinearFilter;

scene.background = texture;

\


customiseSetting('Display/UI/Wallpaper/Opacity', 0); // Temporary until GUI settings are introduced

}

\


function handleError(error) {

// Set wallpaper opacity to 1

updateSetting('Display/UI/Wallpaper/Opacity', 1);

\


log('Unable to access the camera/webcam.');

}

\


navigator.mediaDevices.getUserMedia({video: {facingMode: "environment"}})

.then(handleSuccess)

.catch(function(error) {

if (error.name === 'OverconstrainedError') {

// Fallback to default video settings

navigator.mediaDevices.getUserMedia({video: true})

.then(handleSuccess)

.catch(handleError);

} else {

// Handle other errors

handleError(error);

}

});

};

\


// Fullscreen and pointer lock, request fullscreen mode for the element

function openFullscreen(elem, then) {

if (elem.requestFullscreen) {

elem.requestFullscreen().then(then);

} else if (elem.webkitRequestFullscreen) { /\* Safari \*/

elem.webkitRequestFullscreen().then(then);

} else if (elem.msRequestFullscreen) { /\* IE11 \*/

elem.msRequestFullscreen().then(then);

}

}

// Request pointer lock

function requestPointerLock(myTargetElement) {

const promise = myTargetElement.requestPointerLock({

unadjustedMovement: true,

});

\


if (!promise) {

log("disabling mouse acceleration is not supported");

return;

}

\


return promise

.then(() => log("pointer is locked"))

.catch((error) => {

if (error.name === "NotSupportedError") {

// Some platforms may not support unadjusted movement.

// You can request again a regular pointer lock.

return myTargetElement.requestPointerLock();

}

});

}

\


function lockPointer() {

requestPointerLock(byId('body'));

document.addEventListener("pointerlockchange", lockChangeAlert, false);

};

\


function lockChangeAlert() {

if (document.pointerLockElement === byId('body')) {

document.addEventListener("mousemove", updatePosition, false);

} else {

document.removeEventListener("mousemove", updatePosition, false);

}

}

\


function updatePosition(e) {

onLockedMouseMove(e.movementX, e.movementY);

};

\


function fullscreenAndPointerLock() {

openFullscreen(byId('body'), function() {

lockPointer();

});

}

\


function permission() {

// Check if the device supports deviceorientation and requestPermission

if (typeof(DeviceMotionEvent) !== "undefined" && typeof(DeviceMotionEvent.requestPermission) === "function") {

// Request permission

DeviceMotionEvent.requestPermission()

.then(response => {

// Check the response

if (response == "granted") {};

})

.catch(console.error); // Handle errors

} else {

// Device does not support deviceorientation

log("DeviceOrientationEvent is not defined");

}

}

\


async function keepScreenAwake() {

// Create a reference for the Wake Lock.

let wakeLock = null;

\


// create an async function to request a wake lock

try {

wakeLock = await navigator.wakeLock.request("screen");

log("Wake Lock is active!");

} catch (err) {

// The Wake Lock request has failed - usually system related, such as battery.

log(\`${err.name}, ${err.message}\`);

}

}

\


let framesSoFar = 0;

\


function startFPSCount() {

byId('logic-fps').innerHTML = fps.toString();

\


let renderFPSInterval = setInterval(function() {

byId('render-fps').innerHTML = framesSoFar.toString();

framesSoFar = 0;

}, 1000);

}

\


function start() {

byId('loading-screen').style.display = 'none';

setupCameraStream();

startLogic();

startFPSCount();

render();

permission();

fullscreenAndPointerLock();

keepScreenAwake();

\


byId('left-canvas').addEventListener('click', fullscreenAndPointerLock);

byId('right-canvas').addEventListener('click', fullscreenAndPointerLock);

};

\


// Loading

byId('loading-bar').style.display = 'block';

\


manager.onProgress = function (url, itemsLoaded, itemsTotal) {

//log('Loading file: ' + url + '.\nLoaded ' + itemsLoaded + ' of ' + itemsTotal + ' files.');

\


byId('loading-bar-fg').style.setProperty('--size', ((itemsLoaded / itemsTotal) \* 100) + '%');

};

\


manager.onError = function (url) {

log('There was an error loading ' + url);

};

\


manager.onLoad = function ( ) {

setTimeout(function() {

byId('loading-bar').style.display = 'none';

byId('play').style.display = 'block';

}, 500);

byId('play').addEventListener('click', start);

};

\


function openTileGroup(group) {

}

\


// Logic

let pointerRaycast = new THREE.Raycaster();

let previousIntersection = pointerRaycast.intersectObjects(scene.children);

let clicking = false;

\


function logic() {

// Set camera rotation

if(cameraTargetRotation.x != cameraRotation.x || cameraTargetRotation.y != cameraRotation.y) {

setCameraRotation();

};

\


// Update pointer

pointerRaycast.set(

new THREE.Vector3(0, 0, 0),

leftCamera.getWorldDirection(new THREE.Vector3())

);

\


// Check if the pointer is itersecting with any object

\


const intersections = pointerRaycast.intersectObjects(scene.children);

if (intersections.length > 0) {

for(let intersection of intersections) {

// If it's intersecting with itself, don't do anything

if(intersection.object.name == 'pointer' || intersection.object.name == 'segmentPointer') {

return;

} else {

// If it's intersecting with an object, copy that intersection's position, and start to click

const point = intersections\[0].point;

pointer.position.copy(point);

\


// Truehover

if(Object.keys(intersection.object).includes('ontruehover')) {

// Prevent hover being continuously trigggered

if(previousIntersection.uuid != intersections\[0].uuid) {

intersection.object.ontruehover();

}

}

log('truehover')

\


// Start click after grace period, if object is clickable

if(

Object.keys(intersection.object).includes('onclick') ||

Object.keys(intersection.object).includes('onhover') ||

Object.keys(intersection.object).includes('onhoverexit') ||

Object.keys(intersection.object).includes('onlongpress')

) {

let gracePeriod = setTimeout(function() {

// onhover

if(Object.keys(intersection.object).includes('onhover')) {

intersection.object.onhover();

}

log('hover')

\


// Start click

if(Object.keys(intersection.object).includes('onclick') && (!clicking)) {

clicking = true;

\


let fullness = 0;

\


// Manage pointers

scene.add(segmentPointer);

segmentPointer.position.copy(pointer);

scene.remove(pointer);

let startClick = setInterval(function() {

fullness += (PI \* 2) / (fps \* getSetting('Input/Eye Click/Duration/Pre-click duration'));

setSegmentPointer(

fullness,

getSetting('Display/UI/Pointer/Size') \* getSetting('Display/UI/Pointer/Clicking Size'),

intersection.object.rotation

);

\


byId('pointer-angle').innerHTML = fullness.toFixed(4);

\


// On forfeit

let forfeitDistance = distance3d(point, pointerRaycast.intersectObjects(scene.children)\[0].point);

if(forfeitDistance > getSetting('Input/Eye Click/Movement limit')) {

log('forfeit ' + forfeitDistance)

clearInterval(startClick);

afterClick();

}

\


if(fullness >= PI \* 2) {

log('click')

intersection.object.onclick();

clearInterval(startClick);

\


if(Object.keys(intersection.object).includes('onlongpress')) {

// Start longpress

fullness = 0;

let startLongPress = setInterval(function() {

fullness += (PI \* 2) / (fps \* getSetting('Input/Eye Click/Duration/Long-press duration'));

setSegmentPointer(

fullness,

getSetting('Display/UI/Pointer/Size') \* getSetting('Display/UI/Pointer/Clicking Size'),

intersection.object.rotation,

false

);

byId('pointer-angle').innerHTML = fullness.toFixed(4);

\


let forfeitDistance = distance3d(point, pointerRaycast.intersectObjects(scene.children)\[0].point);

if(forfeitDistance > getSetting('Input/Eye Click/Movement limit')) {

log('forfeitlongpress')

clearInterval(startLongPress);

afterClick();

};

\


// On click

if(fullness >= PI \* 2) {

intersection.object.onlongpress();

log('longpress')

clearInterval(startLongPress);

afterClick();

}

}, 1000 / fps);

} else {

afterClick();

}

}

}, 1000 / fps);

};

}, getSetting('Input/Eye Click/Delayed hover duration') \* 1000)

return;

} else {

afterClick();

}

\


function afterClick() {

// Update previous intersection

previousIntersection = intersections;

previousIntersection.intersection = intersection;

\


// Onhoverexit

if(intersection.object.uuid != previousIntersection.intersection.object.uuid) {

previousIntersection.object.onhoverexit();

}

\


clicking = false;

log('afterclick')

\


// Change back pointers

scene.remove(segmentPointer);

scene.add(pointer);

\


return;

}

}

}

};

};

\


function startLogic() {

logicInterval = setInterval(logic, 1000 / fps);

};

\


function stopLogic() {

clearInterval(logicInterval);

cameraStream.pause();

};

\


// Input

function onLockedMouseMove(xMotion, yMotion) {

cameraTargetRotation.x = confine(cameraTargetRotation.x - (yMotion \* cameraRotationSensitivity), -0.5 \* PI, 0.6 \* PI);

if(wrap(cameraTargetRotation.y - (xMotion \* cameraRotationSensitivity), -PI, PI) != (cameraTargetRotation.y - (xMotion \* cameraRotationSensitivity))) {

cameraRotation.y = wrap(cameraTargetRotation.y - (xMotion \* cameraRotationSensitivity), -PI, PI);

};

cameraTargetRotation.y = wrap(cameraTargetRotation.y - (xMotion \* cameraRotationSensitivity), -PI, PI);

setCameraRotation();

};

\


// Setup buttons

byId('toggle-debug').addEventListener('click', function(event) {

if(byId('debug-menu').style.display == 'block') {

byId('debug-menu').style.display = 'none';

} else {

byId('debug-menu').style.display = 'block';

}

});

\


// Keypress manager

const keysToPreventDefault = \['alt', '/', 'f1', 'f2', 'f3'];

\


function putKeyDown(key) {

if(!keysDown.includes(key.toLowerCase())) {

keysDown.push(key.toLowerCase());

};

};

\


function putKeyUp(key) {

keysDown = removeFromArray(keysDown, \[key.toLowerCase()]);

};

\


document.addEventListener('keydown', function(e) {

if(keysToPreventDefault.includes(e.key.toLowerCase())) {

e.preventDefault();

};

putKeyDown(e.key);

});

\


document.addEventListener('keyup', function(e) {

putKeyUp(e.key);

});

\


// Pointer position

document.addEventListener('mousemove', function(e) {

pointerPosition = {'x': e.clientX, 'y': e.clientY, 'positions': \[{'clientX': e.clientX, 'clientY': e.clientY}], 'type': 'mouse'};

});

\


document.addEventListener('touchmove', function(e) {

pointerPosition = {'x': e.touches\[0].clientX, 'y': e.touches\[0].clientY, 'positions': e.touches, 'type': 'touch'};

});

\


// Gyrometer

window\.addEventListener("deviceorientation", function(event) {

orientation = {

'absolute': event.absolute,

'alpha': event.alpha,

'beta': event.beta,

'gamma': event.gamma

};

\


byId('gyro-absolute').innerHTML = orientation.absolute;

byId('gyro-alpha').innerHTML = orientation.alpha.toFixed(2);

byId('gyro-beta').innerHTML = orientation.beta.toFixed(2);

byId('gyro-gamma').innerHTML = orientation.gamma.toFixed(2);

const theOrientation = (window\.offsetWidth > window\.offsetHeight) ? 'landscape' : 'portrait';

\


// If orientation is logged correctly

if(!Object.values(orientation).includes(null)) {

// Subtract 90deg if in portrait mode

if(theOrientation == 'portrait') {

orientation.alpha = wrap(orientation.alpha + 90, 0, 360);

}

\


// Offset y

const offsetY = 89.5; // I have no idea why this works

\


if(Math.abs(orientation.beta) < 100) {

cameraTargetRotation.x = (-orientation.gamma \* (PI / 180) % 180) - offsetY;

} else {

cameraTargetRotation.x = (orientation.gamma \* (PI / 180) % 180) + offsetY;

}

cameraTargetRotation.y = orientation.alpha \* (PI / 180);

cameraTargetRotation.z = (-orientation.beta \* (PI / 180)) + offsetY;

\


cameraRotation.x = cameraTargetRotation.x;

cameraRotation.y = cameraTargetRotation.y;

cameraRotation.z = cameraTargetRotation.z;

\


if(theOrientation == 'landscape') {

}

\


setCameraRotation();

};

}, true);

view more: next ›