Browse Source

One more push to go!

master
cailean 7 hours ago
parent
commit
afd46b3178
  1. 1
      .gitignore
  2. 23
      css/style.css
  3. 14
      index.html
  4. 51
      js/Content.js
  5. 25
      js/Item.js
  6. 2
      js/ModelLoader.js
  7. 42
      js/Player.js
  8. 4
      js/main.js
  9. 6
      package-lock.json
  10. 1
      package.json
  11. BIN
      public/models/demo-world_repositioned_2711_transforms.glb
  12. BIN
      public/sounds/ambient.mp3
  13. 7
      public/texts/XR_FLOSS_Init.md
  14. 1
      server.js

1
.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

23
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 {

14
index.html

@ -22,13 +22,21 @@
<div id="about-modal" class="modal">
<h2 id="about-title">About Emancipate XR</h2>
<p>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.
<p>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.</p>
<p>The project is led by Aiden Brady and Cailean Finn and has been funded by Immersive Arts UK's 'Experiment' strand.</p>
<p>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.</p>
<p>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.</p>
<p>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.</p>
</div>
<div id="controls-modal" class="modal">
<h2 id="controls-title">Controls</h2>
<p>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.</p>
<p><u>Movement (DESKTOP):</u> WASD</p>
<p><u>VR Controls</u></p>
<p>Left Trigger: TP | Left Joystick: Adjust TP Length</p>
<p>Right Trigger: Interact with Items | Right Grip: Squeeze to Spray Paint | Right Joystick: Scroll Up/Down on Text elements</p>
<p>You can shake your Spray Can for a new Colour!</p>
<p><u>Keyboard Controls</u></p>
<p>WASD & F to Interact!</p>
<script type="module" src="./js/main.js"></script>
<script type="module" src="./js/titleAnimation.js"></script>
<script type="module" src="./js/ui.js"></script>

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

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

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

42
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();

4
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 () {

6
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",

1
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"

BIN
public/models/demo-world_repositioned_2711_transforms.glb (Stored with Git LFS)

Binary file not shown.

BIN
public/sounds/ambient.mp3

Binary file not shown.

7
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

1
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();

Loading…
Cancel
Save