Add flies

This commit is contained in:
Dejvino 2025-11-08 21:05:47 +01:00
parent a7cd8f5546
commit 022e5633dd

View File

@ -84,6 +84,20 @@
const debugLight = false;
const FLIES_COUNT = 2; // Flies
const flies = [];
let landingSurfaces = []; // Array to hold floor and table for fly landings
const raycaster = new THREE.Raycaster();
// --- Configuration ---
const ROOM_SIZE = roomSize;
const FLIGHT_HEIGHT_MIN = 0.5; // Min height for flying
const FLIGHT_HEIGHT_MAX = roomHeight * 0.9; // Max height for flying
const FLY_FLIGHT_SPEED_FACTOR = 0.01; // How quickly 't' increases per frame
const DAMPING_FACTOR = 0.05;
const FLY_WAIT_BASE = 1000;
const FLY_LAND_CHANCE = 0.3;
// --- Utility: Random Color (seeded) ---
function getRandomColor() {
const hue = seededRandom();
@ -92,6 +106,15 @@
return new THREE.Color().setHSL(hue, saturation, lightness).getHex();
}
/**
* Converts degrees to radians.
* @param {number} degrees
* @returns {number}
*/
function degToRad(degrees) {
return degrees * (Math.PI / 180);
}
// --- Seedable Random Number Generator (Mulberry32) ---
let seed = 12345; // Default seed, will be overridden per shelf
function seededRandom() {
@ -108,7 +131,7 @@
scene.background = new THREE.Color(0x000000);
// 2. Camera Setup
const FOV = 55;
const FOV = 65;
camera = new THREE.PerspectiveCamera(FOV, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.set(0, 1.5, 4);
@ -312,6 +335,8 @@
topPanel.castShadow = true;
shelfGroup.add(topPanel);
landingSurfaces.push(topPanel);
// 2. Individual Shelves & Books
const internalHeight = shelfHeight - (2 * woodThickness);
const shelfSpacing = internalHeight / numShelves;
@ -519,6 +544,8 @@
floor.receiveShadow = true;
scene.add(floor);
landingSurfaces.push(floor);
createTvSet(-roomSize/2 + 1.2, -roomSize/2 + 0.8, Math.PI * 0.1);
// --- 5. Lamp (On the table, right side) ---
@ -568,6 +595,8 @@
scene.add(lampGroup);
landingSurfaces.push(shadeMesh);
// --- 7. Old Camera (On the table) ---
const cameraBody = new THREE.BoxGeometry(0.4, 0.3, 0.2);
const cameraLens = new THREE.CylinderGeometry(0.08, 0.08, 0.05, 12);
@ -601,6 +630,8 @@
createBookshelf(-roomSize/2 + 0.2, roomSize/2*0.2, Math.PI/2, 0);
createBookshelf(-roomSize/2 + 0.2, roomSize/2*0.7, Math.PI/2, 0);
createBookshelf(roomSize/2 * 0.7, -roomSize/2+0.3, 0, 1);
setupFlies();
}
// --- Dust Particle System Function ---
@ -762,6 +793,181 @@
playVideoByIndex(0);
}
function randomFlyTarget() {
return new THREE.Vector3(
(Math.random() - 0.5) * (ROOM_SIZE - 1),
FLIGHT_HEIGHT_MIN + Math.random() * (FLIGHT_HEIGHT_MAX - FLIGHT_HEIGHT_MIN),
(Math.random() - 0.5) * (ROOM_SIZE - 1));
}
/**
* Creates a single fly mesh (small cone/tetrahedron).
* @returns {THREE.Group}
*/
function createFlyMesh() {
const flyGroup = new THREE.Group();
const flyMaterial = new THREE.MeshPhongMaterial({
color: 0x111111, // Dark fly color
shininess: 50,
});
// Small Cone/Tetrahedron for a simple shape
const bodyGeometry = new THREE.ConeGeometry(0.01, 0.02, 3);
const body = new THREE.Mesh(bodyGeometry, flyMaterial);
body.rotation.x = degToRad(90); // Point nose in Z direction
body.castShadow = true;
body.receiveShadow = true;
flyGroup.add(body);
// Initial state and parameters for the fly
flyGroup.userData = {
state: 'flying', // 'flying' or 'landed'
landTimer: 0,
t: 0, // Curve progression t parameter (0 to 1)
speed: FLY_FLIGHT_SPEED_FACTOR + Math.random() * 0.01,
curve: null,
landCheckTimer: 0,
oscillationTime: Math.random() * 100, // For smooth y-axis buzzing
};
// Initial random position
flyGroup.position = randomFlyTarget();
return flyGroup;
}
/**
* Creates a new Quadratic Bezier curve for a fly's flight path.
* @param {THREE.Group} fly - The fly mesh group.
* @param {THREE.Vector3} endPoint - The target position for the end of the curve.
*/
function createFlyCurve(fly, endPoint) {
const startPoint = fly.position.clone();
// Calculate the midpoint
const midPoint = new THREE.Vector3().lerpVectors(startPoint, endPoint, 0.5);
// Calculate a random offset for the control point to create curvature
const offsetMagnitude = startPoint.distanceTo(endPoint) * 0.5;
const offsetAngle = Math.random() * Math.PI * 2;
// Displace the control point randomly to create a swooping path.
// Control point y is usually higher than start/end for a nice arc.
const controlPoint = new THREE.Vector3(
midPoint.x + Math.cos(offsetAngle) * offsetMagnitude * 0.5,
midPoint.y + Math.random() * 0.5 + 0.5,
midPoint.z + Math.sin(offsetAngle) * offsetMagnitude * 0.5
);
fly.userData.curve = new THREE.QuadraticBezierCurve3(
startPoint,
controlPoint,
endPoint
);
fly.userData.t = 0; // Reset progression
fly.userData.landCheckTimer = 50 + Math.random() * 50; // New landing decision window
}
/**
* Creates and places the 'flies' meshes.
*/
function setupFlies() {
for (let i = 0; i < FLIES_COUNT; i++) {
const fly = createFlyMesh();
scene.add(fly);
flies.push(fly);
}
}
/**
* Updates the position and state of the flies using Bezier curves.
*/
function updateFlies() {
flies.forEach(fly => {
const data = fly.userData;
if (data.state === 'flying' || data.state === 'landing') {
if (!data.curve) {
// Initialize the first curve
const newTargetPos = randomFlyTarget();
createFlyCurve(fly, newTargetPos);
data.t = 0;
}
// Advance curve progression
data.t += data.speed;
// Check for landing readiness during the flight path
data.landCheckTimer--;
if (data.t >= 1) {
// Path finished
if (data.state === 'landing') {
data.state = 'landed';
data.landTimer = FLY_WAIT_BASE + Math.random() * 1000; // Land for a random duration
data.t = 0;
return; // Stop updates for this fly
}
// 1. Check for landing decision
if (data.landCheckTimer <= 0 && Math.random() > FLY_LAND_CHANCE) {
// Raycast down from the current position to find a landing spot
raycaster.set(fly.position, new THREE.Vector3(0, -1, 0));
const intersects = raycaster.intersectObjects(landingSurfaces, false);
if (intersects.length > 0) {
const intersect = intersects[0];
data.state = 'landing';
// Land slightly above the surface
let newTargetPos = new THREE.Vector3(intersect.point.x,
intersect.point.y + 0.05,
intersect.point.z);
// const newTargetPos = randomFlyTarget();
createFlyCurve(fly, newTargetPos);
data.t = 0;
}
}
if (data.state !== 'landing') {
// 2. If not landing, generate a new random flight path
const newTargetPos = randomFlyTarget();
createFlyCurve(fly, newTargetPos);
data.t = 0; // Reset T for the new curve
}
}
// Set position along the curve
fly.position.copy(data.curve.getPoint(Math.min(data.t, 1)));
// Set rotation tangent to the curve
const tangent = data.curve.getTangent(Math.min(data.t, 1)).normalize();
fly.rotation.y = Math.atan2(tangent.x, tangent.z);
// Add slight Y oscillation for buzzing feel (on top of curve)
data.oscillationTime += 0.1;
fly.position.y += Math.sin(data.oscillationTime * 4) * 0.01;
} else if (data.state === 'landed') {
// --- Landed State ---
data.landTimer--;
if (data.landTimer <= 0) {
// Take off: Generate new flight curve from current landed position
data.state = 'flying';
const newTargetPos = randomFlyTarget();
createFlyCurve(fly, newTargetPos);
data.t = 0;
}
}
});
}
// --- Animation Loop ---
function animate() {
requestAnimationFrame(animate);
@ -864,6 +1070,8 @@
}
}
updateFlies();
// RENDER!
renderer.render(scene, camera);
}