feat: Add a visual flashlight model with a secondary point light and enhance volumetric dust particle lighting and performance.
This commit is contained in:
@@ -54,16 +54,54 @@ export class Player {
|
||||
}
|
||||
|
||||
setupFlashlight() {
|
||||
// Group to hold the visual model and lights
|
||||
this.flashlightGroup = new THREE.Group();
|
||||
this.camera.add(this.flashlightGroup);
|
||||
|
||||
// Position: Bottom-right, slightly forward
|
||||
this.flashlightGroup.position.set(0.3, -0.25, -0.4);
|
||||
|
||||
// 1. Visual Model
|
||||
// Flashlight Body
|
||||
const bodyGeo = new THREE.CylinderGeometry(0.03, 0.04, 0.2, 16);
|
||||
bodyGeo.rotateX(-Math.PI / 2); // Point forward
|
||||
const bodyMat = new THREE.MeshStandardMaterial({ color: 0x222222, roughness: 0.5, metalness: 0.8 });
|
||||
const body = new THREE.Mesh(bodyGeo, bodyMat);
|
||||
this.flashlightGroup.add(body);
|
||||
|
||||
// Flashlight Bulb (Emmissive)
|
||||
const bulbGeo = new THREE.CircleGeometry(0.025, 16);
|
||||
const bulbMat = new THREE.MeshBasicMaterial({ color: 0xffffff });
|
||||
const bulb = new THREE.Mesh(bulbGeo, bulbMat);
|
||||
bulb.position.z = -0.101; // Tip of body
|
||||
this.flashlightGroup.add(bulb);
|
||||
this.bulbMesh = bulb; // To toggle emission
|
||||
|
||||
// Hand (Simple representation)
|
||||
const handGeo = new THREE.BoxGeometry(0.08, 0.08, 0.15);
|
||||
const handMat = new THREE.MeshStandardMaterial({ color: 0xdcb898 }); // Skin tone
|
||||
const hand = new THREE.Mesh(handGeo, handMat);
|
||||
hand.position.set(0.05, -0.05, 0.05); // Grip position
|
||||
hand.rotation.set(0.2, 0.2, 0);
|
||||
this.flashlightGroup.add(hand);
|
||||
|
||||
// 2. Lights
|
||||
// Main SpotLight (The beam)
|
||||
this.flashlight = new THREE.SpotLight(0xffffff, 10);
|
||||
this.flashlight.angle = Math.PI / 6;
|
||||
this.flashlight.penumbra = 0.3;
|
||||
this.flashlight.decay = 1.5; // Lower decay for further reach
|
||||
this.flashlight.distance = 60; // Significantly increased range
|
||||
this.flashlight.decay = 1.5;
|
||||
this.flashlight.distance = 60;
|
||||
this.flashlight.position.set(0, 0, -0.1); // At tip
|
||||
this.flashlight.target.position.set(0, 0, -10); // Aim forward
|
||||
|
||||
this.camera.add(this.flashlight);
|
||||
this.flashlight.position.set(0, 0, 0);
|
||||
this.flashlight.target.position.set(0, 0, -1);
|
||||
this.camera.add(this.flashlight.target);
|
||||
this.flashlightGroup.add(this.flashlight);
|
||||
this.flashlightGroup.add(this.flashlight.target);
|
||||
|
||||
// PointLight (The glow around the player)
|
||||
this.bulbLight = new THREE.PointLight(0xffffff, 2, 4); // Low range (4m)
|
||||
this.bulbLight.position.set(0, 0, -0.15); // Slightly ahead of tip
|
||||
this.flashlightGroup.add(this.bulbLight);
|
||||
}
|
||||
|
||||
setupInput() {
|
||||
@@ -103,8 +141,6 @@ export class Player {
|
||||
|
||||
toggleFlashlight() {
|
||||
if (!this.controls) return;
|
||||
// Note: isLocked might be false if user escaped, but we allow 'F' if controls exist?
|
||||
// Better to allow F only when locked to avoid confusion.
|
||||
if (!this.controls.isLocked) return;
|
||||
|
||||
if (this.battery <= 0 && this.flashlightOn === false) {
|
||||
@@ -113,9 +149,10 @@ export class Player {
|
||||
}
|
||||
|
||||
this.flashlightOn = !this.flashlightOn;
|
||||
if (this.flashlight) {
|
||||
this.flashlight.visible = this.flashlightOn;
|
||||
}
|
||||
// Update all light components
|
||||
if (this.flashlight) this.flashlight.visible = this.flashlightOn;
|
||||
if (this.bulbLight) this.bulbLight.visible = this.flashlightOn;
|
||||
if (this.bulbMesh) this.bulbMesh.material.color.setHex(this.flashlightOn ? 0xffffff : 0x111111);
|
||||
}
|
||||
|
||||
lockControls() {
|
||||
@@ -219,6 +256,8 @@ export class Player {
|
||||
if (this.battery <= 0) {
|
||||
this.flashlightOn = false;
|
||||
this.flashlight.visible = false;
|
||||
if (this.bulbLight) this.bulbLight.visible = false;
|
||||
if (this.bulbMesh) this.bulbMesh.material.color.setHex(0x111111);
|
||||
window.log('Battery depleted!');
|
||||
}
|
||||
|
||||
@@ -235,13 +274,16 @@ export class Player {
|
||||
this.flashlight.intensity = Math.min(50, this.flashlight.intensity + speed * 10);
|
||||
this.flashlight.angle = Math.min(Math.PI / 2, this.flashlight.angle + angleSpeed);
|
||||
}
|
||||
// Sync bulb light
|
||||
this.bulbLight.intensity = this.flashlight.intensity * 0.2;
|
||||
|
||||
// Log occasionally for feedback
|
||||
if (Math.random() < 0.1) window.log(`Light: Int=${this.flashlight.intensity.toFixed(1)} Ang=${this.flashlight.angle.toFixed(2)}`);
|
||||
} else {
|
||||
// Only flicker if not adjusting? Or flicker on top of base intensity.
|
||||
// Let's modify base intensity and add flicker
|
||||
// Simplified: just flicker around the current value
|
||||
this.flashlight.intensity += (Math.random() - 0.5) * 0.5;
|
||||
// Flicker logic
|
||||
const flicker = (Math.random() - 0.5) * 0.5;
|
||||
this.flashlight.intensity += flicker;
|
||||
this.bulbLight.intensity = Math.max(0, this.flashlight.intensity * 0.2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
42
src/World.js
42
src/World.js
@@ -48,8 +48,8 @@ export class World {
|
||||
}
|
||||
|
||||
createDust() {
|
||||
// Create 2000 dust particles
|
||||
const count = 2000;
|
||||
// Create 800 dust particles (Reduced)
|
||||
const count = 800;
|
||||
const geometry = new THREE.BufferGeometry();
|
||||
const positions = new Float32Array(count * 3);
|
||||
const colors = new Float32Array(count * 3); // For controlling opacity/brightness per particle
|
||||
@@ -117,11 +117,16 @@ export class World {
|
||||
// Flashlight info
|
||||
let lightPos, lightDir, lightAngle, lightDist, isLightOn;
|
||||
if (player && player.flashlight) {
|
||||
// Get world position and direction of camera/flashlight
|
||||
// Get world position and direction of FLASHLIGHT
|
||||
lightPos = new THREE.Vector3();
|
||||
lightDir = new THREE.Vector3();
|
||||
player.camera.getWorldPosition(lightPos);
|
||||
player.camera.getWorldDirection(lightDir);
|
||||
const targetPos = new THREE.Vector3();
|
||||
|
||||
player.flashlight.getWorldPosition(lightPos);
|
||||
player.flashlight.target.getWorldPosition(targetPos);
|
||||
|
||||
// True Direction: Target - Position
|
||||
lightDir.subVectors(targetPos, lightPos).normalize();
|
||||
|
||||
lightAngle = player.flashlight.angle; // Cone half-angle
|
||||
lightDist = player.flashlight.distance;
|
||||
@@ -144,7 +149,7 @@ export class World {
|
||||
if (Math.abs(positions[i * 3]) > 20) positions[i * 3] *= -0.9;
|
||||
if (Math.abs(positions[i * 3 + 2]) > 20) positions[i * 3 + 2] *= -0.9;
|
||||
|
||||
// 2. Lighting Check
|
||||
// 2. Lighting Check (Volumetric Beam)
|
||||
let brightness = 0;
|
||||
if (isLightOn) {
|
||||
// Check distance
|
||||
@@ -152,25 +157,30 @@ export class World {
|
||||
const dist = pPos.distanceTo(lightPos);
|
||||
|
||||
if (dist < lightDist) {
|
||||
// Check angle
|
||||
// Vector from light to particle
|
||||
const toPart = pPos.sub(lightPos).normalize();
|
||||
const angle = toPart.angleTo(lightDir);
|
||||
|
||||
// Strictly inside the cone with soft edges
|
||||
if (angle < lightAngle) {
|
||||
// Inside cone!
|
||||
// Fade out at edges of cone? optional
|
||||
// Fade out with distance
|
||||
const atten = 1.0 - (dist / lightDist);
|
||||
brightness = atten * atten; // clearer near camera
|
||||
// "Point of Light" Effect:
|
||||
// 1. Radial falloff: Brightest in center of beam, fades to edge
|
||||
const radialFactor = 1.0 - (angle / lightAngle);
|
||||
|
||||
// 2. Distance falloff: Fades with distance
|
||||
const distFactor = 1.0 - (dist / lightDist);
|
||||
|
||||
// Combine: Power of 4 makes it look like a tight beam/point
|
||||
brightness = Math.pow(radialFactor * distFactor, 4);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Apply color (White * brightness)
|
||||
colors[i * 3] = brightness;
|
||||
colors[i * 3 + 1] = brightness;
|
||||
colors[i * 3 + 2] = brightness;
|
||||
// Apply color (Gray * brightness)
|
||||
const grayScale = 0.3; // Make it gray/subtle
|
||||
colors[i * 3] = brightness * grayScale;
|
||||
colors[i * 3 + 1] = brightness * grayScale;
|
||||
colors[i * 3 + 2] = brightness * grayScale;
|
||||
}
|
||||
|
||||
this.dustParticles.geometry.attributes.position.needsUpdate = true;
|
||||
|
||||
Reference in New Issue
Block a user