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.
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);
What is the context of what Israel apparently did? Just for my own curiosity.