Compare commits
No commits in common. "cdd90a4c5732b5f86c51e609312a5a4e2768fd72" and "4726b419f421ccf10eef93c7444c3cbe5f27687c" have entirely different histories.
cdd90a4c57
...
4726b419f4
@ -7,13 +7,13 @@ function updateCamera() {
|
|||||||
const globalTime = Date.now() * 0.0001;
|
const globalTime = Date.now() * 0.0001;
|
||||||
const lookAtTime = Date.now() * 0.0002;
|
const lookAtTime = Date.now() * 0.0002;
|
||||||
|
|
||||||
const camAmplitude = new THREE.Vector3(1.0, 0.1, 10.0);
|
const camAmplitude = 1.0;
|
||||||
const lookAmplitude = 8.0;
|
const lookAmplitude = 8.0;
|
||||||
|
|
||||||
// Base Camera Position in front of the TV
|
// Base Camera Position in front of the TV
|
||||||
const baseX = 0;
|
const baseX = 0;
|
||||||
const baseY = 2.6;
|
const baseY = 1.6;
|
||||||
const baseZ = 0.0;
|
const baseZ = -10.0;
|
||||||
|
|
||||||
// Base LookAt target (Center of the screen)
|
// Base LookAt target (Center of the screen)
|
||||||
const baseTargetX = 0;
|
const baseTargetX = 0;
|
||||||
@ -21,9 +21,9 @@ function updateCamera() {
|
|||||||
const baseTargetZ = -30.0;
|
const baseTargetZ = -30.0;
|
||||||
|
|
||||||
// Camera Position Offsets (Drift)
|
// Camera Position Offsets (Drift)
|
||||||
const camOffsetX = Math.sin(globalTime * 3.1) * camAmplitude.x;
|
const camOffsetX = Math.sin(globalTime * 3.1) * camAmplitude;
|
||||||
const camOffsetY = Math.cos(globalTime * 2.5) * camAmplitude.y;
|
const camOffsetY = Math.cos(globalTime * 2.5) * camAmplitude * 0.1;
|
||||||
const camOffsetZ = Math.cos(globalTime * 3.2) * camAmplitude.z;
|
const camOffsetZ = Math.cos(globalTime * 3.2) * camAmplitude;
|
||||||
|
|
||||||
state.camera.position.x = baseX + camOffsetX;
|
state.camera.position.x = baseX + camOffsetX;
|
||||||
state.camera.position.y = baseY + camOffsetY;
|
state.camera.position.y = baseY + camOffsetY;
|
||||||
|
|||||||
@ -3,65 +3,58 @@ import { state } from '../state.js';
|
|||||||
import { SceneFeature } from './SceneFeature.js';
|
import { SceneFeature } from './SceneFeature.js';
|
||||||
import sceneFeatureManager from './SceneFeatureManager.js';
|
import sceneFeatureManager from './SceneFeatureManager.js';
|
||||||
|
|
||||||
// --- Dimensions from room-walls.js for positioning ---
|
|
||||||
const naveWidth = 12;
|
|
||||||
const naveHeight = 15;
|
|
||||||
const length = 40;
|
|
||||||
|
|
||||||
export class LightBall extends SceneFeature {
|
export class LightBall extends SceneFeature {
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
this.lightBalls = [];
|
this.ball = null;
|
||||||
|
this.light = null;
|
||||||
sceneFeatureManager.register(this);
|
sceneFeatureManager.register(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
// --- Ball Properties ---
|
// --- Dimensions from room-walls.js for positioning ---
|
||||||
const ballRadius = 0.4;
|
const naveWidth = 12;
|
||||||
const lightIntensity = 5.0;
|
const naveHeight = 15;
|
||||||
const lightColors = [0xff0000, 0x00ff00, 0x0000ff, 0xffff00, 0x00ffff, 0xff00ff]; // Red, Green, Blue, Yellow
|
const length = 40;
|
||||||
|
|
||||||
|
// --- Ball Properties ---
|
||||||
|
const ballRadius = 1.0;
|
||||||
|
const ballColor = 0xffffff; // White light
|
||||||
|
const lightIntensity = 6.0;
|
||||||
|
|
||||||
lightColors.forEach(color => {
|
|
||||||
// --- Create the Ball ---
|
// --- Create the Ball ---
|
||||||
const ballGeometry = new THREE.SphereGeometry(ballRadius, 32, 32);
|
const ballGeometry = new THREE.SphereGeometry(ballRadius, 32, 32);
|
||||||
const ballMaterial = new THREE.MeshBasicMaterial({ color: color, emissive: color, emissiveIntensity: 1.0 });
|
const ballMaterial = new THREE.MeshBasicMaterial({ color: ballColor, emissive: ballColor, emissiveIntensity: 1.0 });
|
||||||
const ball = new THREE.Mesh(ballGeometry, ballMaterial);
|
this.ball = new THREE.Mesh(ballGeometry, ballMaterial);
|
||||||
ball.castShadow = false;
|
this.ball.castShadow = false;
|
||||||
ball.receiveShadow = false;
|
this.ball.receiveShadow = false;
|
||||||
|
|
||||||
// --- Create the Light ---
|
// --- Create the Light ---
|
||||||
const light = new THREE.PointLight(color, lightIntensity, length / 1.5);
|
this.light = new THREE.PointLight(ballColor, lightIntensity, length / 2); // Adjust range to cathedral size
|
||||||
|
this.light.castShadow = true;
|
||||||
|
this.light.shadow.mapSize.width = 512;
|
||||||
|
this.light.shadow.mapSize.height = 512;
|
||||||
|
this.light.shadow.camera.near = 0.1;
|
||||||
|
this.light.shadow.camera.far = length / 2;
|
||||||
|
|
||||||
// --- Initial Position ---
|
// --- Initial Position ---
|
||||||
ball.position.set(
|
this.ball.position.set(0, naveHeight * 0.7, 0); // Near the ceiling
|
||||||
(Math.random() - 0.5) * naveWidth,
|
this.light.position.copy(this.ball.position);
|
||||||
naveHeight * 0.6 + Math.random() * 4,
|
|
||||||
(Math.random() - 0.5) * length * 0.8
|
|
||||||
);
|
|
||||||
light.position.copy(ball.position);
|
|
||||||
|
|
||||||
state.scene.add(ball);
|
state.scene.add(this.ball);
|
||||||
state.scene.add(light);
|
state.scene.add(this.light);
|
||||||
|
|
||||||
this.lightBalls.push({
|
|
||||||
mesh: ball,
|
|
||||||
light: light,
|
|
||||||
driftSpeed: 0.2 + Math.random() * 0.2,
|
|
||||||
driftAmplitude: 4.0 + Math.random() * 4.0,
|
|
||||||
offset: Math.random() * Math.PI * 6,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
update(deltaTime) {
|
update(deltaTime) {
|
||||||
|
// --- Animate the Ball ---
|
||||||
const time = state.clock.getElapsedTime();
|
const time = state.clock.getElapsedTime();
|
||||||
this.lightBalls.forEach(lb => {
|
const driftSpeed = 0.5;
|
||||||
const { mesh, light, driftSpeed, offset } = lb;
|
const driftAmplitude = 10.0;
|
||||||
mesh.position.x = Math.sin(time * driftSpeed + offset) * naveWidth/2 * 0.8;
|
|
||||||
mesh.position.y = 10 + Math.cos(time * driftSpeed * 1.3 + offset) * naveHeight/2 * 0.6;
|
this.ball.position.x = Math.sin(time * driftSpeed) * driftAmplitude;
|
||||||
mesh.position.z = Math.cos(time * driftSpeed * 0.7 + offset) * length/2 * 0.8;
|
this.ball.position.y = 10 + Math.cos(time * driftSpeed * 1.3) * driftAmplitude * 0.5; // bobbing
|
||||||
light.position.copy(mesh.position);
|
this.ball.position.z = Math.cos(time * driftSpeed * 0.7) * driftAmplitude;
|
||||||
});
|
this.light.position.copy(this.ball.position);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -2,12 +2,7 @@ import * as THREE from 'three';
|
|||||||
import { state } from '../state.js';
|
import { state } from '../state.js';
|
||||||
import { SceneFeature } from './SceneFeature.js';
|
import { SceneFeature } from './SceneFeature.js';
|
||||||
import sceneFeatureManager from './SceneFeatureManager.js';
|
import sceneFeatureManager from './SceneFeatureManager.js';
|
||||||
const musicianTextureUrls = [
|
import musiciansTextureUrl from '/textures/musician1.png';
|
||||||
'/textures/musician1.png',
|
|
||||||
'/textures/musician2.png',
|
|
||||||
'/textures/musician3.png',
|
|
||||||
'/textures/musician4.png',
|
|
||||||
];
|
|
||||||
|
|
||||||
// --- Stage dimensions for positioning ---
|
// --- Stage dimensions for positioning ---
|
||||||
const stageHeight = 1.5;
|
const stageHeight = 1.5;
|
||||||
@ -25,8 +20,9 @@ export class MedievalMusicians extends SceneFeature {
|
|||||||
sceneFeatureManager.register(this);
|
sceneFeatureManager.register(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
async init() {
|
init() {
|
||||||
const processTexture = (texture) => {
|
// Load the texture and create the material inside the callback
|
||||||
|
state.loader.load(musiciansTextureUrl, (texture) => {
|
||||||
// 1. Draw texture to canvas to process it
|
// 1. Draw texture to canvas to process it
|
||||||
const image = texture.image;
|
const image = texture.image;
|
||||||
const canvas = document.createElement('canvas');
|
const canvas = document.createElement('canvas');
|
||||||
@ -57,23 +53,18 @@ export class MedievalMusicians extends SceneFeature {
|
|||||||
}
|
}
|
||||||
context.putImageData(imageData, 0, 0);
|
context.putImageData(imageData, 0, 0);
|
||||||
|
|
||||||
return new THREE.CanvasTexture(canvas);
|
// 4. Create a new texture from the modified canvas
|
||||||
};
|
const processedTexture = new THREE.CanvasTexture(canvas);
|
||||||
|
|
||||||
// Load and process all textures, creating a material for each
|
// 5. Create a standard material with the new texture
|
||||||
const materials = await Promise.all(musicianTextureUrls.map(async (url) => {
|
const material = new THREE.MeshStandardMaterial({
|
||||||
const texture = await state.loader.loadAsync(url);
|
|
||||||
const processedTexture = processTexture(texture);
|
|
||||||
return new THREE.MeshStandardMaterial({
|
|
||||||
map: processedTexture,
|
map: processedTexture,
|
||||||
side: THREE.DoubleSide,
|
side: THREE.DoubleSide,
|
||||||
alphaTest: 0.5, // Treat pixels with alpha < 0.5 as fully transparent
|
alphaTest: 0.5, // Treat pixels with alpha < 0.5 as fully transparent
|
||||||
roughness: 0.7,
|
roughness: 0.7,
|
||||||
metalness: 0.1,
|
metalness: 0.1,
|
||||||
});
|
});
|
||||||
}));
|
|
||||||
|
|
||||||
const createMusicians = () => {
|
|
||||||
// 6. Create and position the musicians
|
// 6. Create and position the musicians
|
||||||
const geometry = new THREE.PlaneGeometry(musicianWidth, musicianHeight);
|
const geometry = new THREE.PlaneGeometry(musicianWidth, musicianHeight);
|
||||||
|
|
||||||
@ -81,12 +72,9 @@ export class MedievalMusicians extends SceneFeature {
|
|||||||
new THREE.Vector3(-2, stageHeight + musicianHeight / 2, -length / 2 + stageDepth / 2 - 1),
|
new THREE.Vector3(-2, stageHeight + musicianHeight / 2, -length / 2 + stageDepth / 2 - 1),
|
||||||
new THREE.Vector3(0, stageHeight + musicianHeight / 2, -length / 2 + stageDepth / 2 - 1.5),
|
new THREE.Vector3(0, stageHeight + musicianHeight / 2, -length / 2 + stageDepth / 2 - 1.5),
|
||||||
new THREE.Vector3(2.5, stageHeight + musicianHeight / 2, -length / 2 + stageDepth / 2 - 1.2),
|
new THREE.Vector3(2.5, stageHeight + musicianHeight / 2, -length / 2 + stageDepth / 2 - 1.2),
|
||||||
new THREE.Vector3(1.2, stageHeight + musicianHeight / 2, -length / 2 + stageDepth / 2 - 0.4),
|
|
||||||
];
|
];
|
||||||
|
|
||||||
musicianPositions.forEach((pos, index) => {
|
musicianPositions.forEach(pos => {
|
||||||
// Randomly pick one of the created materials
|
|
||||||
const material = materials[Math.floor(index % materials.length)];
|
|
||||||
const musician = new THREE.Mesh(geometry, material);
|
const musician = new THREE.Mesh(geometry, material);
|
||||||
musician.position.copy(pos);
|
musician.position.copy(pos);
|
||||||
state.scene.add(musician);
|
state.scene.add(musician);
|
||||||
@ -109,9 +97,7 @@ export class MedievalMusicians extends SceneFeature {
|
|||||||
jumpStartTime: 0,
|
jumpStartTime: 0,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
});
|
||||||
|
|
||||||
createMusicians();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
update(deltaTime) {
|
update(deltaTime) {
|
||||||
@ -128,9 +114,7 @@ export class MedievalMusicians extends SceneFeature {
|
|||||||
const planeJumpChance = 0.1;
|
const planeJumpChance = 0.1;
|
||||||
const jumpChance = 0.005;
|
const jumpChance = 0.005;
|
||||||
const jumpDuration = 0.5;
|
const jumpDuration = 0.5;
|
||||||
const jumpHeight = 1.0;
|
const jumpHeight = 2.0;
|
||||||
const jumpVariance = 1.0;
|
|
||||||
const jumpPlaneVariance = 2.0;
|
|
||||||
|
|
||||||
this.musicians.forEach(musicianObj => {
|
this.musicians.forEach(musicianObj => {
|
||||||
const { mesh } = musicianObj;
|
const { mesh } = musicianObj;
|
||||||
@ -147,7 +131,7 @@ export class MedievalMusicians extends SceneFeature {
|
|||||||
// --- Decide to jump to the other plane ---
|
// --- Decide to jump to the other plane ---
|
||||||
musicianObj.state = 'PREPARING_JUMP';
|
musicianObj.state = 'PREPARING_JUMP';
|
||||||
const targetX = (Math.random() - 0.5) * area.x;
|
const targetX = (Math.random() - 0.5) * area.x;
|
||||||
musicianObj.targetPosition = new THREE.Vector3(targetX, area.y + musicianHeight/2, planeEdgeZ);
|
musicianObj.targetPosition = new THREE.Vector3(targetX, mesh.position.y, planeEdgeZ);
|
||||||
} else {
|
} else {
|
||||||
// --- Decide to move to a new spot on the current plane ---
|
// --- Decide to move to a new spot on the current plane ---
|
||||||
const newTarget = new THREE.Vector3(
|
const newTarget = new THREE.Vector3(
|
||||||
@ -177,7 +161,6 @@ export class MedievalMusicians extends SceneFeature {
|
|||||||
} else {
|
} else {
|
||||||
// --- Arrived at edge, start the plane jump ---
|
// --- Arrived at edge, start the plane jump ---
|
||||||
musicianObj.state = 'JUMPING_PLANE';
|
musicianObj.state = 'JUMPING_PLANE';
|
||||||
musicianObj.jumpHeight = jumpHeight + Math.random() * jumpPlaneVariance;
|
|
||||||
musicianObj.jumpStartPos = mesh.position.clone();
|
musicianObj.jumpStartPos = mesh.position.clone();
|
||||||
const targetPlane = musicianObj.currentPlane === 'stage' ? 'floor' : 'stage';
|
const targetPlane = musicianObj.currentPlane === 'stage' ? 'floor' : 'stage';
|
||||||
const targetArea = targetPlane === 'stage' ? stageArea : floorArea;
|
const targetArea = targetPlane === 'stage' ? stageArea : floorArea;
|
||||||
@ -195,7 +178,7 @@ export class MedievalMusicians extends SceneFeature {
|
|||||||
if (musicianObj.jumpProgress < 1) {
|
if (musicianObj.jumpProgress < 1) {
|
||||||
// Determine base height based on which half of the jump we're in
|
// Determine base height based on which half of the jump we're in
|
||||||
const baseHeight = musicianObj.jumpProgress < 0.5 ? musicianObj.jumpStartPos.y : musicianObj.jumpEndPos.y;
|
const baseHeight = musicianObj.jumpProgress < 0.5 ? musicianObj.jumpStartPos.y : musicianObj.jumpEndPos.y;
|
||||||
const arcHeight = Math.sin(musicianObj.jumpProgress * Math.PI) * musicianObj.jumpHeight;
|
const arcHeight = Math.sin(musicianObj.jumpProgress * Math.PI) * jumpHeight;
|
||||||
|
|
||||||
// Interpolate horizontal position
|
// Interpolate horizontal position
|
||||||
const horizontalProgress = musicianObj.jumpProgress;
|
const horizontalProgress = musicianObj.jumpProgress;
|
||||||
@ -218,7 +201,7 @@ export class MedievalMusicians extends SceneFeature {
|
|||||||
const jumpProgress = (time - musicianObj.jumpStartTime) / jumpDuration;
|
const jumpProgress = (time - musicianObj.jumpStartTime) / jumpDuration;
|
||||||
if (jumpProgress < 1) {
|
if (jumpProgress < 1) {
|
||||||
const baseHeight = area.y + musicianHeight/2;
|
const baseHeight = area.y + musicianHeight/2;
|
||||||
mesh.position.y = baseHeight + Math.sin(jumpProgress * Math.PI) * musicianObj.jumpHeight;
|
mesh.position.y = baseHeight + Math.sin(jumpProgress * Math.PI) * jumpHeight;
|
||||||
} else {
|
} else {
|
||||||
musicianObj.isJumping = false;
|
musicianObj.isJumping = false;
|
||||||
mesh.position.y = area.y + musicianHeight / 2;
|
mesh.position.y = area.y + musicianHeight / 2;
|
||||||
@ -226,7 +209,6 @@ export class MedievalMusicians extends SceneFeature {
|
|||||||
} else {
|
} else {
|
||||||
if (Math.random() < jumpChance && musicianObj.state !== 'JUMPING_PLANE' && musicianObj.state !== 'PREPARING_JUMP') {
|
if (Math.random() < jumpChance && musicianObj.state !== 'JUMPING_PLANE' && musicianObj.state !== 'PREPARING_JUMP') {
|
||||||
musicianObj.isJumping = true;
|
musicianObj.isJumping = true;
|
||||||
musicianObj.jumpHeight = jumpHeight + Math.random() * jumpVariance;
|
|
||||||
musicianObj.jumpStartTime = time;
|
musicianObj.jumpStartTime = time;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,152 +0,0 @@
|
|||||||
import * as THREE from 'three';
|
|
||||||
import { state } from '../state.js';
|
|
||||||
import { SceneFeature } from './SceneFeature.js';
|
|
||||||
import sceneFeatureManager from './SceneFeatureManager.js';
|
|
||||||
const guestTextureUrls = [
|
|
||||||
'/textures/guest1.png',
|
|
||||||
'/textures/guest2.png',
|
|
||||||
'/textures/guest3.png',
|
|
||||||
'/textures/guest4.png',
|
|
||||||
];
|
|
||||||
|
|
||||||
// --- Scene dimensions for positioning ---
|
|
||||||
const stageHeight = 1.5;
|
|
||||||
const stageDepth = 5;
|
|
||||||
const length = 44;
|
|
||||||
|
|
||||||
// --- Billboard Properties ---
|
|
||||||
const guestHeight = 2.5;
|
|
||||||
const guestWidth = 2.5;
|
|
||||||
|
|
||||||
export class PartyGuests extends SceneFeature {
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
this.guests = [];
|
|
||||||
sceneFeatureManager.register(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
async init() {
|
|
||||||
const processTexture = (texture) => {
|
|
||||||
const image = texture.image;
|
|
||||||
const canvas = document.createElement('canvas');
|
|
||||||
canvas.width = image.width;
|
|
||||||
canvas.height = image.height;
|
|
||||||
const context = canvas.getContext('2d');
|
|
||||||
context.drawImage(image, 0, 0);
|
|
||||||
const keyPixelData = context.getImageData(0, 0, 1, 1).data;
|
|
||||||
const keyColor = { r: keyPixelData[0], g: keyPixelData[1], b: keyPixelData[2] };
|
|
||||||
const imageData = context.getImageData(0, 0, canvas.width, canvas.height);
|
|
||||||
const data = imageData.data;
|
|
||||||
const threshold = 20;
|
|
||||||
for (let i = 0; i < data.length; i += 4) {
|
|
||||||
const r = data[i], g = data[i + 1], b = data[i + 2];
|
|
||||||
const distance = Math.sqrt(Math.pow(r - keyColor.r, 2) + Math.pow(g - keyColor.g, 2) + Math.pow(b - keyColor.b, 2));
|
|
||||||
if (distance < threshold) data[i + 3] = 0;
|
|
||||||
}
|
|
||||||
context.putImageData(imageData, 0, 0);
|
|
||||||
return new THREE.CanvasTexture(canvas);
|
|
||||||
};
|
|
||||||
|
|
||||||
const materials = await Promise.all(guestTextureUrls.map(async (url) => {
|
|
||||||
const texture = await state.loader.loadAsync(url);
|
|
||||||
const processedTexture = processTexture(texture);
|
|
||||||
return new THREE.MeshStandardMaterial({
|
|
||||||
map: processedTexture,
|
|
||||||
side: THREE.DoubleSide,
|
|
||||||
alphaTest: 0.5,
|
|
||||||
roughness: 0.7,
|
|
||||||
metalness: 0.1,
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
|
|
||||||
const createGuests = () => {
|
|
||||||
const geometry = new THREE.PlaneGeometry(guestWidth, guestHeight);
|
|
||||||
const numGuests = 80;
|
|
||||||
|
|
||||||
for (let i = 0; i < numGuests; i++) {
|
|
||||||
const material = materials[i % materials.length];
|
|
||||||
const guest = new THREE.Mesh(geometry, material);
|
|
||||||
const pos = new THREE.Vector3(
|
|
||||||
(Math.random() - 0.5) * 10,
|
|
||||||
guestHeight / 2,
|
|
||||||
(Math.random() * 20) - 12 // Position them in the main hall
|
|
||||||
);
|
|
||||||
guest.position.copy(pos);
|
|
||||||
state.scene.add(guest);
|
|
||||||
|
|
||||||
this.guests.push({
|
|
||||||
mesh: guest,
|
|
||||||
state: 'WAITING',
|
|
||||||
targetPosition: pos.clone(),
|
|
||||||
waitStartTime: 0,
|
|
||||||
waitTime: 3 + Math.random() * 4, // Wait longer: 3-7 seconds
|
|
||||||
isJumping: false,
|
|
||||||
jumpStartTime: 0,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
createGuests();
|
|
||||||
}
|
|
||||||
|
|
||||||
update(deltaTime) {
|
|
||||||
if (this.guests.length === 0) return;
|
|
||||||
|
|
||||||
const cameraPosition = new THREE.Vector3();
|
|
||||||
state.camera.getWorldPosition(cameraPosition);
|
|
||||||
|
|
||||||
const time = state.clock.getElapsedTime();
|
|
||||||
const moveSpeed = 1.0; // Move slower
|
|
||||||
const movementArea = { x: 10, z: 30, y: 0, centerZ: 0 };
|
|
||||||
const jumpChance = 0.05; // Jump way more
|
|
||||||
const jumpDuration = 0.5;
|
|
||||||
const jumpHeight = 0.1;
|
|
||||||
const jumpVariance = 0.5;
|
|
||||||
|
|
||||||
this.guests.forEach(guestObj => {
|
|
||||||
const { mesh } = guestObj;
|
|
||||||
mesh.lookAt(cameraPosition.x, mesh.position.y, cameraPosition.z);
|
|
||||||
|
|
||||||
if (guestObj.state === 'WAITING') {
|
|
||||||
if (time > guestObj.waitStartTime + guestObj.waitTime) {
|
|
||||||
const newTarget = new THREE.Vector3(
|
|
||||||
(Math.random() - 0.5) * movementArea.x,
|
|
||||||
movementArea.y + guestHeight / 2,
|
|
||||||
movementArea.centerZ + (Math.random() - 0.5) * movementArea.z
|
|
||||||
);
|
|
||||||
guestObj.targetPosition = newTarget;
|
|
||||||
guestObj.state = 'MOVING';
|
|
||||||
}
|
|
||||||
} else if (guestObj.state === 'MOVING') {
|
|
||||||
const distance = mesh.position.distanceTo(guestObj.targetPosition);
|
|
||||||
if (distance > 0.1) {
|
|
||||||
const direction = guestObj.targetPosition.clone().sub(mesh.position).normalize();
|
|
||||||
mesh.position.add(direction.multiplyScalar(moveSpeed * deltaTime));
|
|
||||||
} else {
|
|
||||||
guestObj.state = 'WAITING';
|
|
||||||
guestObj.waitStartTime = time;
|
|
||||||
guestObj.waitTime = 3 + Math.random() * 4;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (guestObj.isJumping) {
|
|
||||||
const jumpProgress = (time - guestObj.jumpStartTime) / jumpDuration;
|
|
||||||
if (jumpProgress < 1) {
|
|
||||||
const baseHeight = movementArea.y + guestHeight / 2;
|
|
||||||
mesh.position.y = baseHeight + Math.sin(jumpProgress * Math.PI) * guestObj.jumpHeight;
|
|
||||||
} else {
|
|
||||||
guestObj.isJumping = false;
|
|
||||||
mesh.position.y = movementArea.y + guestHeight / 2;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (Math.random() < jumpChance) {
|
|
||||||
guestObj.isJumping = true;
|
|
||||||
guestObj.jumpHeight = jumpHeight + Math.random() * jumpVariance;
|
|
||||||
guestObj.jumpStartTime = time;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
new PartyGuests();
|
|
||||||
@ -40,7 +40,7 @@ export class RoomWalls extends SceneFeature {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// --- Geometry Definitions ---
|
// --- Geometry Definitions ---
|
||||||
const pillarGeo = new THREE.CylinderGeometry(pillarSize / 2, pillarSize / 2, pillarHeight, 24);
|
const pillarGeo = new THREE.BoxGeometry(pillarSize, pillarHeight, pillarSize);
|
||||||
|
|
||||||
// --- Object Creation Functions ---
|
// --- Object Creation Functions ---
|
||||||
const createMesh = (geometry, material, position, rotation = new THREE.Euler()) => {
|
const createMesh = (geometry, material, position, rotation = new THREE.Euler()) => {
|
||||||
@ -89,7 +89,7 @@ export class RoomWalls extends SceneFeature {
|
|||||||
const z = -length / 2 + pillarSpacing * (i + 0.5);
|
const z = -length / 2 + pillarSpacing * (i + 0.5);
|
||||||
// Add wall sections between pillars
|
// Add wall sections between pillars
|
||||||
if (i <= numPillars) {
|
if (i <= numPillars) {
|
||||||
createMesh(arcadeWallGeo, arcadeWallMat, new THREE.Vector3(-naveWidth / 2 , pillarHeight + arcadeWallHeight / 2, z));
|
createMesh(arcadeWallGeo, arcadeWallMat, new THREE.Vector3(-naveWidth / 2, pillarHeight + arcadeWallHeight / 2, z));
|
||||||
createMesh(arcadeWallGeo, arcadeWallMat, new THREE.Vector3(naveWidth / 2, pillarHeight + arcadeWallHeight / 2, z));
|
createMesh(arcadeWallGeo, arcadeWallMat, new THREE.Vector3(naveWidth / 2, pillarHeight + arcadeWallHeight / 2, z));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -8,8 +8,6 @@ import { LightBall } from './light-ball.js';
|
|||||||
import { Pews } from './pews.js';
|
import { Pews } from './pews.js';
|
||||||
import { Stage } from './stage.js';
|
import { Stage } from './stage.js';
|
||||||
import { MedievalMusicians } from './medieval-musicians.js';
|
import { MedievalMusicians } from './medieval-musicians.js';
|
||||||
import { PartyGuests } from './party-guests.js';
|
|
||||||
import { StageTorches } from './stage-torches.js';
|
|
||||||
// Scene Features ^^^
|
// Scene Features ^^^
|
||||||
|
|
||||||
// --- Scene Modeling Function ---
|
// --- Scene Modeling Function ---
|
||||||
|
|||||||
@ -1,117 +0,0 @@
|
|||||||
import * as THREE from 'three';
|
|
||||||
import { state } from '../state.js';
|
|
||||||
import { SceneFeature } from './SceneFeature.js';
|
|
||||||
import sceneFeatureManager from './SceneFeatureManager.js';
|
|
||||||
import sparkTextureUrl from '/textures/spark.png';
|
|
||||||
|
|
||||||
export class StageTorches extends SceneFeature {
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
this.torches = [];
|
|
||||||
sceneFeatureManager.register(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
init() {
|
|
||||||
// --- Stage Dimensions for positioning ---
|
|
||||||
const length = 40;
|
|
||||||
const naveWidth = 12;
|
|
||||||
const stageWidth = naveWidth - 1;
|
|
||||||
const stageHeight = 1.5;
|
|
||||||
const stageDepth = 5;
|
|
||||||
|
|
||||||
const torchPositions = [
|
|
||||||
new THREE.Vector3(-stageWidth / 2, stageHeight, -length / 2 + 0.5),
|
|
||||||
new THREE.Vector3(stageWidth / 2, stageHeight, -length / 2 + 0.5),
|
|
||||||
new THREE.Vector3(-stageWidth / 2, stageHeight, -length / 2 + stageDepth - 0.5),
|
|
||||||
new THREE.Vector3(stageWidth / 2, stageHeight, -length / 2 + stageDepth - 0.5),
|
|
||||||
];
|
|
||||||
|
|
||||||
torchPositions.forEach(pos => {
|
|
||||||
const torch = this.createTorch(pos);
|
|
||||||
this.torches.push(torch);
|
|
||||||
state.scene.add(torch.group);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
createTorch(position) {
|
|
||||||
const torchGroup = new THREE.Group();
|
|
||||||
torchGroup.position.copy(position);
|
|
||||||
|
|
||||||
// --- Torch Holder ---
|
|
||||||
const holderMaterial = new THREE.MeshStandardMaterial({ color: 0x333333, roughness: 0.6, metalness: 0.5 });
|
|
||||||
const holderGeo = new THREE.CylinderGeometry(0.1, 0.15, 1.0, 12);
|
|
||||||
const holderMesh = new THREE.Mesh(holderGeo, holderMaterial);
|
|
||||||
holderMesh.position.y = 0.5;
|
|
||||||
holderMesh.castShadow = true;
|
|
||||||
holderMesh.receiveShadow = true;
|
|
||||||
torchGroup.add(holderMesh);
|
|
||||||
|
|
||||||
// --- Point Light ---
|
|
||||||
const pointLight = new THREE.PointLight(0xffaa44, 2.5, 8);
|
|
||||||
pointLight.position.y = 1.2;
|
|
||||||
pointLight.castShadow = true;
|
|
||||||
pointLight.shadow.mapSize.width = 128;
|
|
||||||
pointLight.shadow.mapSize.height = 128;
|
|
||||||
torchGroup.add(pointLight);
|
|
||||||
|
|
||||||
// --- Particle System for Fire ---
|
|
||||||
const particleCount = 50;
|
|
||||||
const particles = new THREE.BufferGeometry();
|
|
||||||
const positions = [];
|
|
||||||
const particleData = [];
|
|
||||||
|
|
||||||
const sparkTexture = state.loader.load(sparkTextureUrl);
|
|
||||||
const particleMaterial = new THREE.PointsMaterial({
|
|
||||||
map: sparkTexture,
|
|
||||||
color: 0xffaa00,
|
|
||||||
size: 0.5,
|
|
||||||
blending: THREE.AdditiveBlending,
|
|
||||||
transparent: true,
|
|
||||||
depthWrite: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
for (let i = 0; i < particleCount; i++) {
|
|
||||||
positions.push(0, 1, 0);
|
|
||||||
particleData.push({
|
|
||||||
velocity: new THREE.Vector3((Math.random() - 0.5) * 0.2, Math.random() * 1.5, (Math.random() - 0.5) * 0.2),
|
|
||||||
life: Math.random() * 1.0,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
particles.setAttribute('position', new THREE.Float32BufferAttribute(positions, 3));
|
|
||||||
const particleSystem = new THREE.Points(particles, particleMaterial);
|
|
||||||
torchGroup.add(particleSystem);
|
|
||||||
|
|
||||||
return { group: torchGroup, light: pointLight, particles: particleSystem, particleData: particleData };
|
|
||||||
}
|
|
||||||
|
|
||||||
update(deltaTime) {
|
|
||||||
this.torches.forEach(torch => {
|
|
||||||
// --- Animate Particles ---
|
|
||||||
const positions = torch.particles.geometry.attributes.position.array;
|
|
||||||
for (let i = 0; i < torch.particleData.length; i++) {
|
|
||||||
const data = torch.particleData[i];
|
|
||||||
data.life -= deltaTime;
|
|
||||||
|
|
||||||
if (data.life <= 0) {
|
|
||||||
// Reset particle
|
|
||||||
positions[i * 3] = 0;
|
|
||||||
positions[i * 3 + 1] = 1;
|
|
||||||
positions[i * 3 + 2] = 0;
|
|
||||||
data.life = Math.random() * 1.0;
|
|
||||||
} else {
|
|
||||||
// Update position
|
|
||||||
positions[i * 3] += data.velocity.x * deltaTime;
|
|
||||||
positions[i * 3 + 1] += data.velocity.y * deltaTime;
|
|
||||||
positions[i * 3 + 2] += data.velocity.z * deltaTime;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
torch.particles.geometry.attributes.position.needsUpdate = true;
|
|
||||||
|
|
||||||
// --- Flicker Light ---
|
|
||||||
const flicker = Math.random() * 0.5;
|
|
||||||
torch.light.intensity = 2.0 + flicker;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
new StageTorches();
|
|
||||||
@ -17,7 +17,6 @@ export class StainedGlass extends SceneFeature {
|
|||||||
const aisleWidth = 6;
|
const aisleWidth = 6;
|
||||||
const totalWidth = naveWidth + 2 * aisleWidth;
|
const totalWidth = naveWidth + 2 * aisleWidth;
|
||||||
const aisleHeight = 8;
|
const aisleHeight = 8;
|
||||||
const naveHeight = 15;
|
|
||||||
|
|
||||||
// --- Window Properties ---
|
// --- Window Properties ---
|
||||||
const windowWidth = 3;
|
const windowWidth = 3;
|
||||||
@ -32,11 +31,18 @@ export class StainedGlass extends SceneFeature {
|
|||||||
side: THREE.DoubleSide,
|
side: THREE.DoubleSide,
|
||||||
metalness: 0.1, // Glass is not very metallic
|
metalness: 0.1, // Glass is not very metallic
|
||||||
roughness: 0.3, // Glass is smooth
|
roughness: 0.3, // Glass is smooth
|
||||||
emissive: 0x222222, // Set to white to use vertex colors for glow
|
clearcoat: 1.0,
|
||||||
emissiveIntensity: 0.1, // Start with a base intensity
|
emissive: 0x000000, // We will control emissiveness via update
|
||||||
//blending: THREE.AdditiveBlending, // Additive blending for a bright, glowing effect
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// --- Procedural Geometry Generation ---
|
||||||
|
const createProceduralWindowGeometry = () => {
|
||||||
|
const segmentsX = 8;
|
||||||
|
const segmentsY = 12;
|
||||||
|
const vertices = [];
|
||||||
|
const colors = [];
|
||||||
|
const normals = [];
|
||||||
|
|
||||||
const colorPalette = [
|
const colorPalette = [
|
||||||
new THREE.Color(0x6A0DAD), // Purple
|
new THREE.Color(0x6A0DAD), // Purple
|
||||||
new THREE.Color(0x00008B), // Dark Blue
|
new THREE.Color(0x00008B), // Dark Blue
|
||||||
@ -47,42 +53,12 @@ export class StainedGlass extends SceneFeature {
|
|||||||
new THREE.Color(0x4B0082), // Indigo
|
new THREE.Color(0x4B0082), // Indigo
|
||||||
];
|
];
|
||||||
|
|
||||||
// --- Procedural Geometry Generation ---
|
|
||||||
const createProceduralWindowGeometry = (mainColor) => {
|
|
||||||
const secondColor = new THREE.Color((Math.random()), (Math.random()), (Math.random()));
|
|
||||||
const segmentsX = 8;
|
|
||||||
const segmentsY = 12;
|
|
||||||
const vertices = [];
|
|
||||||
const colors = [];
|
|
||||||
const normals = [];
|
|
||||||
|
|
||||||
const randomnessFactor = 0.4; // How much to vary the normals
|
const randomnessFactor = 0.4; // How much to vary the normals
|
||||||
|
|
||||||
const addTriangle = (v1, v2, v3, isBorder = false) => {
|
const addTriangle = (v1, v2, v3) => {
|
||||||
let segmentColor;
|
const color = colorPalette[Math.floor(Math.random() * colorPalette.length)];
|
||||||
const rand = Math.random();
|
|
||||||
|
|
||||||
if (rand < 0.05) { // 5% chance for a "lead line"
|
|
||||||
segmentColor = secondColor.clone();
|
|
||||||
} else if (isBorder && rand < 0.6) { // 60% chance for border segments to be the main color
|
|
||||||
segmentColor = mainColor.clone().offsetHSL(0, 0, -0.1); // Slightly darker border
|
|
||||||
} else if (isBorder) { // Remaining chance for border to be an accent
|
|
||||||
segmentColor = colorPalette[Math.floor(Math.random() * colorPalette.length)].clone().offsetHSL(0, 0, -0.1);
|
|
||||||
}
|
|
||||||
else { // Inner panels
|
|
||||||
if (rand < 0.65) { // 65% chance (after lead lines) to be a variation of the main color
|
|
||||||
segmentColor = mainColor.clone();
|
|
||||||
// Slightly shift hue, saturation, and lightness
|
|
||||||
segmentColor.offsetHSL((Math.random() - 0.5) * 0.2, (Math.random() - 0.5) * 0.2, (Math.random() - 0.5) * 0.4);
|
|
||||||
} else if (rand < 0.8) {
|
|
||||||
segmentColor = secondColor.clone().offsetHSL((Math.random() - 0.5) * 0.2, (Math.random() - 0.5) * 0.2, (Math.random() - 0.5) * 0.4);
|
|
||||||
} else { // Remaining chance for a random accent color
|
|
||||||
segmentColor = new THREE.Color((Math.random()), (Math.random()), (Math.random()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
vertices.push(v1.x, v1.y, v1.z, v2.x, v2.y, v2.z, v3.x, v3.y, v3.z);
|
vertices.push(v1.x, v1.y, v1.z, v2.x, v2.y, v2.z, v3.x, v3.y, v3.z);
|
||||||
colors.push(segmentColor.r, segmentColor.g, segmentColor.b, segmentColor.r, segmentColor.g, segmentColor.b, segmentColor.r, segmentColor.g, segmentColor.b);
|
colors.push(color.r, color.g, color.b, color.r, color.g, color.b, color.r, color.g, color.b);
|
||||||
|
|
||||||
// Calculate the base normal for the flat triangle face
|
// Calculate the base normal for the flat triangle face
|
||||||
const edge1 = new THREE.Vector3().subVectors(v2, v1);
|
const edge1 = new THREE.Vector3().subVectors(v2, v1);
|
||||||
@ -109,9 +85,8 @@ export class StainedGlass extends SceneFeature {
|
|||||||
const v2 = new THREE.Vector3(x2, y, 0);
|
const v2 = new THREE.Vector3(x2, y, 0);
|
||||||
const v3 = new THREE.Vector3(x, y2, 0);
|
const v3 = new THREE.Vector3(x, y2, 0);
|
||||||
const v4 = new THREE.Vector3(x2, y2, 0);
|
const v4 = new THREE.Vector3(x2, y2, 0);
|
||||||
const isBorder = i === 0 || i === segmentsX - 1 || j === 0;
|
addTriangle(v1, v2, v3);
|
||||||
addTriangle(v1, v2, v3, isBorder);
|
addTriangle(v2, v4, v3);
|
||||||
addTriangle(v2, v4, v3, isBorder);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -123,7 +98,7 @@ export class StainedGlass extends SceneFeature {
|
|||||||
const v1 = archCenter;
|
const v1 = archCenter;
|
||||||
const v2 = new THREE.Vector3(Math.cos(angle1) * -windowWidth / 2, Math.sin(angle1) * windowArchHeight + windowBaseHeight, 0);
|
const v2 = new THREE.Vector3(Math.cos(angle1) * -windowWidth / 2, Math.sin(angle1) * windowArchHeight + windowBaseHeight, 0);
|
||||||
const v3 = new THREE.Vector3(Math.cos(angle2) * -windowWidth / 2, Math.sin(angle2) * windowArchHeight + windowBaseHeight, 0);
|
const v3 = new THREE.Vector3(Math.cos(angle2) * -windowWidth / 2, Math.sin(angle2) * windowArchHeight + windowBaseHeight, 0);
|
||||||
addTriangle(v1, v2, v3, true); // Treat all arch segments as part of the border
|
addTriangle(v1, v2, v3);
|
||||||
}
|
}
|
||||||
|
|
||||||
const geometry = new THREE.BufferGeometry();
|
const geometry = new THREE.BufferGeometry();
|
||||||
@ -133,67 +108,9 @@ export class StainedGlass extends SceneFeature {
|
|||||||
return geometry;
|
return geometry;
|
||||||
};
|
};
|
||||||
|
|
||||||
const createProceduralRoseWindowGeometry = (mainColor, radius) => {
|
|
||||||
const segments = 32; // Number of radial segments
|
|
||||||
const rings = 5;
|
|
||||||
const vertices = [];
|
|
||||||
const colors = [];
|
|
||||||
const normals = [];
|
|
||||||
|
|
||||||
const randomnessFactor = 0.4;
|
|
||||||
|
|
||||||
const addTriangle = (v1, v2, v3, isBorder = false) => {
|
|
||||||
let segmentColor;
|
|
||||||
const rand = Math.random();
|
|
||||||
|
|
||||||
if (rand < 0.05) { // 5% chance for a "lead line"
|
|
||||||
segmentColor = new THREE.Color(0x333333);
|
|
||||||
} else if (isBorder && rand < 0.6) {
|
|
||||||
segmentColor = mainColor.clone().offsetHSL(0, 0, -0.1);
|
|
||||||
} else if (isBorder) {
|
|
||||||
segmentColor = colorPalette[Math.floor(Math.random() * colorPalette.length)].clone().offsetHSL(0, 0, -0.1);
|
|
||||||
} else {
|
|
||||||
if (rand < 0.85) {
|
|
||||||
segmentColor = mainColor.clone();
|
|
||||||
segmentColor.offsetHSL((Math.random() - 0.5) * 0.1, (Math.random() - 0.5) * 0.3, (Math.random() - 0.5) * 0.2);
|
|
||||||
} else {
|
|
||||||
segmentColor = colorPalette[Math.floor(Math.random() * colorPalette.length)];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
vertices.push(v1.x, v1.y, v1.z, v2.x, v2.y, v2.z, v3.x, v3.y, v3.z);
|
|
||||||
colors.push(segmentColor.r, segmentColor.g, segmentColor.b, segmentColor.r, segmentColor.g, segmentColor.b, segmentColor.r, segmentColor.g, segmentColor.b);
|
|
||||||
const edge1 = new THREE.Vector3().subVectors(v2, v1);
|
|
||||||
const edge2 = new THREE.Vector3().subVectors(v3, v1);
|
|
||||||
const faceNormal = new THREE.Vector3().crossVectors(edge1, edge2).normalize();
|
|
||||||
const randomVec = new THREE.Vector3(Math.random() - 0.5, Math.random() - 0.5, Math.random() - 0.5).normalize();
|
|
||||||
faceNormal.add(randomVec.multiplyScalar(randomnessFactor)).normalize();
|
|
||||||
normals.push(faceNormal.x, faceNormal.y, faceNormal.z, faceNormal.x, faceNormal.y, faceNormal.z, faceNormal.x, faceNormal.y, faceNormal.z);
|
|
||||||
};
|
|
||||||
|
|
||||||
for (let r = 0; r < rings; r++) {
|
|
||||||
for (let s = 0; s < segments; s++) {
|
|
||||||
const angle1 = (s / segments) * Math.PI * 2;
|
|
||||||
const angle2 = ((s + 1) / segments) * Math.PI * 2;
|
|
||||||
const v1 = new THREE.Vector3(Math.cos(angle1) * (r * radius / rings), Math.sin(angle1) * (r * radius / rings), 0);
|
|
||||||
const v2 = new THREE.Vector3(Math.cos(angle2) * (r * radius / rings), Math.sin(angle2) * (r * radius / rings), 0);
|
|
||||||
const v3 = new THREE.Vector3(Math.cos(angle1) * ((r + 1) * radius / rings), Math.sin(angle1) * ((r + 1) * radius / rings), 0);
|
|
||||||
const v4 = new THREE.Vector3(Math.cos(angle2) * ((r + 1) * radius / rings), Math.sin(angle2) * ((r + 1) * radius / rings), 0);
|
|
||||||
addTriangle(v1, v2, v3, r === rings - 1);
|
|
||||||
addTriangle(v2, v4, v3, r === rings - 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const geometry = new THREE.BufferGeometry();
|
|
||||||
geometry.setAttribute('position', new THREE.Float32BufferAttribute(vertices, 3));
|
|
||||||
geometry.setAttribute('color', new THREE.Float32BufferAttribute(colors, 3));
|
|
||||||
geometry.setAttribute('normal', new THREE.Float32BufferAttribute(normals, 3));
|
|
||||||
return geometry;
|
|
||||||
};
|
|
||||||
|
|
||||||
// --- Create and Place Windows ---
|
// --- Create and Place Windows ---
|
||||||
const createAndPlaceWindow = (position, rotationY) => {
|
const createAndPlaceWindow = (position, rotationY) => {
|
||||||
// Pick a main color for this entire window
|
const geometry = createProceduralWindowGeometry(); // Generate unique geometry for each window
|
||||||
const mainColor = colorPalette[Math.floor(Math.random() * colorPalette.length)];
|
|
||||||
const geometry = createProceduralWindowGeometry(mainColor); // Generate unique geometry for each window
|
|
||||||
const windowMesh = new THREE.Mesh(geometry, material);
|
const windowMesh = new THREE.Mesh(geometry, material);
|
||||||
windowMesh.position.copy(position);
|
windowMesh.position.copy(position);
|
||||||
windowMesh.rotation.y = rotationY;
|
windowMesh.rotation.y = rotationY;
|
||||||
@ -210,34 +127,13 @@ export class StainedGlass extends SceneFeature {
|
|||||||
// Right side
|
// Right side
|
||||||
createAndPlaceWindow(new THREE.Vector3(totalWidth / 2 - 0.01, y, z), -Math.PI / 2);
|
createAndPlaceWindow(new THREE.Vector3(totalWidth / 2 - 0.01, y, z), -Math.PI / 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Add Windows to the Nave/Clerestory Walls ---
|
|
||||||
for (let i = 0; i < numWindowsPerSide * 2; i++) {
|
|
||||||
const z = -length / 2 + windowSpacing / 2 * (i + 0.5);
|
|
||||||
const y = aisleHeight + (naveHeight - aisleHeight - (windowBaseHeight + windowArchHeight)) / 2; // Center them vertically in the clerestory
|
|
||||||
|
|
||||||
// Left side of Nave
|
|
||||||
createAndPlaceWindow(new THREE.Vector3(-naveWidth / 2 + 0.01, y, z), Math.PI / 2);
|
|
||||||
// Right side of Nave
|
|
||||||
createAndPlaceWindow(new THREE.Vector3(naveWidth / 2 - 0.01, y, z), -Math.PI / 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Add Huge Rose Window behind the stage ---
|
|
||||||
const roseWindowRadius = naveWidth / 2 - 1; // Almost as wide as the nave
|
|
||||||
const roseWindowMainColor = colorPalette[Math.floor(Math.random() * colorPalette.length)];
|
|
||||||
const roseWindowGeo = createProceduralRoseWindowGeometry(roseWindowMainColor, roseWindowRadius);
|
|
||||||
const roseWindowMesh = new THREE.Mesh(roseWindowGeo, material);
|
|
||||||
roseWindowMesh.position.set(0, naveHeight - 2, -length / 2 + 0.05);
|
|
||||||
state.scene.add(roseWindowMesh);
|
|
||||||
this.windows.push(roseWindowMesh);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
update(deltaTime) {
|
update(deltaTime) {
|
||||||
// Add a subtle pulsing glow to the windows
|
// Add a subtle pulsing glow to the windows
|
||||||
const pulseSpeed = 0.5;
|
const pulseSpeed = 0.5;
|
||||||
const minIntensity = 0.1; // Increased intensity for a stronger glow
|
const minIntensity = 0.5;
|
||||||
const maxIntensity = 0.2;
|
const maxIntensity = 0.9;
|
||||||
const intensity = minIntensity + (maxIntensity - minIntensity) * (0.5 * (1 + Math.sin(state.clock.getElapsedTime() * pulseSpeed)));
|
const intensity = minIntensity + (maxIntensity - minIntensity) * (0.5 * (1 + Math.sin(state.clock.getElapsedTime() * pulseSpeed)));
|
||||||
|
|
||||||
// To make the glow match the vertex colors, we set the emissive color to white
|
// To make the glow match the vertex colors, we set the emissive color to white
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 972 KiB |
|
Before Width: | Height: | Size: 951 KiB |
|
Before Width: | Height: | Size: 981 KiB |
|
Before Width: | Height: | Size: 1.2 MiB |
|
Before Width: | Height: | Size: 1004 KiB |
|
Before Width: | Height: | Size: 941 KiB |
|
Before Width: | Height: | Size: 1.0 MiB |
|
Before Width: | Height: | Size: 950 B |
|
Before Width: | Height: | Size: 2.0 MiB After Width: | Height: | Size: 2.3 MiB |
|
Before Width: | Height: | Size: 2.4 MiB After Width: | Height: | Size: 2.3 MiB |