Files
lucidia-metaverse/main.js
Alexa Louise 47cf47f624 Initial commit: Lucidia Metaverse
Complete 3D metaverse platform with:
- Three.js 3D rendering
- Cannon.js physics engine
- Pointer lock controls
- Procedural cityscape
- Floating islands
- Portal system
- Particle effects
- WebXR/VR support ready
- Multiplayer ready (Socket.io)

Features:
- First-person controls (WASD + mouse)
- Jump and run mechanics
- Chat system
- Real-time HUD
- Loading screen
- Responsive design

Built with Vite for fast builds and hot reload.

🤖 Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-22 19:02:56 -06:00

520 lines
18 KiB
JavaScript

import * as THREE from 'three';
import { PointerLockControls } from 'three/addons/controls/PointerLockControls.js';
import * as CANNON from 'cannon-es';
// ===========================
// LUCIDIA METAVERSE ENGINE
// ===========================
class LucidiaMetaverse {
constructor() {
this.scene = new THREE.Scene();
this.camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
this.renderer = new THREE.WebGLRenderer({ antialias: true });
this.clock = new THREE.Clock();
// Physics world
this.world = new CANNON.World();
this.world.gravity.set(0, -30, 0);
// Player
this.player = {
velocity: new THREE.Vector3(),
canJump: false,
height: 1.8,
speed: 10,
jumpPower: 15
};
// Controls
this.moveForward = false;
this.moveBackward = false;
this.moveLeft = false;
this.moveRight = false;
this.canRun = false;
// Other players
this.otherPlayers = new Map();
// FPS counter
this.frameCount = 0;
this.lastTime = performance.now();
this.init();
}
init() {
// Renderer setup
this.renderer.setSize(window.innerWidth, window.innerHeight);
this.renderer.setPixelRatio(window.devicePixelRatio);
this.renderer.shadowMap.enabled = true;
this.renderer.shadowMap.type = THREE.PCFSoftShadowMap;
this.renderer.toneMapping = THREE.ACESFilmicToneMapping;
this.renderer.toneMappingExposure = 1.0;
document.getElementById('canvas-container').appendChild(this.renderer.domElement);
// Camera position
this.camera.position.set(0, this.player.height, 10);
// Create environment
this.createSky();
this.createLighting();
this.createGround();
this.createCityscape();
this.createFloatingIslands();
this.createPortals();
this.createParticleEffects();
// Physics for player
const playerShape = new CANNON.Sphere(0.5);
this.playerBody = new CANNON.Body({
mass: 70,
shape: playerShape,
linearDamping: 0.9
});
this.playerBody.position.set(0, this.player.height, 10);
this.world.addBody(this.playerBody);
// Controls
this.controls = new PointerLockControls(this.camera, document.body);
// Event listeners
this.setupEventListeners();
// Hide loading screen
setTimeout(() => {
document.getElementById('loading').style.display = 'none';
document.getElementById('controls').style.display = 'block';
document.getElementById('chat').style.display = 'block';
document.getElementById('chat-input').style.display = 'block';
this.addChatMessage('System', 'Welcome to Lucidia Metaverse');
}, 2000);
// Start animation loop
this.animate();
}
createSky() {
// Gradient sky
const skyGeo = new THREE.SphereGeometry(500, 32, 32);
const skyMat = new THREE.ShaderMaterial({
uniforms: {
topColor: { value: new THREE.Color(0x0077ff) },
bottomColor: { value: new THREE.Color(0xFF006B) },
offset: { value: 33 },
exponent: { value: 0.6 }
},
vertexShader: `
varying vec3 vWorldPosition;
void main() {
vec4 worldPosition = modelMatrix * vec4(position, 1.0);
vWorldPosition = worldPosition.xyz;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
`,
fragmentShader: `
uniform vec3 topColor;
uniform vec3 bottomColor;
uniform float offset;
uniform float exponent;
varying vec3 vWorldPosition;
void main() {
float h = normalize(vWorldPosition + offset).y;
gl_FragColor = vec4(mix(bottomColor, topColor, max(pow(max(h, 0.0), exponent), 0.0)), 1.0);
}
`,
side: THREE.BackSide
});
const sky = new THREE.Mesh(skyGeo, skyMat);
this.scene.add(sky);
// Stars
const starsGeometry = new THREE.BufferGeometry();
const starsMaterial = new THREE.PointsMaterial({ color: 0xFFFFFF, size: 0.1 });
const starsVertices = [];
for (let i = 0; i < 10000; i++) {
const x = (Math.random() - 0.5) * 1000;
const y = Math.random() * 500;
const z = (Math.random() - 0.5) * 1000;
starsVertices.push(x, y, z);
}
starsGeometry.setAttribute('position', new THREE.Float32BufferAttribute(starsVertices, 3));
const stars = new THREE.Points(starsGeometry, starsMaterial);
this.scene.add(stars);
}
createLighting() {
// Ambient light
const ambient = new THREE.AmbientLight(0xffffff, 0.5);
this.scene.add(ambient);
// Directional light (sun)
const sun = new THREE.DirectionalLight(0xFFD700, 1.5);
sun.position.set(50, 100, 50);
sun.castShadow = true;
sun.shadow.camera.left = -50;
sun.shadow.camera.right = 50;
sun.shadow.camera.top = 50;
sun.shadow.camera.bottom = -50;
sun.shadow.mapSize.width = 2048;
sun.shadow.mapSize.height = 2048;
this.scene.add(sun);
// Point lights for atmosphere
const colors = [0xFF9D00, 0xFF6B00, 0xFF0066, 0x7700FF, 0x0066FF];
for (let i = 0; i < 5; i++) {
const light = new THREE.PointLight(colors[i], 2, 50);
light.position.set(
Math.random() * 100 - 50,
5,
Math.random() * 100 - 50
);
this.scene.add(light);
}
}
createGround() {
// Main ground
const groundGeo = new THREE.PlaneGeometry(200, 200, 50, 50);
const groundMat = new THREE.MeshStandardMaterial({
color: 0x1a1a2e,
roughness: 0.8,
metalness: 0.2,
wireframe: false
});
const ground = new THREE.Mesh(groundGeo, groundMat);
ground.rotation.x = -Math.PI / 2;
ground.receiveShadow = true;
this.scene.add(ground);
// Physics ground
const groundShape = new CANNON.Plane();
const groundBody = new CANNON.Body({ mass: 0 });
groundBody.addShape(groundShape);
groundBody.quaternion.setFromAxisAngle(new CANNON.Vec3(1, 0, 0), -Math.PI / 2);
this.world.addBody(groundBody);
// Grid overlay
const gridHelper = new THREE.GridHelper(200, 50, 0xFF9D00, 0xFF006B);
gridHelper.position.y = 0.01;
this.scene.add(gridHelper);
}
createCityscape() {
// Create procedural buildings
for (let i = 0; i < 50; i++) {
const width = Math.random() * 3 + 2;
const height = Math.random() * 30 + 10;
const depth = Math.random() * 3 + 2;
const buildingGeo = new THREE.BoxGeometry(width, height, depth);
const buildingMat = new THREE.MeshStandardMaterial({
color: new THREE.Color().setHSL(Math.random(), 0.5, 0.3),
emissive: new THREE.Color().setHSL(Math.random(), 1, 0.2),
emissiveIntensity: 0.3,
roughness: 0.7,
metalness: 0.5
});
const building = new THREE.Mesh(buildingGeo, buildingMat);
// Random position in a circle around spawn
const angle = Math.random() * Math.PI * 2;
const distance = Math.random() * 40 + 30;
building.position.set(
Math.cos(angle) * distance,
height / 2,
Math.sin(angle) * distance
);
building.castShadow = true;
building.receiveShadow = true;
this.scene.add(building);
// Windows
for (let j = 0; j < Math.floor(height / 3); j++) {
const windowGeo = new THREE.PlaneGeometry(width * 0.8, 2);
const windowMat = new THREE.MeshBasicMaterial({
color: Math.random() > 0.7 ? 0xFFFF00 : 0x333333
});
const window1 = new THREE.Mesh(windowGeo, windowMat);
window1.position.copy(building.position);
window1.position.y = j * 3 + 2;
window1.position.z += depth / 2 + 0.01;
this.scene.add(window1);
}
}
}
createFloatingIslands() {
// Floating platforms
for (let i = 0; i < 10; i++) {
const islandGeo = new THREE.CylinderGeometry(5, 3, 2, 8);
const islandMat = new THREE.MeshStandardMaterial({
color: 0x8B4513,
roughness: 0.9
});
const island = new THREE.Mesh(islandGeo, islandMat);
island.position.set(
Math.random() * 100 - 50,
Math.random() * 30 + 20,
Math.random() * 100 - 50
);
island.castShadow = true;
island.receiveShadow = true;
this.scene.add(island);
// Glowing crystal on top
const crystalGeo = new THREE.OctahedronGeometry(1);
const crystalMat = new THREE.MeshStandardMaterial({
color: 0xFF00FF,
emissive: 0xFF00FF,
emissiveIntensity: 1
});
const crystal = new THREE.Mesh(crystalGeo, crystalMat);
crystal.position.copy(island.position);
crystal.position.y += 2;
this.scene.add(crystal);
}
}
createPortals() {
// Create portal rings
const portalPositions = [
{ x: 20, y: 5, z: 0 },
{ x: -20, y: 5, z: 0 },
{ x: 0, y: 5, z: 20 },
{ x: 0, y: 5, z: -20 }
];
portalPositions.forEach(pos => {
const ringGeo = new THREE.TorusGeometry(3, 0.2, 16, 100);
const ringMat = new THREE.MeshStandardMaterial({
color: 0x00FFFF,
emissive: 0x00FFFF,
emissiveIntensity: 2,
metalness: 1
});
const ring = new THREE.Mesh(ringGeo, ringMat);
ring.position.set(pos.x, pos.y, pos.z);
this.scene.add(ring);
// Portal surface
const portalGeo = new THREE.CircleGeometry(2.8, 32);
const portalMat = new THREE.MeshBasicMaterial({
color: 0x0066FF,
transparent: true,
opacity: 0.5,
side: THREE.DoubleSide
});
const portal = new THREE.Mesh(portalGeo, portalMat);
portal.position.copy(ring.position);
this.scene.add(portal);
});
}
createParticleEffects() {
// Floating particles
const particleCount = 1000;
const particlesGeometry = new THREE.BufferGeometry();
const particlePositions = new Float32Array(particleCount * 3);
for (let i = 0; i < particleCount * 3; i += 3) {
particlePositions[i] = (Math.random() - 0.5) * 100;
particlePositions[i + 1] = Math.random() * 50;
particlePositions[i + 2] = (Math.random() - 0.5) * 100;
}
particlesGeometry.setAttribute('position', new THREE.BufferAttribute(particlePositions, 3));
const particlesMaterial = new THREE.PointsMaterial({
color: 0xFF9D00,
size: 0.1,
transparent: true,
opacity: 0.6
});
this.particles = new THREE.Points(particlesGeometry, particlesMaterial);
this.scene.add(this.particles);
}
setupEventListeners() {
// Click to enable pointer lock
document.body.addEventListener('click', () => {
if (!this.controls.isLocked) {
this.controls.lock();
}
});
// Keyboard controls
document.addEventListener('keydown', (e) => {
switch (e.code) {
case 'KeyW': this.moveForward = true; break;
case 'KeyS': this.moveBackward = true; break;
case 'KeyA': this.moveLeft = true; break;
case 'KeyD': this.moveRight = true; break;
case 'ShiftLeft': this.canRun = true; break;
case 'Space':
if (this.player.canJump) {
this.playerBody.velocity.y = this.player.jumpPower;
this.player.canJump = false;
}
break;
case 'KeyT':
e.preventDefault();
document.getElementById('chat-input').focus();
break;
}
});
document.addEventListener('keyup', (e) => {
switch (e.code) {
case 'KeyW': this.moveForward = false; break;
case 'KeyS': this.moveBackward = false; break;
case 'KeyA': this.moveLeft = false; break;
case 'KeyD': this.moveRight = false; break;
case 'ShiftLeft': this.canRun = false; break;
}
});
// Chat input
document.getElementById('chat-input').addEventListener('keydown', (e) => {
if (e.code === 'Enter') {
const msg = e.target.value.trim();
if (msg) {
this.addChatMessage('You', msg);
e.target.value = '';
}
e.target.blur();
this.controls.lock();
} else if (e.code === 'Escape') {
e.target.blur();
this.controls.lock();
}
});
// Window resize
window.addEventListener('resize', () => {
this.camera.aspect = window.innerWidth / window.innerHeight;
this.camera.updateProjectionMatrix();
this.renderer.setSize(window.innerWidth, window.innerHeight);
});
// VR button (if supported)
if ('xr' in navigator) {
navigator.xr.isSessionSupported('immersive-vr').then((supported) => {
if (supported) {
document.getElementById('vr-button').style.display = 'block';
document.getElementById('vr-button').addEventListener('click', () => {
this.enterVR();
});
}
});
}
}
addChatMessage(sender, message) {
const chatDiv = document.getElementById('chat');
const msgEl = document.createElement('div');
msgEl.style.marginBottom = '5px';
msgEl.innerHTML = `<strong>${sender}:</strong> ${message}`;
chatDiv.appendChild(msgEl);
chatDiv.scrollTop = chatDiv.scrollHeight;
}
updateMovement(delta) {
const speed = this.canRun ? this.player.speed * 2 : this.player.speed;
const velocity = new CANNON.Vec3(0, this.playerBody.velocity.y, 0);
const forward = new THREE.Vector3();
const right = new THREE.Vector3();
this.camera.getWorldDirection(forward);
forward.y = 0;
forward.normalize();
right.crossVectors(forward, new THREE.Vector3(0, 1, 0));
if (this.moveForward) velocity.z -= forward.z * speed;
if (this.moveBackward) velocity.z += forward.z * speed;
if (this.moveLeft) velocity.x -= right.x * speed;
if (this.moveRight) velocity.x += right.x * speed;
if (this.moveForward) velocity.x -= forward.x * speed;
if (this.moveBackward) velocity.x += forward.x * speed;
if (this.moveLeft) velocity.z -= right.z * speed;
if (this.moveRight) velocity.z += right.z * speed;
this.playerBody.velocity.x = velocity.x;
this.playerBody.velocity.z = velocity.z;
// Check if on ground
if (Math.abs(this.playerBody.velocity.y) < 0.1 && this.playerBody.position.y < this.player.height + 0.1) {
this.player.canJump = true;
}
// Update camera position
this.camera.position.x = this.playerBody.position.x;
this.camera.position.y = this.playerBody.position.y + this.player.height / 2;
this.camera.position.z = this.playerBody.position.z;
// Update HUD
document.getElementById('position').textContent =
`${this.camera.position.x.toFixed(1)}, ${this.camera.position.y.toFixed(1)}, ${this.camera.position.z.toFixed(1)}`;
}
updateFPS() {
this.frameCount++;
const currentTime = performance.now();
if (currentTime >= this.lastTime + 1000) {
document.getElementById('fps').textContent = this.frameCount;
this.frameCount = 0;
this.lastTime = currentTime;
}
}
enterVR() {
this.renderer.xr.enabled = true;
navigator.xr.requestSession('immersive-vr').then((session) => {
this.renderer.xr.setSession(session);
});
}
animate() {
requestAnimationFrame(() => this.animate());
const delta = this.clock.getDelta();
// Update physics
this.world.step(1 / 60, delta, 3);
// Update movement
if (this.controls.isLocked) {
this.updateMovement(delta);
}
// Animate particles
if (this.particles) {
this.particles.rotation.y += 0.0005;
}
// Update FPS counter
this.updateFPS();
// Render
this.renderer.render(this.scene, this.camera);
}
}
// Initialize the metaverse when page loads
window.addEventListener('load', () => {
new LucidiaMetaverse();
});