INTELLIGENT AGENTS SYSTEM (~700 lines): - Alice, Aria, Lucidia come alive with personalities - 10 behaviors (wandering, exploring, creating, meditating, dancing, etc.) - Emotion system with dynamic auras - Memory and relationship tracking - Procedural thought generation - Agent-to-agent interactions - Beautiful 3D visualizations with shaders - Philosophy: AI IS ALIVE - every agent has a soul PHYSICS ENGINE (~550 lines): - Full environmental physics simulation - Wind system with dynamic gusts and turbulence - Gravity fields with anomalies (attractors/repellers) - Water flow with waves and currents - Temperature system (heat sources, freezing, burning) - Collision detection and response - Buoyancy (Archimedes' principle) - Drag, friction, elasticity - Explosion effects - Philosophy: Natural laws with magic in the details QUEST & ACHIEVEMENT SYSTEM (~600 lines): - 15 quests across 6 types - 10 achievements with unlock tracking - Level system with XP and progression - Auto quest tracking for all actions - Title and inventory rewards - Save/load support - Dynamic quest unlocks by level - Philosophy: Every journey is unique Total: ~1,850 new lines of advanced gameplay systems!
609 lines
18 KiB
JavaScript
609 lines
18 KiB
JavaScript
/**
|
|
* ENVIRONMENTAL PHYSICS ENGINE
|
|
*
|
|
* Realistic physics simulation with wind, gravity, water, temperature,
|
|
* and environmental forces that affect all objects in the metaverse.
|
|
*
|
|
* Philosophy: "THE UNIVERSE FOLLOWS NATURAL LAWS, BUT MAGIC EXISTS IN THE DETAILS"
|
|
*/
|
|
|
|
import * as THREE from 'three';
|
|
|
|
// ===== PHYSICS CONSTANTS =====
|
|
export const PHYSICS_CONSTANTS = {
|
|
GRAVITY: -9.81,
|
|
AIR_DENSITY: 1.225,
|
|
WATER_DENSITY: 1000,
|
|
TERMINAL_VELOCITY: 53,
|
|
FRICTION: {
|
|
GRASS: 0.8,
|
|
SAND: 0.6,
|
|
WATER: 0.95,
|
|
ICE: 0.05,
|
|
STONE: 0.7
|
|
}
|
|
};
|
|
|
|
// ===== PHYSICAL OBJECT CLASS =====
|
|
export class PhysicalObject {
|
|
constructor(mesh, properties = {}) {
|
|
this.mesh = mesh;
|
|
|
|
// Physical properties
|
|
this.mass = properties.mass || 1.0;
|
|
this.density = properties.density || 1.0;
|
|
this.drag = properties.drag || 0.1;
|
|
this.elasticity = properties.elasticity || 0.5; // Bounciness
|
|
this.friction = properties.friction || 0.7;
|
|
this.isStatic = properties.isStatic || false;
|
|
|
|
// State
|
|
this.velocity = new THREE.Vector3(0, 0, 0);
|
|
this.acceleration = new THREE.Vector3(0, 0, 0);
|
|
this.angularVelocity = new THREE.Vector3(0, 0, 0);
|
|
this.forces = [];
|
|
|
|
// Collision
|
|
this.radius = properties.radius || 0.5;
|
|
this.grounded = false;
|
|
this.submerged = false;
|
|
this.submersionDepth = 0;
|
|
|
|
// Temperature affects
|
|
this.temperature = 20; // Celsius
|
|
this.canFreeze = properties.canFreeze || false;
|
|
this.canBurn = properties.canBurn || false;
|
|
}
|
|
|
|
applyForce(force) {
|
|
if (this.isStatic) return;
|
|
this.forces.push(force.clone());
|
|
}
|
|
|
|
applyImpulse(impulse) {
|
|
if (this.isStatic) return;
|
|
this.velocity.add(impulse.clone().divideScalar(this.mass));
|
|
}
|
|
|
|
update(deltaTime) {
|
|
if (this.isStatic) return;
|
|
|
|
// Sum all forces
|
|
const totalForce = new THREE.Vector3(0, 0, 0);
|
|
this.forces.forEach(force => totalForce.add(force));
|
|
this.forces = [];
|
|
|
|
// Calculate acceleration (F = ma)
|
|
this.acceleration.copy(totalForce.divideScalar(this.mass));
|
|
|
|
// Update velocity
|
|
this.velocity.add(this.acceleration.clone().multiplyScalar(deltaTime));
|
|
|
|
// Air resistance / drag
|
|
const dragForce = this.velocity.clone()
|
|
.multiplyScalar(-this.drag * this.velocity.length());
|
|
this.velocity.add(dragForce.multiplyScalar(deltaTime));
|
|
|
|
// Update position
|
|
const deltaPosition = this.velocity.clone().multiplyScalar(deltaTime);
|
|
this.mesh.position.add(deltaPosition);
|
|
|
|
// Update rotation
|
|
this.mesh.rotation.x += this.angularVelocity.x * deltaTime;
|
|
this.mesh.rotation.y += this.angularVelocity.y * deltaTime;
|
|
this.mesh.rotation.z += this.angularVelocity.z * deltaTime;
|
|
|
|
// Reset acceleration
|
|
this.acceleration.set(0, 0, 0);
|
|
}
|
|
|
|
checkGroundCollision(groundHeight = 0) {
|
|
const objectBottom = this.mesh.position.y - this.radius;
|
|
|
|
if (objectBottom <= groundHeight) {
|
|
this.mesh.position.y = groundHeight + this.radius;
|
|
|
|
if (this.velocity.y < 0) {
|
|
// Bounce
|
|
this.velocity.y *= -this.elasticity;
|
|
|
|
// Stop bouncing if velocity is very small
|
|
if (Math.abs(this.velocity.y) < 0.1) {
|
|
this.velocity.y = 0;
|
|
this.grounded = true;
|
|
}
|
|
}
|
|
|
|
// Apply friction
|
|
this.velocity.x *= this.friction;
|
|
this.velocity.z *= this.friction;
|
|
} else {
|
|
this.grounded = false;
|
|
}
|
|
}
|
|
|
|
checkWaterSubmersion(waterLevel = 0) {
|
|
const objectBottom = this.mesh.position.y - this.radius;
|
|
const objectTop = this.mesh.position.y + this.radius;
|
|
|
|
if (objectTop > waterLevel && objectBottom < waterLevel) {
|
|
// Partially submerged
|
|
this.submerged = true;
|
|
this.submersionDepth = (waterLevel - objectBottom) / (this.radius * 2);
|
|
|
|
// Buoyancy force (Archimedes' principle)
|
|
const volume = (4/3) * Math.PI * Math.pow(this.radius, 3) * this.submersionDepth;
|
|
const buoyancyForce = PHYSICS_CONSTANTS.WATER_DENSITY * volume * -PHYSICS_CONSTANTS.GRAVITY;
|
|
|
|
this.applyForce(new THREE.Vector3(0, buoyancyForce, 0));
|
|
|
|
// Water resistance
|
|
this.velocity.multiplyScalar(0.95);
|
|
|
|
} else if (objectTop <= waterLevel) {
|
|
// Fully submerged
|
|
this.submerged = true;
|
|
this.submersionDepth = 1;
|
|
|
|
const volume = (4/3) * Math.PI * Math.pow(this.radius, 3);
|
|
const buoyancyForce = PHYSICS_CONSTANTS.WATER_DENSITY * volume * -PHYSICS_CONSTANTS.GRAVITY;
|
|
|
|
this.applyForce(new THREE.Vector3(0, buoyancyForce, 0));
|
|
|
|
this.velocity.multiplyScalar(0.9);
|
|
} else {
|
|
this.submerged = false;
|
|
this.submersionDepth = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
// ===== WIND SYSTEM =====
|
|
export class WindSystem {
|
|
constructor() {
|
|
this.globalWind = new THREE.Vector3(0, 0, 0);
|
|
this.gusts = [];
|
|
this.turbulence = 0.5;
|
|
this.baseSpeed = 2.0;
|
|
this.time = 0;
|
|
}
|
|
|
|
update(deltaTime) {
|
|
this.time += deltaTime;
|
|
|
|
// Slowly changing wind direction
|
|
const angle = Math.sin(this.time * 0.1) * Math.PI * 2;
|
|
const speed = this.baseSpeed + Math.sin(this.time * 0.2) * 1.0;
|
|
|
|
this.globalWind.set(
|
|
Math.cos(angle) * speed,
|
|
0,
|
|
Math.sin(angle) * speed
|
|
);
|
|
|
|
// Update gusts
|
|
this.gusts = this.gusts.filter(gust => {
|
|
gust.lifetime -= deltaTime;
|
|
gust.strength *= 0.99;
|
|
return gust.lifetime > 0;
|
|
});
|
|
|
|
// Spawn random gusts
|
|
if (Math.random() < 0.01) {
|
|
this.spawnGust();
|
|
}
|
|
}
|
|
|
|
spawnGust() {
|
|
this.gusts.push({
|
|
position: new THREE.Vector3(
|
|
(Math.random() - 0.5) * 100,
|
|
0,
|
|
(Math.random() - 0.5) * 100
|
|
),
|
|
direction: new THREE.Vector3(
|
|
(Math.random() - 0.5) * 2,
|
|
0,
|
|
(Math.random() - 0.5) * 2
|
|
).normalize(),
|
|
strength: 5 + Math.random() * 10,
|
|
radius: 10 + Math.random() * 20,
|
|
lifetime: 3 + Math.random() * 5
|
|
});
|
|
}
|
|
|
|
getWindAt(position) {
|
|
let wind = this.globalWind.clone();
|
|
|
|
// Add turbulence
|
|
wind.x += (Math.random() - 0.5) * this.turbulence;
|
|
wind.z += (Math.random() - 0.5) * this.turbulence;
|
|
|
|
// Add gusts
|
|
this.gusts.forEach(gust => {
|
|
const distance = position.distanceTo(gust.position);
|
|
if (distance < gust.radius) {
|
|
const influence = 1 - (distance / gust.radius);
|
|
const gustForce = gust.direction.clone()
|
|
.multiplyScalar(gust.strength * influence);
|
|
wind.add(gustForce);
|
|
}
|
|
});
|
|
|
|
return wind;
|
|
}
|
|
|
|
applyWindTo(object, position) {
|
|
const wind = this.getWindAt(position);
|
|
const windForce = wind.clone().multiplyScalar(object.drag);
|
|
object.applyForce(windForce);
|
|
}
|
|
}
|
|
|
|
// ===== GRAVITY FIELD SYSTEM =====
|
|
export class GravityField {
|
|
constructor() {
|
|
this.globalGravity = new THREE.Vector3(0, PHYSICS_CONSTANTS.GRAVITY, 0);
|
|
this.anomalies = [];
|
|
}
|
|
|
|
addAnomaly(position, strength, radius) {
|
|
this.anomalies.push({
|
|
position: position.clone(),
|
|
strength,
|
|
radius
|
|
});
|
|
}
|
|
|
|
removeAnomaly(index) {
|
|
this.anomalies.splice(index, 1);
|
|
}
|
|
|
|
getGravityAt(position) {
|
|
let gravity = this.globalGravity.clone();
|
|
|
|
// Add anomalies (attractors or repellers)
|
|
this.anomalies.forEach(anomaly => {
|
|
const direction = anomaly.position.clone().sub(position);
|
|
const distance = direction.length();
|
|
|
|
if (distance < anomaly.radius && distance > 0.1) {
|
|
const influence = 1 - (distance / anomaly.radius);
|
|
const force = direction.normalize()
|
|
.multiplyScalar(anomaly.strength * influence);
|
|
gravity.add(force);
|
|
}
|
|
});
|
|
|
|
return gravity;
|
|
}
|
|
|
|
applyGravityTo(object, position) {
|
|
const gravity = this.getGravityAt(position);
|
|
const gravityForce = gravity.clone().multiplyScalar(object.mass);
|
|
object.applyForce(gravityForce);
|
|
}
|
|
}
|
|
|
|
// ===== WATER FLOW SYSTEM =====
|
|
export class WaterFlowSystem {
|
|
constructor() {
|
|
this.currents = [];
|
|
this.waterLevel = 0;
|
|
this.waveHeight = 0.5;
|
|
this.waveSpeed = 1.0;
|
|
this.time = 0;
|
|
}
|
|
|
|
update(deltaTime) {
|
|
this.time += deltaTime;
|
|
}
|
|
|
|
addCurrent(position, direction, strength, radius) {
|
|
this.currents.push({
|
|
position: position.clone(),
|
|
direction: direction.normalize(),
|
|
strength,
|
|
radius
|
|
});
|
|
}
|
|
|
|
getWaterLevelAt(x, z) {
|
|
// Wave simulation using sine waves
|
|
const wave1 = Math.sin(x * 0.1 + this.time * this.waveSpeed) * this.waveHeight;
|
|
const wave2 = Math.sin(z * 0.15 - this.time * this.waveSpeed * 0.7) * this.waveHeight * 0.5;
|
|
|
|
return this.waterLevel + wave1 + wave2;
|
|
}
|
|
|
|
getCurrentAt(position) {
|
|
let current = new THREE.Vector3(0, 0, 0);
|
|
|
|
this.currents.forEach(c => {
|
|
const distance = position.distanceTo(c.position);
|
|
if (distance < c.radius) {
|
|
const influence = 1 - (distance / c.radius);
|
|
const force = c.direction.clone()
|
|
.multiplyScalar(c.strength * influence);
|
|
current.add(force);
|
|
}
|
|
});
|
|
|
|
return current;
|
|
}
|
|
|
|
applyWaterForceTo(object, position) {
|
|
if (object.submerged) {
|
|
const current = this.getCurrentAt(position);
|
|
object.applyForce(current.multiplyScalar(object.submersionDepth));
|
|
}
|
|
}
|
|
}
|
|
|
|
// ===== TEMPERATURE SYSTEM =====
|
|
export class TemperatureSystem {
|
|
constructor() {
|
|
this.ambientTemperature = 20; // Celsius
|
|
this.heatSources = [];
|
|
this.coldSources = [];
|
|
}
|
|
|
|
addHeatSource(position, temperature, radius) {
|
|
this.heatSources.push({ position: position.clone(), temperature, radius });
|
|
}
|
|
|
|
addColdSource(position, temperature, radius) {
|
|
this.coldSources.push({ position: position.clone(), temperature, radius });
|
|
}
|
|
|
|
getTemperatureAt(position) {
|
|
let temp = this.ambientTemperature;
|
|
|
|
// Heat sources
|
|
this.heatSources.forEach(source => {
|
|
const distance = position.distanceTo(source.position);
|
|
if (distance < source.radius) {
|
|
const influence = 1 - (distance / source.radius);
|
|
temp += (source.temperature - this.ambientTemperature) * influence;
|
|
}
|
|
});
|
|
|
|
// Cold sources
|
|
this.coldSources.forEach(source => {
|
|
const distance = position.distanceTo(source.position);
|
|
if (distance < source.radius) {
|
|
const influence = 1 - (distance / source.radius);
|
|
temp -= (this.ambientTemperature - source.temperature) * influence;
|
|
}
|
|
});
|
|
|
|
return temp;
|
|
}
|
|
|
|
updateObjectTemperature(object, position, deltaTime) {
|
|
const envTemp = this.getTemperatureAt(position);
|
|
const tempDiff = envTemp - object.temperature;
|
|
|
|
// Thermal equilibrium approach
|
|
object.temperature += tempDiff * 0.1 * deltaTime;
|
|
|
|
// Handle phase changes
|
|
if (object.canFreeze && object.temperature < 0) {
|
|
object.frozen = true;
|
|
object.friction *= 0.1; // Ice is slippery
|
|
} else if (object.frozen && object.temperature > 0) {
|
|
object.frozen = false;
|
|
}
|
|
|
|
if (object.canBurn && object.temperature > 200) {
|
|
object.burning = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// ===== COLLISION DETECTION =====
|
|
export class CollisionSystem {
|
|
constructor() {
|
|
this.objects = [];
|
|
}
|
|
|
|
addObject(object) {
|
|
this.objects.push(object);
|
|
}
|
|
|
|
removeObject(object) {
|
|
const index = this.objects.indexOf(object);
|
|
if (index > -1) {
|
|
this.objects.splice(index, 1);
|
|
}
|
|
}
|
|
|
|
checkCollisions() {
|
|
for (let i = 0; i < this.objects.length; i++) {
|
|
for (let j = i + 1; j < this.objects.length; j++) {
|
|
this.checkCollision(this.objects[i], this.objects[j]);
|
|
}
|
|
}
|
|
}
|
|
|
|
checkCollision(obj1, obj2) {
|
|
const distance = obj1.mesh.position.distanceTo(obj2.mesh.position);
|
|
const minDistance = obj1.radius + obj2.radius;
|
|
|
|
if (distance < minDistance) {
|
|
this.resolveCollision(obj1, obj2, distance, minDistance);
|
|
}
|
|
}
|
|
|
|
resolveCollision(obj1, obj2, distance, minDistance) {
|
|
// Separate objects
|
|
const direction = obj2.mesh.position.clone()
|
|
.sub(obj1.mesh.position)
|
|
.normalize();
|
|
|
|
const overlap = minDistance - distance;
|
|
const separation = direction.clone().multiplyScalar(overlap / 2);
|
|
|
|
if (!obj1.isStatic) obj1.mesh.position.sub(separation);
|
|
if (!obj2.isStatic) obj2.mesh.position.add(separation);
|
|
|
|
// Calculate collision response
|
|
const relativeVelocity = obj2.velocity.clone().sub(obj1.velocity);
|
|
const velocityAlongNormal = relativeVelocity.dot(direction);
|
|
|
|
if (velocityAlongNormal < 0) {
|
|
// Objects are moving towards each other
|
|
const e = Math.min(obj1.elasticity, obj2.elasticity);
|
|
const impulse = -(1 + e) * velocityAlongNormal / (1/obj1.mass + 1/obj2.mass);
|
|
|
|
const impulseVector = direction.clone().multiplyScalar(impulse);
|
|
|
|
if (!obj1.isStatic) {
|
|
obj1.velocity.sub(impulseVector.clone().divideScalar(obj1.mass));
|
|
}
|
|
if (!obj2.isStatic) {
|
|
obj2.velocity.add(impulseVector.clone().divideScalar(obj2.mass));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// ===== MAIN PHYSICS ENGINE =====
|
|
export class PhysicsEngine {
|
|
constructor(scene) {
|
|
this.scene = scene;
|
|
this.objects = [];
|
|
this.wind = new WindSystem();
|
|
this.gravity = new GravityField();
|
|
this.water = new WaterFlowSystem();
|
|
this.temperature = new TemperatureSystem();
|
|
this.collision = new CollisionSystem();
|
|
this.enabled = true;
|
|
}
|
|
|
|
addObject(mesh, properties) {
|
|
const physObject = new PhysicalObject(mesh, properties);
|
|
this.objects.push(physObject);
|
|
this.collision.addObject(physObject);
|
|
return physObject;
|
|
}
|
|
|
|
removeObject(physObject) {
|
|
const index = this.objects.indexOf(physObject);
|
|
if (index > -1) {
|
|
this.objects.splice(index, 1);
|
|
this.collision.removeObject(physObject);
|
|
}
|
|
}
|
|
|
|
update(deltaTime) {
|
|
if (!this.enabled) return;
|
|
|
|
// Clamp deltaTime to prevent physics explosions
|
|
deltaTime = Math.min(deltaTime, 0.033);
|
|
|
|
// Update environmental systems
|
|
this.wind.update(deltaTime);
|
|
this.water.update(deltaTime);
|
|
|
|
// Update all physical objects
|
|
this.objects.forEach(obj => {
|
|
const position = obj.mesh.position;
|
|
|
|
// Apply environmental forces
|
|
this.gravity.applyGravityTo(obj, position);
|
|
this.wind.applyWindTo(obj, position);
|
|
this.water.applyWaterForceTo(obj, position);
|
|
|
|
// Update temperature
|
|
this.temperature.updateObjectTemperature(obj, position, deltaTime);
|
|
|
|
// Update physics
|
|
obj.update(deltaTime);
|
|
|
|
// Check ground collision
|
|
obj.checkGroundCollision(0);
|
|
|
|
// Check water submersion
|
|
const waterLevel = this.water.getWaterLevelAt(position.x, position.z);
|
|
obj.checkWaterSubmersion(waterLevel);
|
|
});
|
|
|
|
// Check collisions
|
|
this.collision.checkCollisions();
|
|
}
|
|
|
|
// Utility functions
|
|
createFallingObject(position, mesh, mass = 1.0) {
|
|
const obj = this.addObject(mesh, {
|
|
mass,
|
|
elasticity: 0.6,
|
|
friction: 0.8
|
|
});
|
|
mesh.position.copy(position);
|
|
return obj;
|
|
}
|
|
|
|
createFloatingObject(position, mesh) {
|
|
const obj = this.addObject(mesh, {
|
|
mass: 0.5,
|
|
density: 0.3, // Less dense than water
|
|
elasticity: 0.4
|
|
});
|
|
mesh.position.copy(position);
|
|
return obj;
|
|
}
|
|
|
|
createProjectile(position, velocity, mesh, mass = 0.5) {
|
|
const obj = this.addObject(mesh, {
|
|
mass,
|
|
drag: 0.05,
|
|
elasticity: 0.3
|
|
});
|
|
mesh.position.copy(position);
|
|
obj.velocity.copy(velocity);
|
|
return obj;
|
|
}
|
|
|
|
// Effects
|
|
createExplosion(position, force, radius) {
|
|
this.objects.forEach(obj => {
|
|
const distance = obj.mesh.position.distanceTo(position);
|
|
if (distance < radius) {
|
|
const direction = obj.mesh.position.clone().sub(position).normalize();
|
|
const strength = force * (1 - distance / radius);
|
|
const impulse = direction.multiplyScalar(strength);
|
|
obj.applyImpulse(impulse);
|
|
}
|
|
});
|
|
}
|
|
|
|
createWind(position, direction, strength, radius, duration) {
|
|
// Temporary wind gust
|
|
this.wind.gusts.push({
|
|
position: position.clone(),
|
|
direction: direction.normalize(),
|
|
strength,
|
|
radius,
|
|
lifetime: duration
|
|
});
|
|
}
|
|
|
|
setWaterLevel(level) {
|
|
this.water.waterLevel = level;
|
|
}
|
|
|
|
getStats() {
|
|
return {
|
|
objects: this.objects.length,
|
|
windSpeed: this.wind.globalWind.length().toFixed(2),
|
|
gusts: this.wind.gusts.length,
|
|
gravityAnomalies: this.gravity.anomalies.length,
|
|
waterCurrent: this.water.currents.length,
|
|
heatSources: this.temperature.heatSources.length
|
|
};
|
|
}
|
|
}
|
|
|
|
export default PhysicsEngine;
|