Skip Navigation
AnCaps vs Libertarians
  • I thought libertarians valued individual liberty.

  • Tablets and smartwatches.. Why?
  • Which watch have you?

  • Here’s How Much Cleaner Energy Could Save America, in Lives and Money | Widespread adoption of heat pumps could prevent thousands of premature deaths and save billions on energy bills
  • From the heading, you could interpret that to mean they're going to tell you how much money and lives would be saved by adopting cleaner energy, or how much money and lives it would take to invest in cleaner energy, that in turn would save America.

  • Could someone explain to me how generational abuse is a thing?
  • Just to clarify, you are under the impression people have free will? What do you mean by that?

  • Green crosshair highlights in Excel spreadsheet will not go away, no matter how many online solutions I've tried!
  • Do you have to use Excel? Have you tried LibreOffice Calc, or OpenOffice, etc?

  • China opens an antitrust investigation into Nvidia
  • I'm all in support of Taiwan over China, but let's not act like NVIDIA is a small company barely struggling to survive, it's one of the biggest companies in the world and has done its fair share of unethical things, as any publicly traded company or that size has.

  • Apple Pay’s first competitor on the iPhone has arrived in Norway
  • Is there one like that for Android too? I don't have a Samsung phone and don't use anything Google, so an alternative to Google Pay that allowed NFC payments would be good.

  • "Circle to search" is actually....good?
  • Do you know if there's a FOSS project that does any of this? Android does provide a screen capture API, and there are some FOSS OCR and translation apps separately, bit I don't know of one that combines these things.

  • [AI] Stereo Madness
  • It wasn't a totally AI based story, I described the plot to it and got it to write it in good words as mine weren't.

  • Mitochondria
  • This idea is overused as heavily dependant on which school you go to. My school taught a finance course, and gave advice on job seeking and interviews.

    Also, mitochondria is usually taught at GCSE in the UK at least, which is not the last year of school. 'Mitochondria is the powerhouse of the cell' is very much a meme, it might have been interesting to use any other piece of useless information taught in schools instead.

  • If you had the choice to be born would you have been born?
  • I would rather have not experienced my life.

  • Why are you on Lemmy?
  • Found out about it from Reddit, and it seemed better in several ways, so I tried it out.

  • Earth eggs
  • Apples of the earth (pomme de terre, תפוח אדמה, probably both misspelt)

  • Is Lemmy getting more radical lately?
  • There is definitely a trend towards calling for death and violence against billionaires and other powerful figures that I've observed over a while.

  • [AI] Stereo Madness
  • I used Qwen/Qwen2.5-72B-Instruct through HuggingChat, in the prompt I told it the story idea and asked it to refine it and make it more eloquent.

  • Why Italy’s Piracy Shield risks moving from tiresome digital farce to serious national tragedy
  • Presumably VPNs and Tor still work. I often have to use those in areas where internet access is restricted. Sure, Italy shouldn't be doing such stupid things, but we don't have the power to change that, we have the power to create decentralised and people controlled system to give power back to us.

  • [AI] Stereo Madness

    I awoke to find myself lying on a cold, hard floor, disoriented and confused. My head throbbed as I tried to piece together where I was and how I had gotten there. As my eyes adjusted to the darkness, I noticed a computer in the corner of the room, its screen glowing with the familiar title screen of Geometry Dash, specifically the level "Stereo Madness."

    Before I could fully process my surroundings, a synthetic voice boomed from the walls, sending a chill down my spine.

    "Welcome," the voice intoned coldly. "Three of your family members have been captured and are being held in this facility. To save their lives, you must complete the level 'Stereo Madness' on your first try. Each coin you collect will save one of them. Fail to complete the level, and they will all die. You have no second chances."

    The back wall, previously white and opaque, suddenly turned transparent. Through the glass, I could see my father, mother, and sister, each restrained under a separate guillotine. The sight was horrifying, and my heart raced as I realised the gravity of the situation.

    The computer screen flickered, and the game started automatically. My hands trembled as I positioned myself over the button, my fingers hovering above it. I was tense and sweaty, but I knew I had to remain calm to increase my chances of success. I had played this level countless times, and I needed to rely on that experience now.

    The music began, and I focused intently on the screen. I dodged the first few obstacles with ease, my movements becoming more fluid as I fell into a rhythm. The first coin appeared, and I collected it almost automatically, my mind and fingers working in perfect sync. The second coin followed, and I managed to grab it as well, my confidence growing.

    The third coin loomed ahead, and I felt a surge of fear. This was the coin I usually failed to get. The risk was too high. I made a split-second decision to avoid it, focusing instead on completing the level. My heart pounded as I navigated the final obstacles, and with a final, precise jump, I crossed the finish line.

    The game ended, and the screen displayed "Level Complete." I turned around, my breath hitching in my throat. I heard a sickening 'thunk' as the guillotine over my father's head activated. His lifeless body fell to the ground, and I felt a wave of grief and guilt wash over me. But then, the guillotines over my mother and sister remained inactive, and they were freed.

    A door on the orthogonal wall opened, revealing a busy street. The noise of the city was a stark contrast to the silent, oppressive room. Overwhelmed with emotions, I slumped down to the floor and started sobbing uncontrollably. My mother and sister, in shock, rushed to my side. They helped me to my feet, and together, we walked unsteadily out the open door, stepping into the bright, chaotic world outside.

    5
    What does your Spotify Wrapped look like?
  • Don't use Spotify, but here is my Duolingo Year In Review

  • Technology in Avatar universe just doesn't add up.
  • In the real world new techniques is often developed for war and is only afterwards adapted into civilian life.

    And they do have both Aeroplanes and cars in LoK, where they have mech suits. I don't remember any in the original series.

    In the real world we had both airships and planes and only stopped using airships because of the Hindenburg disaster.

  • Technology in Avatar universe just doesn't add up.
  • Can you give some examples of what you mean?

  • Improvement suggestions for civ6

    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.

    4
    Sid Meier's Civilization @lemmy.ca JackGreenEarth @lemm.ee
    Improvement suggestions for civ6

    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.

    0
    Improvement suggestions for civ6

    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.

    2
    General Programming Discussion @lemmy.ml JackGreenEarth @lemm.ee
    Why is my 3d pointer not being displayed in my webapp?

    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. > > ::: spoiler 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); > > > :::

    0
    Why is my 3d pointer not being displayed in my webapp?

    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. > > ::: spoiler 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); > > > :::

    0
    Why are no images, in any format, being displayed in my Android Studio app?

    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.

    0
    Why are no images, in any format, being displayed in my Android Studio app?

    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.

    0
    Why are no images, in any format, being displayed in my Android Studio app?

    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.

    21
    General Programming Discussion @lemmy.ml JackGreenEarth @lemm.ee
    Why are no images, in any format, being displayed in my Android Studio app?

    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.

    0
    Why are no images, in any format, being displayed in my Android Studio app?

    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.

    0
    Why are no images, in any format, being displayed in my Android Studio app?

    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.

    9
    Why is my 3d pointer not being displayed in my webapp?

    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); ```

    0
    Unable to get IP address via DHCP on HP ProLiant DL380 Gen9 with Debian

    I'm having trouble getting an IP address via DHCP on my HP ProLiant DL380 Gen9 server running Debian. I've tried various troubleshooting steps, but nothing seems to work.

    Error messages:

    • "No DHCPOFFERS received"
    • "No working leases in persistent database - sleeping"
    • "Activation of network connection failed" (GNOME)
    • "IP configuration unavailable" (Plasma)

    Hardware:

    • HP ProLiant DL380 Gen9 server

    Software:

    • Debian operating system
    • GNOME and Plasma desktop environments

    Troubleshooting steps:

    • Checked network cable and ensured it's properly connected
    • Restarted network service
    • Set /etc/NetworkManager/NetworkManager.conf managed=true

    Additional information:

    • Internet worked during the Debian installation process, but not after booting into the installed system.
    • The problem occurs on both GNOME and Plasma desktop environments, but Plasma provides a slightly more helpful error message.

    I'd appreciate any help or guidance on resolving this issue. Has anyone else experienced similar problems with DHCP on a HP ProLiant DL380 Gen9 server with Debian?

    57
    Suggestion for Android device for archival purposes?

    I need an Android device for archival purposes, it has several requirements and I would appreciate any suggestions. I've done some research myself, but I would appreciate it maybe you know of something I haven't discovered.

    Requirements:

    • Must fit inside this Faraday pouch, or a different one that is of a similar price, and is both water and fire proof
    • Must support an open OS (Degoogled, rooted Android ROM or Linux - both support touch interfaces, Kiwix, and storing and viewing files)
    • at least 256GB internal storage to store the files
    • at least 8GB RAM for running local AI models, preferably nearer 16GB
    • Good battery life, since it will stay inside the pouch most of the time and I don't want to have to take it out to charge it every day
    • As cheap as possible

    I have no other requirements, it doesn't even have to have a camera, let alone a good one.

    Thanks for all your help!

    24
    A nice gap in the middle

    !

    These are the folders inside Games, by the way

    7
    Motorola Update neuters Syncthing

    Android 14, an update released on 22 June under no more description than 'security update' has stopped syncthing being able to write to folders, even the few it allowed me to previously specifically grant access to. I can't add a folder to sync, as it says 'your android version hasn't granted syncthing write access, this folder is locked to read only' and then doesn't even add it.

    My specific model, moto g73 doesn't support root or custom ROMs, which I wasn't aware of when I got it, but I have it now and apart from that it's a really good phone, so I hope there is some way to rescue this.

    I should have the option to decline their 'security', even if it is buried under many layers of settings, and grant the apps storage permission anyway. Otherwise it is not security, it is them stealing ownership of my device from me.

    It should absolutely be Motorola's onus to fix this, not the hardworking Syncthing team, but if you do know some way to get this working again, I would be grateful.

    EDIT: Oh, by the way, this update also stopped every file manager app I had, of which I had three, one being the native Android file manager itself, working properly.

    Edit2: This only seems to apply to the micro SD card storage, not internal storage. Unfortunately, I've filled up my internal storage and need to use my micro SD card. I was using Syncthing as a backup, so I had a copy in case the micro SD card corrupted everything.

    6
    JackGreenEarth JackGreenEarth @lemm.ee
    Posts 66
    Comments 3.6K