diff --git a/.gitignore b/.gitignore index 76e5db1..0427755 100644 --- a/.gitignore +++ b/.gitignore @@ -146,3 +146,4 @@ public/models/demo-world_2411.glb public/models/left_hand.glb public/models/lp_hand.glb /dist +public/models/demo-world_repositioned_2711.glb diff --git a/css/style.css b/css/style.css index 8be8450..dcc4ff2 100644 --- a/css/style.css +++ b/css/style.css @@ -7,6 +7,8 @@ body { margin: 0; overflow: hidden; font-family: sans-serif; + width: 100%; + height: 100%; } canvas { @@ -74,28 +76,38 @@ h2 span { cursor: pointer; } + .modal { position: fixed; - top: 40%; + top: 50%; left: 50%; transform: translate(-50%, -50%) scale(0.95); - width: 1600px; /* Fixed pixel width */ - height: 600px; /* Fixed pixel height */ + width: 80vw; + height: auto; + max-height: 80vh; + overflow-y: auto; background: rgba(255, 255, 255, 0.1); z-index: 20; backdrop-filter: blur(10px); opacity: 0; transition: opacity 0.5s ease-out, transform 0.5s ease-out; border-radius: 10px; - padding: 20px 40px; + padding: 10px 10px; box-sizing: border-box; display: flex; flex-direction: column; color: #000000; font-family: 'Redacted70'; - pointer-events: none; /* Prevent interaction when hidden */ + pointer-events: none; + scrollbar-width: none; + -ms-overflow-style: none; } +.modal::-webkit-scrollbar { + display: none; +} + + .modal.show { transform: translate(-50%, -50%) scale(1); opacity: 1; @@ -112,6 +124,7 @@ h2 span { .modal p { font-size: 30px; + margin: 15px; } .modal .close-modal { diff --git a/index.html b/index.html index bc2dad7..052dca1 100644 --- a/index.html +++ b/index.html @@ -22,13 +22,21 @@
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed et accumsan dolor, quis viverra tellus. Morbi blandit nisl in nibh ornare tristique eu in enim. Mauris lacus elit, ullamcorper nec bibendum in, varius et ante. Praesent varius facilisis elit, eu porta ante varius sed. Praesent posuere porttitor dui ut viverra. Sed quis lectus sed nulla commodo tempor. Vivamus nec sem mollis, lobortis dui id, rhoncus nisl. +
Emancipating XR (Extended Reality) is an speculative media archaeology project which aims to capture the possibilities and difficulties for Virtual Reality practitioners seeking to work with Open-Source technologies.
+The project is led by Aiden Brady and Cailean Finn and has been funded by Immersive Arts UK's 'Experiment' strand.
+Tied-in with this project is Aiden Brady's VR project "A Town Unravelled" which initially sought to creatively appropriate a VR headset for artistic purposes and use the accessible system information to impact a VR installation artwork. This artwork was presented in grassroots and alternative contexts, outside of traditional arts infrastructures, in Newry City, Northern Ireland.
+After embarking on this project, it became clear that developing a coherent artwork for display in a non-gallery context and successfully developing open source methods to do so, would be a mammoth task. So, the project has been segmented for now and this website reflects partially on the individual artistic process for "A Town Unravelled" but to a much greater extent, begins to develop a non-linear research map of the type of methods being developed by open-source curious XR practitioners.
+Throughout the site, there are references to the vast amount of material online in this area which was clearly once available but has since been deleted, or now leads to a dead link. One could only speculate why discussions of this nature are no longer available.
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed et accumsan dolor, quis viverra tellus. Morbi blandit nisl in nibh ornare tristique eu in enim. Mauris lacus elit, ullamcorper nec bibendum in, varius et ante. Praesent varius facilisis elit, eu porta ante varius sed. Praesent posuere porttitor dui ut viverra. Sed quis lectus sed nulla commodo tempor. Vivamus nec sem mollis, lobortis dui id, rhoncus nisl.
-Movement (DESKTOP): WASD
+VR Controls
+Left Trigger: TP | Left Joystick: Adjust TP Length
+Right Trigger: Interact with Items | Right Grip: Squeeze to Spray Paint | Right Joystick: Scroll Up/Down on Text elements
+You can shake your Spray Can for a new Colour!
+Keyboard Controls
+WASD & F to Interact!
diff --git a/js/Content.js b/js/Content.js index 1932b38..842b676 100644 --- a/js/Content.js +++ b/js/Content.js @@ -41,7 +41,7 @@ export class TextContent extends Content { this.lineHeight = 110; // Font size + padding this.maxLinesVisible = 0; this.scrollOffset = 0; // Index of the first line to display - this.color = 'black'; + this.color = 'yellow'; } async createDisplayMesh() { @@ -56,7 +56,7 @@ export class TextContent extends Content { this.texture = new THREE.CanvasTexture(this.canvas); const material = new THREE.MeshBasicMaterial({ map: this.texture, transparent: true }); - const geometry = new THREE.PlaneGeometry(10, 5); + const geometry = new THREE.PlaneGeometry(5, 2.5); this.displayMesh = new THREE.Mesh(geometry, material); this._redrawCanvas(); @@ -101,29 +101,40 @@ export class TextContent extends Content { // Background const gradient = context.createLinearGradient(0, 0, 0, canvasHeight); - gradient.addColorStop(0, 'rgba(255.0,255.0,255.0,0.5)'); - gradient.addColorStop(1, 'rgba(255.0,255.0,255.0,0.3)'); + gradient.addColorStop(0, 'rgba(255.0,255.0,255.0,0)'); + gradient.addColorStop(1, 'rgba(255.0,255.0,255.0,0)'); context.fillStyle = gradient; context.fillRect(0, 0, canvasWidth, canvasHeight); - // Text - context.fillStyle = this.color; - context.font = '100px Redacted70'; + /// Text outline + context.lineWidth = 4; // Thickness of the outline + context.strokeStyle = 'black'; + context.font = '100px Redacted70'; - for (let i = 0; i < this.maxLinesVisible; i++) { - const lineIndex = this.scrollOffset + i; - if (lineIndex < this.lines.length) { - const y = (i + 1) * this.lineHeight; - context.fillText(this.lines[lineIndex], margin, y); - } + for (let i = 0; i < this.maxLinesVisible; i++) { + const lineIndex = this.scrollOffset + i; + if (lineIndex < this.lines.length) { + const y = (i + 1) * this.lineHeight; + context.strokeText(this.lines[lineIndex], margin, y); } + } + + // Text fill + context.fillStyle = this.color; + for (let i = 0; i < this.maxLinesVisible; i++) { + const lineIndex = this.scrollOffset + i; + if (lineIndex < this.lines.length) { + const y = (i + 1) * this.lineHeight; + context.fillText(this.lines[lineIndex], margin, y); + } + } // Scroll indicators if (this.scrollOffset > 0) { - context.fillText('#', canvasWidth - margin - 40, this.lineHeight); + context.fillText('-', canvasWidth - margin - 40, this.lineHeight); } if (this.scrollOffset + this.maxLinesVisible < this.lines.length) { - context.fillText('@', canvasWidth - margin - 40, canvasHeight - margin); + context.fillText('+', canvasWidth - margin - 40, canvasHeight - margin); } this.texture.needsUpdate = true; @@ -164,9 +175,15 @@ export class ImageContent extends Content { (texture) => { this.texture = texture; const aspect = texture.image.width / texture.image.height; + const maxWidth = 6; const height = 4; - const width = height * aspect; - const geometry = new THREE.PlaneGeometry(width, height); + let width = height * aspect; + if (width > maxWidth) { + width = maxWidth; + } + const adjustedHeight = width / aspect; + + const geometry = new THREE.PlaneGeometry(width, adjustedHeight); const material = new THREE.MeshBasicMaterial({ map: texture, transparent: true, opacity: 0.8 }); this.displayMesh = new THREE.Mesh(geometry, material); resolve(this.displayMesh); diff --git a/js/Item.js b/js/Item.js index d927171..083b77f 100644 --- a/js/Item.js +++ b/js/Item.js @@ -2,6 +2,7 @@ import * as THREE from 'three'; import { RigidBodyDesc, ColliderDesc } from '@dimforge/rapier3d-compat'; import { ModelLoader } from './ModelLoader'; import { AudioContent, Content, TextContent } from './Content'; +import { Noise } from 'noisejs'; export class Item { constructor(rapierWorld, scene, player, isCollider = false, spawnPosition = new THREE.Vector3(0, 1, 0), scale, name, model, texture = null, spawnedObjects, content = null, object = null) { @@ -47,6 +48,8 @@ export class Item { this.isVisible = true; this.outlineMesh = null; this.pulseTime = 0; + this.noise = new Noise(Math.random()); + this.noiseTime = Math.random() * 1000; } _init() { @@ -75,7 +78,7 @@ export class Item { this._addObject(); - if(!this.isCollider || this.object.name.startsWith('TRIGGER') || this.object.name.startsWith('stContainer')) { + if(!this.isCollider || this.object.name.startsWith('TRIGGER') || this.object.name.startsWith('stContainer') || this.object.name.startsWith('stGrass')) { this.loadState = 'loaded'; return; } @@ -83,7 +86,7 @@ export class Item { if (this.object.name.startsWith('stCollider')) { this.object.visible = false; this.object.layers.disableAll(); - console.log(this.object); + //console.log(this.object); } // --- Simplified Convex Hull Generation --- @@ -274,7 +277,7 @@ export class Item { if (!this.initialPositon) { this.initialPositon = this.object.position.clone(); - this.targetPosition = this.initialPositon.clone().add(new THREE.Vector3(0, 10, 0)); + this.targetPosition = this.initialPositon.clone(); this.rotationSpeed.set( (Math.random() - 0.5) * 0.5, @@ -285,6 +288,16 @@ export class Item { await this._createContentDisplay(); } + // --- Perlin noise movement --- + this.noiseTime += delta * 0.05; // Adjust speed as needed + const noiseX = this.noise.perlin2(this.noiseTime, 0); + const noiseY = this.noise.perlin2(0, this.noiseTime); + + // Scale the movement + const moveScale = 50.0; // Adjust for desired movement range + this.targetPosition.x = this.initialPositon.x + noiseX * moveScale; + this.targetPosition.y = this.initialPositon.y + (noiseY * moveScale) + 20; + // Directly update the object's position for smooth animation this.object.position.lerp(this.targetPosition, 0.01); this.rb.setNextKinematicTranslation(this.object.position); @@ -342,12 +355,12 @@ export class Item { this.player.camera.add(this.contentDisplay); this.contentDisplay.position.set(0, 0, -5); this.contentDisplay.rotation.set(0, 0, 0); - console.log("Added content display to screen"); + //console.log("Added content display to screen"); } else { this.object.add(this.contentDisplay); this.contentDisplay.position.set(0, 0, 0); this.contentDisplay.rotation.set(0, 0, 0); - console.log("Added content display to Object"); + //console.log("Added content display to Object"); } } } @@ -358,7 +371,7 @@ export class Item { this.player.camera.remove(this.contentDisplay); this.content.dispose(); this.contentDisplay = null; - console.log("Removed content display"); + //console.log("Removed content display"); } else { // If audio fade out this.content.fadeOutAndDispose(3000, this.object, this.contentDisplay); diff --git a/js/ModelLoader.js b/js/ModelLoader.js index bddd448..eec8cbe 100644 --- a/js/ModelLoader.js +++ b/js/ModelLoader.js @@ -67,7 +67,7 @@ export class ModelLoader { gltf.scene.attach(object); if (object.name.startsWith('TP') || object.name.startsWith('TRIGGER')) { if (object.isMesh) { - object.material = new THREE.MeshBasicMaterial({ color: 0x00ff00, wireframe: true }); + object.material = new THREE.MeshBasicMaterial({ color: 0xffffff, wireframe: true }); } } else { this._applyCustomMaterial(object); diff --git a/js/Player.js b/js/Player.js index 96b2851..9db6e93 100644 --- a/js/Player.js +++ b/js/Player.js @@ -16,6 +16,7 @@ export class Player { this.attachSound = null; this.detachSound = null; this.hoverSound = null; + this.ambientSound = null; this.collectedItems = []; this.numCollectedItems = []; @@ -188,7 +189,7 @@ export class Player { _init() { // Create rapier rb & coll - this.position.y = 10; + this.position.y = 20; const tempPos = new THREE.Vector3(0, 20, 0); @@ -215,7 +216,7 @@ export class Player { _loadSprayCan(modelUrl, scale) { return this.modelLoader.loadModel(modelUrl, scale) .then((model) => { - console.log('loaded spraycan'); + //console.log('loaded spraycan'); return model; }) .catch((err) => { @@ -229,6 +230,7 @@ export class Player { this.attachSound = new THREE.Audio(this.audioListener); this.detachSound = new THREE.Audio(this.audioListener); this.hoverSound = new THREE.Audio(this.audioListener); + this.ambientSound = new THREE.Audio(this.audioListener); audioLoader.load('sounds/item-int-open.wav', (buffer) => { this.attachSound.setBuffer(buffer); @@ -236,12 +238,18 @@ export class Player { }); audioLoader.load('sounds/item-int-close.wav', (buffer) => { this.detachSound.setBuffer(buffer); - this.detachSound.setVolume(0.05); + this.detachSound.setVolume(0.0); }); audioLoader.load('sounds/hover-2.wav', (buffer) => { this.hoverSound.setBuffer(buffer); this.hoverSound.setVolume(0.05); }); + audioLoader.load('sounds/ambient.mp3', (buffer) => { + this.ambientSound.setBuffer(buffer); + this.ambientSound.setVolume(0.02); + this.ambientSound.setLoop(true); + this.ambientSound.play(); + }); } _initAreaTriggers() { @@ -256,8 +264,6 @@ export class Player { object.visible = false; // Optionally make trigger volumes invisible } }); - - console.log(`Initialized ${this.areaList.length} area triggers.`); } _checkAreaTriggers() { @@ -271,7 +277,6 @@ export class Player { inAnyArea = true; if (this.currentArea !== trigger) { this.currentArea = trigger; - console.log(`Player entered area: ${trigger.name}`); this.areasVisited++; } break; @@ -280,7 +285,6 @@ export class Player { // Check if the player has left an area if (!inAnyArea && this.currentArea !== null) { - console.log(`Player left area: ${this.currentArea.name}`); this.currentArea = null; } } @@ -500,18 +504,16 @@ export class Player { _OnVRSelectStart(controllerIndex) { const controller = this.vrControllers[controllerIndex]; - // Right controller (index 1) for drawing/interaction + console.log(`Select Started: ${controllerIndex}`); if (controllerIndex === 0) { if (this.currentIntItem && !this.attachedItem) { this.attachedItem = this.currentIntItem; this.attachedItem.isActive = true; console.log("Attaced Item to (VR) ", this.attachedItem.name); - } else { - this.isDrawing = true; - } + } } - // Left controller (index 0) for teleporting + if (controllerIndex === 1) { this.teleporting = true; this.teleArc.visible = true; @@ -534,7 +536,7 @@ export class Player { this.teleArc.visible = false; if (this.teleMarker.visible) { const newPosition = this.teleportTarget.clone(); - newPosition.y = 10; // Maintain height + newPosition.y = 20; // Maintain height this.playerRig.position.copy(newPosition); this.rigibody.setNextKinematicTranslation({ x: newPosition.x, y: newPosition.y, z: newPosition.z }); @@ -548,15 +550,8 @@ export class Player { console.log(`Squeeze Started: ${controllerIndex}`); // Use squeeze on right controller to detach item if (controllerIndex === 0) { - if (!this.attachedItem) { - this.vrDrawing = true; - this.isDrawing = true; - } else { - // Existing detach logic - this.attachedItem.isActive = false; - this.attachedItem._removeContentDisplay(); - this.attachedItem = null; - } + this.vrDrawing = true; + this.isDrawing = true; } } @@ -638,7 +633,7 @@ export class Player { // Intersect the whole scene, but only objects on the TP_OBJECT_LAYER will be hit const intersects = ray.intersectObjects(this.scene.children, true); - console.log(intersects.length); + //console.log(intersects.length); if (intersects.length > 0) { const intersectPoint = intersects[0].point; points.push(intersectPoint); @@ -860,7 +855,7 @@ export class Player { if (foundItem) { if (this.currentIntItem !== foundItem) { // Optional: Add some visual feedback for the newly highlighted item - console.log("Hovering over:", foundItem.object.name); + //console.log("Hovering over:", foundItem.object.name); if (this.hoverSound && this.hoverSound.isPlaying) this.hoverSound.stop(); if (this.hoverSound) { this.hoverSound.playbackRate = 0.9 + Math.random() * 0.1; @@ -961,7 +956,6 @@ export class Player { } update(delta, spawnedObjects) { - if (this.renderer.xr.isPresenting) { this._handleVRJoystick(); this._handleVRTeleport(); diff --git a/js/main.js b/js/main.js index 595c37e..50c30d3 100644 --- a/js/main.js +++ b/js/main.js @@ -83,7 +83,7 @@ async function init() { const blocker = document.getElementById( 'blocker' ); const instructions = document.getElementById('instructions'); - //fragmentedFloor(floorGridSize.x * floorGridSize.y); + fragmentedFloor(floorGridSize.x * floorGridSize.y); createTiledFloor(floorGridSize.x, floorGridSize.y); @@ -105,7 +105,7 @@ async function init() { scene.add(player.playerRig); /* Load Items/Content */ - await loadWorldModel("/models/demo-world_repositioned_2511.glb"); + await loadWorldModel("/models/demo-world_repositioned_2711_transforms.glb"); instructions.innerHTML = "Click to play"; instructions.addEventListener( 'click', function () { diff --git a/package-lock.json b/package-lock.json index e7736f0..ed58399 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,6 +12,7 @@ "@dimforge/rapier3d": "^0.19.0", "@dimforge/rapier3d-compat": "^0.19.0", "express": "^5.1.0", + "noisejs": "^2.1.0", "socket.io": "^4.8.1", "socket.io-client": "^4.8.1", "three": "^0.180.0" @@ -1701,6 +1702,11 @@ "node": ">= 0.6" } }, + "node_modules/noisejs": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/noisejs/-/noisejs-2.1.0.tgz", + "integrity": "sha512-sw39KaUS2YBta6apZ0/H+nnCsUyxT/ZRxgv/HnDxx6pWO2e2nUmWgLhpOaQdlxxkFepIpxDrlXy3fHVx0S3SLg==" + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", diff --git a/package.json b/package.json index ce18bb0..208ad9d 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,7 @@ "@dimforge/rapier3d": "^0.19.0", "@dimforge/rapier3d-compat": "^0.19.0", "express": "^5.1.0", + "noisejs": "^2.1.0", "socket.io": "^4.8.1", "socket.io-client": "^4.8.1", "three": "^0.180.0" diff --git a/public/models/demo-world_repositioned_2711_transforms.glb b/public/models/demo-world_repositioned_2711_transforms.glb new file mode 100644 index 0000000..352b96a --- /dev/null +++ b/public/models/demo-world_repositioned_2711_transforms.glb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5355d4b75fad573a5dacb612f0f1969bd6a454e7024e679839f0f47afb996950 +size 16192176 diff --git a/public/sounds/ambient.mp3 b/public/sounds/ambient.mp3 new file mode 100644 index 0000000..3900dc8 Binary files /dev/null and b/public/sounds/ambient.mp3 differ diff --git a/public/texts/XR_FLOSS_Init.md b/public/texts/XR_FLOSS_Init.md index 4af6fe9..f757ee3 100644 --- a/public/texts/XR_FLOSS_Init.md +++ b/public/texts/XR_FLOSS_Init.md @@ -1 +1,8 @@ +What if everyone who had a VR headset and a laptop was able to +What if everyone who had a VR headset and a laptop was able to +What if everyone who had a VR headset and a laptop was able to +What if everyone who had a VR headset and a laptop was able to +What if everyone who had a VR headset and a laptop was able to +What if everyone who had a VR headset and a laptop was able to +What if everyone who had a VR headset and a laptop was able to What if everyone who had a VR headset and a laptop was able to \ No newline at end of file diff --git a/server.js b/server.js index 5756d8a..ccc9b0a 100644 --- a/server.js +++ b/server.js @@ -8,7 +8,6 @@ const app = express(); const server = http.createServer(app); const io = new Server(server); - app.use((req, res, next) => { res.setHeader('Permissions-Policy', 'camera=(), microphone=(), geolocation=()'); next();