Add flies
This commit is contained in:
parent
a7cd8f5546
commit
022e5633dd
@ -84,6 +84,20 @@
|
|||||||
|
|
||||||
const debugLight = false;
|
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) ---
|
// --- Utility: Random Color (seeded) ---
|
||||||
function getRandomColor() {
|
function getRandomColor() {
|
||||||
const hue = seededRandom();
|
const hue = seededRandom();
|
||||||
@ -92,6 +106,15 @@
|
|||||||
return new THREE.Color().setHSL(hue, saturation, lightness).getHex();
|
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) ---
|
// --- Seedable Random Number Generator (Mulberry32) ---
|
||||||
let seed = 12345; // Default seed, will be overridden per shelf
|
let seed = 12345; // Default seed, will be overridden per shelf
|
||||||
function seededRandom() {
|
function seededRandom() {
|
||||||
@ -108,7 +131,7 @@
|
|||||||
scene.background = new THREE.Color(0x000000);
|
scene.background = new THREE.Color(0x000000);
|
||||||
|
|
||||||
// 2. Camera Setup
|
// 2. Camera Setup
|
||||||
const FOV = 55;
|
const FOV = 65;
|
||||||
camera = new THREE.PerspectiveCamera(FOV, window.innerWidth / window.innerHeight, 0.1, 1000);
|
camera = new THREE.PerspectiveCamera(FOV, window.innerWidth / window.innerHeight, 0.1, 1000);
|
||||||
camera.position.set(0, 1.5, 4);
|
camera.position.set(0, 1.5, 4);
|
||||||
|
|
||||||
@ -312,6 +335,8 @@
|
|||||||
topPanel.castShadow = true;
|
topPanel.castShadow = true;
|
||||||
shelfGroup.add(topPanel);
|
shelfGroup.add(topPanel);
|
||||||
|
|
||||||
|
landingSurfaces.push(topPanel);
|
||||||
|
|
||||||
// 2. Individual Shelves & Books
|
// 2. Individual Shelves & Books
|
||||||
const internalHeight = shelfHeight - (2 * woodThickness);
|
const internalHeight = shelfHeight - (2 * woodThickness);
|
||||||
const shelfSpacing = internalHeight / numShelves;
|
const shelfSpacing = internalHeight / numShelves;
|
||||||
@ -519,6 +544,8 @@
|
|||||||
floor.receiveShadow = true;
|
floor.receiveShadow = true;
|
||||||
scene.add(floor);
|
scene.add(floor);
|
||||||
|
|
||||||
|
landingSurfaces.push(floor);
|
||||||
|
|
||||||
createTvSet(-roomSize/2 + 1.2, -roomSize/2 + 0.8, Math.PI * 0.1);
|
createTvSet(-roomSize/2 + 1.2, -roomSize/2 + 0.8, Math.PI * 0.1);
|
||||||
|
|
||||||
// --- 5. Lamp (On the table, right side) ---
|
// --- 5. Lamp (On the table, right side) ---
|
||||||
@ -568,6 +595,8 @@
|
|||||||
|
|
||||||
scene.add(lampGroup);
|
scene.add(lampGroup);
|
||||||
|
|
||||||
|
landingSurfaces.push(shadeMesh);
|
||||||
|
|
||||||
// --- 7. Old Camera (On the table) ---
|
// --- 7. Old Camera (On the table) ---
|
||||||
const cameraBody = new THREE.BoxGeometry(0.4, 0.3, 0.2);
|
const cameraBody = new THREE.BoxGeometry(0.4, 0.3, 0.2);
|
||||||
const cameraLens = new THREE.CylinderGeometry(0.08, 0.08, 0.05, 12);
|
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.2, Math.PI/2, 0);
|
||||||
createBookshelf(-roomSize/2 + 0.2, roomSize/2*0.7, 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);
|
createBookshelf(roomSize/2 * 0.7, -roomSize/2+0.3, 0, 1);
|
||||||
|
|
||||||
|
setupFlies();
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Dust Particle System Function ---
|
// --- Dust Particle System Function ---
|
||||||
@ -762,6 +793,181 @@
|
|||||||
playVideoByIndex(0);
|
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 ---
|
// --- Animation Loop ---
|
||||||
function animate() {
|
function animate() {
|
||||||
requestAnimationFrame(animate);
|
requestAnimationFrame(animate);
|
||||||
@ -864,6 +1070,8 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updateFlies();
|
||||||
|
|
||||||
// RENDER!
|
// RENDER!
|
||||||
renderer.render(scene, camera);
|
renderer.render(scene, camera);
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user