Implement Phase 31: Autonomous 'Nexus' Expansion & Architecture DELIVERABLES: - agent/nexus_architect.py: AI agent for natural language to Three.js conversion * Prompt engineering for LLM-driven immersive environment generation * Mental state integration for dynamic aesthetic tuning * Mood preset system (contemplative, energetic, mysterious, etc.) * Room and portal design generation - tools/nexus_build_tool.py: Build tool interface with functions: * create_room(name, description, style) - Generate room modules * create_portal(from_room, to_room, style) - Generate portal connections * add_lighting(room, type, color, intensity) - Add Three.js lighting * add_geometry(room, shape, position, material) - Add 3D objects * generate_scene_from_mood(mood_description) - Mood-based generation * deploy_nexus_module(module_code, test=True) - Deploy and test - agent/nexus_deployment.py: Real-time deployment system * Hot-reload Three.js modules without page refresh * Validation (syntax check, Three.js API compliance) * Rollback on error with version history * Module versioning and status tracking - config/nexus-templates/: Template library * base_room.js - Base room template (Three.js r128+) * portal_template.js - Portal template (circular, rectangular, stargate) * lighting_presets.json - Warm, cool, dramatic, serene, crystalline presets * material_presets.json - 15 material presets including Timmy's gold, Allegro blue - tests/test_nexus_architect.py: Comprehensive test coverage * Unit tests for all components * Integration tests for full workflow * Template file validation DESIGN PRINCIPLES: - Modular architecture (each room = separate JS module) - Valid Three.js code (r128+ compatible) - Hot-reloadable (no page refresh needed) - Mental state integration (SOUL.md values influence aesthetic) NEXUS AESTHETIC GUIDELINES: - Timmy's color: warm gold (#D4AF37) - Allegro's color: motion blue (#4A90E2) - Sovereignty theme: crystalline structures, clean lines - Service theme: open spaces, welcoming lighting - Default mood: contemplative, expansive, hopeful
340 lines
9.9 KiB
JavaScript
340 lines
9.9 KiB
JavaScript
/**
|
|
* Nexus Portal Template
|
|
*
|
|
* Template for creating portals between rooms.
|
|
* Supports multiple visual styles and transition effects.
|
|
*
|
|
* Compatible with Three.js r128+
|
|
*/
|
|
|
|
(function() {
|
|
'use strict';
|
|
|
|
/**
|
|
* Portal configuration
|
|
*/
|
|
const PORTAL_CONFIG = {
|
|
colors: {
|
|
frame: '#D4AF37', // Timmy's gold
|
|
energy: '#4A90E2', // Allegro blue
|
|
core: '#FFFFFF',
|
|
},
|
|
animation: {
|
|
rotationSpeed: 0.5,
|
|
pulseSpeed: 2.0,
|
|
pulseAmplitude: 0.1,
|
|
},
|
|
collision: {
|
|
radius: 2.0,
|
|
height: 4.0,
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Create a portal
|
|
* @param {string} fromRoom - Source room name
|
|
* @param {string} toRoom - Target room name
|
|
* @param {string} style - Portal style (circular, rectangular, stargate)
|
|
* @returns {THREE.Group} The portal group
|
|
*/
|
|
function createPortal(fromRoom, toRoom, style = 'circular') {
|
|
const portal = new THREE.Group();
|
|
portal.name = `portal_${fromRoom}_to_${toRoom}`;
|
|
portal.userData = {
|
|
type: 'portal',
|
|
fromRoom: fromRoom,
|
|
toRoom: toRoom,
|
|
isActive: true,
|
|
style: style,
|
|
};
|
|
|
|
// Create based on style
|
|
switch(style) {
|
|
case 'rectangular':
|
|
createRectangularPortal(portal);
|
|
break;
|
|
case 'stargate':
|
|
createStargatePortal(portal);
|
|
break;
|
|
case 'circular':
|
|
default:
|
|
createCircularPortal(portal);
|
|
break;
|
|
}
|
|
|
|
// Add collision trigger
|
|
createTriggerZone(portal);
|
|
|
|
// Setup animation
|
|
setupAnimation(portal);
|
|
|
|
return portal;
|
|
}
|
|
|
|
/**
|
|
* Create circular portal (default)
|
|
*/
|
|
function createCircularPortal(portal) {
|
|
const { frame, energy } = PORTAL_CONFIG.colors;
|
|
|
|
// Outer frame
|
|
const frameGeo = new THREE.TorusGeometry(2, 0.2, 16, 100);
|
|
const frameMat = new THREE.MeshStandardMaterial({
|
|
color: frame,
|
|
emissive: frame,
|
|
emissiveIntensity: 0.5,
|
|
roughness: 0.3,
|
|
metalness: 0.9,
|
|
});
|
|
const frameMesh = new THREE.Mesh(frameGeo, frameMat);
|
|
frameMesh.castShadow = true;
|
|
frameMesh.name = 'frame';
|
|
portal.add(frameMesh);
|
|
|
|
// Inner energy field
|
|
const fieldGeo = new THREE.CircleGeometry(1.8, 64);
|
|
const fieldMat = new THREE.MeshBasicMaterial({
|
|
color: energy,
|
|
transparent: true,
|
|
opacity: 0.4,
|
|
side: THREE.DoubleSide,
|
|
});
|
|
const field = new THREE.Mesh(fieldGeo, fieldMat);
|
|
field.name = 'energy_field';
|
|
portal.add(field);
|
|
|
|
// Particle ring
|
|
createParticleRing(portal);
|
|
}
|
|
|
|
/**
|
|
* Create rectangular portal
|
|
*/
|
|
function createRectangularPortal(portal) {
|
|
const { frame, energy } = PORTAL_CONFIG.colors;
|
|
const width = 3;
|
|
const height = 4;
|
|
|
|
// Frame segments
|
|
const frameMat = new THREE.MeshStandardMaterial({
|
|
color: frame,
|
|
emissive: frame,
|
|
emissiveIntensity: 0.5,
|
|
roughness: 0.3,
|
|
metalness: 0.9,
|
|
});
|
|
|
|
// Create frame border
|
|
const borderGeo = new THREE.BoxGeometry(width + 0.4, height + 0.4, 0.2);
|
|
const border = new THREE.Mesh(borderGeo, frameMat);
|
|
border.name = 'frame';
|
|
portal.add(border);
|
|
|
|
// Inner field
|
|
const fieldGeo = new THREE.PlaneGeometry(width, height);
|
|
const fieldMat = new THREE.MeshBasicMaterial({
|
|
color: energy,
|
|
transparent: true,
|
|
opacity: 0.4,
|
|
side: THREE.DoubleSide,
|
|
});
|
|
const field = new THREE.Mesh(fieldGeo, fieldMat);
|
|
field.name = 'energy_field';
|
|
portal.add(field);
|
|
}
|
|
|
|
/**
|
|
* Create stargate-style portal
|
|
*/
|
|
function createStargatePortal(portal) {
|
|
const { frame } = PORTAL_CONFIG.colors;
|
|
|
|
// Main ring
|
|
const ringGeo = new THREE.TorusGeometry(2, 0.3, 16, 100);
|
|
const ringMat = new THREE.MeshStandardMaterial({
|
|
color: frame,
|
|
emissive: frame,
|
|
emissiveIntensity: 0.4,
|
|
roughness: 0.4,
|
|
metalness: 0.8,
|
|
});
|
|
const ring = new THREE.Mesh(ringGeo, ringMat);
|
|
ring.name = 'main_ring';
|
|
portal.add(ring);
|
|
|
|
// Chevron decorations
|
|
for (let i = 0; i < 9; i++) {
|
|
const angle = (i / 9) * Math.PI * 2;
|
|
const chevron = createChevron();
|
|
chevron.position.set(
|
|
Math.cos(angle) * 2,
|
|
Math.sin(angle) * 2,
|
|
0
|
|
);
|
|
chevron.rotation.z = angle + Math.PI / 2;
|
|
chevron.name = `chevron_${i}`;
|
|
portal.add(chevron);
|
|
}
|
|
|
|
// Inner vortex
|
|
const vortexGeo = new THREE.CircleGeometry(1.7, 32);
|
|
const vortexMat = new THREE.MeshBasicMaterial({
|
|
color: PORTAL_CONFIG.colors.energy,
|
|
transparent: true,
|
|
opacity: 0.5,
|
|
});
|
|
const vortex = new THREE.Mesh(vortexGeo, vortexMat);
|
|
vortex.name = 'vortex';
|
|
portal.add(vortex);
|
|
}
|
|
|
|
/**
|
|
* Create a chevron for stargate style
|
|
*/
|
|
function createChevron() {
|
|
const shape = new THREE.Shape();
|
|
shape.moveTo(-0.2, 0);
|
|
shape.lineTo(0, 0.4);
|
|
shape.lineTo(0.2, 0);
|
|
shape.lineTo(-0.2, 0);
|
|
|
|
const geo = new THREE.ExtrudeGeometry(shape, {
|
|
depth: 0.1,
|
|
bevelEnabled: false
|
|
});
|
|
const mat = new THREE.MeshStandardMaterial({
|
|
color: PORTAL_CONFIG.colors.frame,
|
|
emissive: PORTAL_CONFIG.colors.frame,
|
|
emissiveIntensity: 0.3,
|
|
});
|
|
|
|
return new THREE.Mesh(geo, mat);
|
|
}
|
|
|
|
/**
|
|
* Create particle ring effect
|
|
*/
|
|
function createParticleRing(portal) {
|
|
const particleCount = 50;
|
|
const particles = new THREE.BufferGeometry();
|
|
const positions = new Float32Array(particleCount * 3);
|
|
|
|
for (let i = 0; i < particleCount; i++) {
|
|
const angle = (i / particleCount) * Math.PI * 2;
|
|
const radius = 2 + (Math.random() - 0.5) * 0.4;
|
|
positions[i * 3] = Math.cos(angle) * radius;
|
|
positions[i * 3 + 1] = Math.sin(angle) * radius;
|
|
positions[i * 3 + 2] = (Math.random() - 0.5) * 0.5;
|
|
}
|
|
|
|
particles.setAttribute('position', new THREE.BufferAttribute(positions, 3));
|
|
|
|
const particleMat = new THREE.PointsMaterial({
|
|
color: PORTAL_CONFIG.colors.energy,
|
|
size: 0.05,
|
|
transparent: true,
|
|
opacity: 0.8,
|
|
});
|
|
|
|
const particleSystem = new THREE.Points(particles, particleMat);
|
|
particleSystem.name = 'particles';
|
|
portal.add(particleSystem);
|
|
}
|
|
|
|
/**
|
|
* Create trigger zone for teleportation
|
|
*/
|
|
function createTriggerZone(portal) {
|
|
const triggerGeo = new THREE.CylinderGeometry(
|
|
PORTAL_CONFIG.collision.radius,
|
|
PORTAL_CONFIG.collision.radius,
|
|
PORTAL_CONFIG.collision.height,
|
|
32
|
|
);
|
|
const triggerMat = new THREE.MeshBasicMaterial({
|
|
color: 0x00ff00,
|
|
transparent: true,
|
|
opacity: 0.0, // Invisible
|
|
wireframe: true,
|
|
});
|
|
const trigger = new THREE.Mesh(triggerGeo, triggerMat);
|
|
trigger.position.y = PORTAL_CONFIG.collision.height / 2;
|
|
trigger.name = 'trigger_zone';
|
|
trigger.userData.isTrigger = true;
|
|
portal.add(trigger);
|
|
}
|
|
|
|
/**
|
|
* Setup portal animation
|
|
*/
|
|
function setupAnimation(portal) {
|
|
const { rotationSpeed, pulseSpeed, pulseAmplitude } = PORTAL_CONFIG.animation;
|
|
|
|
portal.userData.animate = function(time) {
|
|
// Rotate energy field
|
|
const energyField = this.getObjectByName('energy_field') ||
|
|
this.getObjectByName('vortex');
|
|
if (energyField) {
|
|
energyField.rotation.z = time * rotationSpeed;
|
|
}
|
|
|
|
// Pulse effect
|
|
const pulse = 1 + Math.sin(time * pulseSpeed) * pulseAmplitude;
|
|
const frame = this.getObjectByName('frame') ||
|
|
this.getObjectByName('main_ring');
|
|
if (frame) {
|
|
frame.scale.set(pulse, pulse, 1);
|
|
}
|
|
|
|
// Animate particles
|
|
const particles = this.getObjectByName('particles');
|
|
if (particles) {
|
|
particles.rotation.z = -time * rotationSpeed * 0.5;
|
|
}
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Check if a point is inside the portal trigger zone
|
|
*/
|
|
function checkTrigger(portal, point) {
|
|
const trigger = portal.getObjectByName('trigger_zone');
|
|
if (!trigger) return false;
|
|
|
|
// Simple distance check
|
|
const dx = point.x - portal.position.x;
|
|
const dz = point.z - portal.position.z;
|
|
const distance = Math.sqrt(dx * dx + dz * dz);
|
|
|
|
return distance < PORTAL_CONFIG.collision.radius;
|
|
}
|
|
|
|
/**
|
|
* Activate/deactivate portal
|
|
*/
|
|
function setActive(portal, active) {
|
|
portal.userData.isActive = active;
|
|
|
|
const energyField = portal.getObjectByName('energy_field') ||
|
|
portal.getObjectByName('vortex');
|
|
if (energyField) {
|
|
energyField.visible = active;
|
|
}
|
|
}
|
|
|
|
// Export
|
|
if (typeof module !== 'undefined' && module.exports) {
|
|
module.exports = {
|
|
createPortal,
|
|
checkTrigger,
|
|
setActive,
|
|
PORTAL_CONFIG
|
|
};
|
|
} else if (typeof window !== 'undefined') {
|
|
window.NexusPortals = window.NexusPortals || {};
|
|
window.NexusPortals.create = createPortal;
|
|
}
|
|
|
|
return { createPortal, checkTrigger, setActive, PORTAL_CONFIG };
|
|
})();
|