const FLIES_COUNT = 2; class FliesEffect { constructor(scene) { this.flies = []; this._setupFlies(scene); } _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) ); } _createFlyMesh() { const flyGroup = new THREE.Group(); const flyMaterial = new THREE.MeshPhongMaterial({ color: 0x111111, shininess: 50 }); const bodyGeometry = new THREE.ConeGeometry(0.01, 0.02, 3); const body = new THREE.Mesh(bodyGeometry, flyMaterial); body.rotation.x = degToRad(90); body.castShadow = true; body.receiveShadow = true; flyGroup.add(body); flyGroup.userData = { state: 'flying', landTimer: 0, t: 0, speed: FLY_FLIGHT_SPEED_FACTOR + Math.random() * 0.01, curve: null, landCheckTimer: 0, oscillationTime: Math.random() * 100, }; flyGroup.position.copy(this._randomFlyTarget()); return flyGroup; } _createFlyCurve(fly, endPoint) { const startPoint = fly.position.clone(); const midPoint = new THREE.Vector3().lerpVectors(startPoint, endPoint, 0.5); const offsetMagnitude = startPoint.distanceTo(endPoint) * 0.5; const offsetAngle = Math.random() * Math.PI * 2; 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; fly.userData.landCheckTimer = 50 + Math.random() * 50; } _setupFlies(scene) { for (let i = 0; i < FLIES_COUNT; i++) { const fly = this._createFlyMesh(); scene.add(fly); this.flies.push(fly); } } update() { this.flies.forEach(fly => { const data = fly.userData; if (data.state === 'flying' || data.state === 'landing') { if (!data.curve) { const newTargetPos = this._randomFlyTarget(); this._createFlyCurve(fly, newTargetPos); data.t = 0; } data.t += data.speed; data.landCheckTimer--; if (data.t >= 1) { if (data.state === 'landing') { data.state = 'landed'; data.landTimer = FLY_WAIT_BASE + Math.random() * 1000; data.t = 0; return; } if (data.landCheckTimer <= 0 && Math.random() > FLY_LAND_CHANCE) { 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'; let newTargetPos = new THREE.Vector3( intersect.point.x, intersect.point.y + 0.05, intersect.point.z ); this._createFlyCurve(fly, newTargetPos); data.t = 0; } } if (data.state !== 'landing') { const newTargetPos = this._randomFlyTarget(); this._createFlyCurve(fly, newTargetPos); data.t = 0; } } fly.position.copy(data.curve.getPoint(Math.min(data.t, 1))); const tangent = data.curve.getTangent(Math.min(data.t, 1)).normalize(); fly.rotation.y = Math.atan2(tangent.x, tangent.z); data.oscillationTime += 0.1; fly.position.y += Math.sin(data.oscillationTime * 4) * 0.01; } else if (data.state === 'landed') { data.landTimer--; if (data.landTimer <= 0) { data.state = 'flying'; const newTargetPos = this._randomFlyTarget(); this._createFlyCurve(fly, newTargetPos); data.t = 0; } } }); } }