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() {
|
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 = new THREE.SpotLight(0xffffff, 10);
|
||||||
this.flashlight.angle = Math.PI / 6;
|
this.flashlight.angle = Math.PI / 6;
|
||||||
this.flashlight.penumbra = 0.3;
|
this.flashlight.penumbra = 0.3;
|
||||||
this.flashlight.decay = 1.5; // Lower decay for further reach
|
this.flashlight.decay = 1.5;
|
||||||
this.flashlight.distance = 60; // Significantly increased range
|
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.flashlightGroup.add(this.flashlight);
|
||||||
this.flashlight.position.set(0, 0, 0);
|
this.flashlightGroup.add(this.flashlight.target);
|
||||||
this.flashlight.target.position.set(0, 0, -1);
|
|
||||||
this.camera.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() {
|
setupInput() {
|
||||||
@@ -103,8 +141,6 @@ export class Player {
|
|||||||
|
|
||||||
toggleFlashlight() {
|
toggleFlashlight() {
|
||||||
if (!this.controls) return;
|
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.controls.isLocked) return;
|
||||||
|
|
||||||
if (this.battery <= 0 && this.flashlightOn === false) {
|
if (this.battery <= 0 && this.flashlightOn === false) {
|
||||||
@@ -113,9 +149,10 @@ export class Player {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.flashlightOn = !this.flashlightOn;
|
this.flashlightOn = !this.flashlightOn;
|
||||||
if (this.flashlight) {
|
// Update all light components
|
||||||
this.flashlight.visible = this.flashlightOn;
|
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() {
|
lockControls() {
|
||||||
@@ -219,6 +256,8 @@ export class Player {
|
|||||||
if (this.battery <= 0) {
|
if (this.battery <= 0) {
|
||||||
this.flashlightOn = false;
|
this.flashlightOn = false;
|
||||||
this.flashlight.visible = 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!');
|
window.log('Battery depleted!');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -235,13 +274,16 @@ export class Player {
|
|||||||
this.flashlight.intensity = Math.min(50, this.flashlight.intensity + speed * 10);
|
this.flashlight.intensity = Math.min(50, this.flashlight.intensity + speed * 10);
|
||||||
this.flashlight.angle = Math.min(Math.PI / 2, this.flashlight.angle + angleSpeed);
|
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
|
// Log occasionally for feedback
|
||||||
if (Math.random() < 0.1) window.log(`Light: Int=${this.flashlight.intensity.toFixed(1)} Ang=${this.flashlight.angle.toFixed(2)}`);
|
if (Math.random() < 0.1) window.log(`Light: Int=${this.flashlight.intensity.toFixed(1)} Ang=${this.flashlight.angle.toFixed(2)}`);
|
||||||
} else {
|
} else {
|
||||||
// Only flicker if not adjusting? Or flicker on top of base intensity.
|
// Flicker logic
|
||||||
// Let's modify base intensity and add flicker
|
const flicker = (Math.random() - 0.5) * 0.5;
|
||||||
// Simplified: just flicker around the current value
|
this.flashlight.intensity += flicker;
|
||||||
this.flashlight.intensity += (Math.random() - 0.5) * 0.5;
|
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() {
|
createDust() {
|
||||||
// Create 2000 dust particles
|
// Create 800 dust particles (Reduced)
|
||||||
const count = 2000;
|
const count = 800;
|
||||||
const geometry = new THREE.BufferGeometry();
|
const geometry = new THREE.BufferGeometry();
|
||||||
const positions = new Float32Array(count * 3);
|
const positions = new Float32Array(count * 3);
|
||||||
const colors = new Float32Array(count * 3); // For controlling opacity/brightness per particle
|
const colors = new Float32Array(count * 3); // For controlling opacity/brightness per particle
|
||||||
@@ -117,11 +117,16 @@ export class World {
|
|||||||
// Flashlight info
|
// Flashlight info
|
||||||
let lightPos, lightDir, lightAngle, lightDist, isLightOn;
|
let lightPos, lightDir, lightAngle, lightDist, isLightOn;
|
||||||
if (player && player.flashlight) {
|
if (player && player.flashlight) {
|
||||||
// Get world position and direction of camera/flashlight
|
// Get world position and direction of FLASHLIGHT
|
||||||
lightPos = new THREE.Vector3();
|
lightPos = new THREE.Vector3();
|
||||||
lightDir = new THREE.Vector3();
|
lightDir = new THREE.Vector3();
|
||||||
player.camera.getWorldPosition(lightPos);
|
const targetPos = new THREE.Vector3();
|
||||||
player.camera.getWorldDirection(lightDir);
|
|
||||||
|
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
|
lightAngle = player.flashlight.angle; // Cone half-angle
|
||||||
lightDist = player.flashlight.distance;
|
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]) > 20) positions[i * 3] *= -0.9;
|
||||||
if (Math.abs(positions[i * 3 + 2]) > 20) positions[i * 3 + 2] *= -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;
|
let brightness = 0;
|
||||||
if (isLightOn) {
|
if (isLightOn) {
|
||||||
// Check distance
|
// Check distance
|
||||||
@@ -152,25 +157,30 @@ export class World {
|
|||||||
const dist = pPos.distanceTo(lightPos);
|
const dist = pPos.distanceTo(lightPos);
|
||||||
|
|
||||||
if (dist < lightDist) {
|
if (dist < lightDist) {
|
||||||
// Check angle
|
|
||||||
// Vector from light to particle
|
// Vector from light to particle
|
||||||
const toPart = pPos.sub(lightPos).normalize();
|
const toPart = pPos.sub(lightPos).normalize();
|
||||||
const angle = toPart.angleTo(lightDir);
|
const angle = toPart.angleTo(lightDir);
|
||||||
|
|
||||||
|
// Strictly inside the cone with soft edges
|
||||||
if (angle < lightAngle) {
|
if (angle < lightAngle) {
|
||||||
// Inside cone!
|
// "Point of Light" Effect:
|
||||||
// Fade out at edges of cone? optional
|
// 1. Radial falloff: Brightest in center of beam, fades to edge
|
||||||
// Fade out with distance
|
const radialFactor = 1.0 - (angle / lightAngle);
|
||||||
const atten = 1.0 - (dist / lightDist);
|
|
||||||
brightness = atten * atten; // clearer near camera
|
// 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)
|
// Apply color (Gray * brightness)
|
||||||
colors[i * 3] = brightness;
|
const grayScale = 0.3; // Make it gray/subtle
|
||||||
colors[i * 3 + 1] = brightness;
|
colors[i * 3] = brightness * grayScale;
|
||||||
colors[i * 3 + 2] = brightness;
|
colors[i * 3 + 1] = brightness * grayScale;
|
||||||
|
colors[i * 3 + 2] = brightness * grayScale;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.dustParticles.geometry.attributes.position.needsUpdate = true;
|
this.dustParticles.geometry.attributes.position.needsUpdate = true;
|
||||||
|
|||||||
Reference in New Issue
Block a user