Compare commits
9 Commits
4726b419f4
...
cdd90a4c57
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cdd90a4c57 | ||
|
|
c944fb0f4d | ||
|
|
d9dd5a56d1 | ||
|
|
1d1c8da558 | ||
|
|
ea813c88be | ||
|
|
2811fdbbd8 | ||
|
|
a4e48d1d5a | ||
|
|
56ec41a802 | ||
|
|
d36313df37 |
@ -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 = 1.0;
|
const camAmplitude = new THREE.Vector3(1.0, 0.1, 10.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 = 1.6;
|
const baseY = 2.6;
|
||||||
const baseZ = -10.0;
|
const baseZ = 0.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;
|
const camOffsetX = Math.sin(globalTime * 3.1) * camAmplitude.x;
|
||||||
const camOffsetY = Math.cos(globalTime * 2.5) * camAmplitude * 0.1;
|
const camOffsetY = Math.cos(globalTime * 2.5) * camAmplitude.y;
|
||||||
const camOffsetZ = Math.cos(globalTime * 3.2) * camAmplitude;
|
const camOffsetZ = Math.cos(globalTime * 3.2) * camAmplitude.z;
|
||||||
|
|
||||||
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,58 +3,65 @@ 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.ball = null;
|
this.lightBalls = [];
|
||||||
this.light = null;
|
|
||||||
sceneFeatureManager.register(this);
|
sceneFeatureManager.register(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
// --- Dimensions from room-walls.js for positioning ---
|
|
||||||
const naveWidth = 12;
|
|
||||||
const naveHeight = 15;
|
|
||||||
const length = 40;
|
|
||||||
|
|
||||||
// --- Ball Properties ---
|
// --- Ball Properties ---
|
||||||
const ballRadius = 1.0;
|
const ballRadius = 0.4;
|
||||||
const ballColor = 0xffffff; // White light
|
const lightIntensity = 5.0;
|
||||||
const lightIntensity = 6.0;
|
const lightColors = [0xff0000, 0x00ff00, 0x0000ff, 0xffff00, 0x00ffff, 0xff00ff]; // Red, Green, Blue, Yellow
|
||||||
|
|
||||||
|
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: ballColor, emissive: ballColor, emissiveIntensity: 1.0 });
|
const ballMaterial = new THREE.MeshBasicMaterial({ color: color, emissive: color, emissiveIntensity: 1.0 });
|
||||||
this.ball = new THREE.Mesh(ballGeometry, ballMaterial);
|
const ball = new THREE.Mesh(ballGeometry, ballMaterial);
|
||||||
this.ball.castShadow = false;
|
ball.castShadow = false;
|
||||||
this.ball.receiveShadow = false;
|
ball.receiveShadow = false;
|
||||||
|
|
||||||
// --- Create the Light ---
|
// --- Create the Light ---
|
||||||
this.light = new THREE.PointLight(ballColor, lightIntensity, length / 2); // Adjust range to cathedral size
|
const light = new THREE.PointLight(color, lightIntensity, length / 1.5);
|
||||||
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 ---
|
||||||
this.ball.position.set(0, naveHeight * 0.7, 0); // Near the ceiling
|
ball.position.set(
|
||||||
this.light.position.copy(this.ball.position);
|
(Math.random() - 0.5) * naveWidth,
|
||||||
|
naveHeight * 0.6 + Math.random() * 4,
|
||||||
|
(Math.random() - 0.5) * length * 0.8
|
||||||
|
);
|
||||||
|
light.position.copy(ball.position);
|
||||||
|
|
||||||
state.scene.add(this.ball);
|
state.scene.add(ball);
|
||||||
state.scene.add(this.light);
|
state.scene.add(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();
|
||||||
const driftSpeed = 0.5;
|
this.lightBalls.forEach(lb => {
|
||||||
const driftAmplitude = 10.0;
|
const { mesh, light, driftSpeed, offset } = lb;
|
||||||
|
mesh.position.x = Math.sin(time * driftSpeed + offset) * naveWidth/2 * 0.8;
|
||||||
this.ball.position.x = Math.sin(time * driftSpeed) * driftAmplitude;
|
mesh.position.y = 10 + Math.cos(time * driftSpeed * 1.3 + offset) * naveHeight/2 * 0.6;
|
||||||
this.ball.position.y = 10 + Math.cos(time * driftSpeed * 1.3) * driftAmplitude * 0.5; // bobbing
|
mesh.position.z = Math.cos(time * driftSpeed * 0.7 + offset) * length/2 * 0.8;
|
||||||
this.ball.position.z = Math.cos(time * driftSpeed * 0.7) * driftAmplitude;
|
light.position.copy(mesh.position);
|
||||||
this.light.position.copy(this.ball.position);
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -2,7 +2,12 @@ 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';
|
||||||
import musiciansTextureUrl from '/textures/musician1.png';
|
const musicianTextureUrls = [
|
||||||
|
'/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;
|
||||||
@ -20,9 +25,8 @@ export class MedievalMusicians extends SceneFeature {
|
|||||||
sceneFeatureManager.register(this);
|
sceneFeatureManager.register(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
init() {
|
async init() {
|
||||||
// Load the texture and create the material inside the callback
|
const processTexture = (texture) => {
|
||||||
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');
|
||||||
@ -53,18 +57,23 @@ export class MedievalMusicians extends SceneFeature {
|
|||||||
}
|
}
|
||||||
context.putImageData(imageData, 0, 0);
|
context.putImageData(imageData, 0, 0);
|
||||||
|
|
||||||
// 4. Create a new texture from the modified canvas
|
return new THREE.CanvasTexture(canvas);
|
||||||
const processedTexture = new THREE.CanvasTexture(canvas);
|
};
|
||||||
|
|
||||||
// 5. Create a standard material with the new texture
|
// Load and process all textures, creating a material for each
|
||||||
const material = new THREE.MeshStandardMaterial({
|
const materials = await Promise.all(musicianTextureUrls.map(async (url) => {
|
||||||
|
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);
|
||||||
|
|
||||||
@ -72,9 +81,12 @@ 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 => {
|
musicianPositions.forEach((pos, index) => {
|
||||||
|
// 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);
|
||||||
@ -97,7 +109,9 @@ export class MedievalMusicians extends SceneFeature {
|
|||||||
jumpStartTime: 0,
|
jumpStartTime: 0,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
};
|
||||||
|
|
||||||
|
createMusicians();
|
||||||
}
|
}
|
||||||
|
|
||||||
update(deltaTime) {
|
update(deltaTime) {
|
||||||
@ -114,7 +128,9 @@ 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 = 2.0;
|
const jumpHeight = 1.0;
|
||||||
|
const jumpVariance = 1.0;
|
||||||
|
const jumpPlaneVariance = 2.0;
|
||||||
|
|
||||||
this.musicians.forEach(musicianObj => {
|
this.musicians.forEach(musicianObj => {
|
||||||
const { mesh } = musicianObj;
|
const { mesh } = musicianObj;
|
||||||
@ -131,7 +147,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, mesh.position.y, planeEdgeZ);
|
musicianObj.targetPosition = new THREE.Vector3(targetX, area.y + musicianHeight/2, 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(
|
||||||
@ -161,6 +177,7 @@ 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;
|
||||||
@ -178,7 +195,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) * jumpHeight;
|
const arcHeight = Math.sin(musicianObj.jumpProgress * Math.PI) * musicianObj.jumpHeight;
|
||||||
|
|
||||||
// Interpolate horizontal position
|
// Interpolate horizontal position
|
||||||
const horizontalProgress = musicianObj.jumpProgress;
|
const horizontalProgress = musicianObj.jumpProgress;
|
||||||
@ -201,7 +218,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) * jumpHeight;
|
mesh.position.y = baseHeight + Math.sin(jumpProgress * Math.PI) * musicianObj.jumpHeight;
|
||||||
} else {
|
} else {
|
||||||
musicianObj.isJumping = false;
|
musicianObj.isJumping = false;
|
||||||
mesh.position.y = area.y + musicianHeight / 2;
|
mesh.position.y = area.y + musicianHeight / 2;
|
||||||
@ -209,6 +226,7 @@ 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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
152
party-cathedral/src/scene/party-guests.js
Normal file
@ -0,0 +1,152 @@
|
|||||||
|
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.BoxGeometry(pillarSize, pillarHeight, pillarSize);
|
const pillarGeo = new THREE.CylinderGeometry(pillarSize / 2, pillarSize / 2, pillarHeight, 24);
|
||||||
|
|
||||||
// --- 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,6 +8,8 @@ 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 ---
|
||||||
|
|||||||
117
party-cathedral/src/scene/stage-torches.js
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
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,6 +17,7 @@ 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;
|
||||||
@ -31,18 +32,11 @@ 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
|
||||||
clearcoat: 1.0,
|
emissive: 0x222222, // Set to white to use vertex colors for glow
|
||||||
emissive: 0x000000, // We will control emissiveness via update
|
emissiveIntensity: 0.1, // Start with a base intensity
|
||||||
|
//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
|
||||||
@ -53,12 +47,42 @@ 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) => {
|
const addTriangle = (v1, v2, v3, isBorder = false) => {
|
||||||
const color = colorPalette[Math.floor(Math.random() * colorPalette.length)];
|
let segmentColor;
|
||||||
|
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(color.r, color.g, color.b, color.r, color.g, color.b, color.r, color.g, color.b);
|
colors.push(segmentColor.r, segmentColor.g, segmentColor.b, segmentColor.r, segmentColor.g, segmentColor.b, segmentColor.r, segmentColor.g, segmentColor.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);
|
||||||
@ -85,8 +109,9 @@ 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);
|
||||||
addTriangle(v1, v2, v3);
|
const isBorder = i === 0 || i === segmentsX - 1 || j === 0;
|
||||||
addTriangle(v2, v4, v3);
|
addTriangle(v1, v2, v3, isBorder);
|
||||||
|
addTriangle(v2, v4, v3, isBorder);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -98,7 +123,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);
|
addTriangle(v1, v2, v3, true); // Treat all arch segments as part of the border
|
||||||
}
|
}
|
||||||
|
|
||||||
const geometry = new THREE.BufferGeometry();
|
const geometry = new THREE.BufferGeometry();
|
||||||
@ -108,9 +133,67 @@ 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) => {
|
||||||
const geometry = createProceduralWindowGeometry(); // Generate unique geometry for each window
|
// Pick a main color for this entire 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;
|
||||||
@ -127,13 +210,34 @@ 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.5;
|
const minIntensity = 0.1; // Increased intensity for a stronger glow
|
||||||
const maxIntensity = 0.9;
|
const maxIntensity = 0.2;
|
||||||
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
|
||||||
|
|||||||
BIN
party-cathedral/textures/guest1.png
Normal file
|
After Width: | Height: | Size: 972 KiB |
BIN
party-cathedral/textures/guest2.png
Normal file
|
After Width: | Height: | Size: 951 KiB |
BIN
party-cathedral/textures/guest3.png
Normal file
|
After Width: | Height: | Size: 981 KiB |
BIN
party-cathedral/textures/guest4.png
Normal file
|
After Width: | Height: | Size: 1.2 MiB |
BIN
party-cathedral/textures/musician2.png
Normal file
|
After Width: | Height: | Size: 1004 KiB |
BIN
party-cathedral/textures/musician3.png
Normal file
|
After Width: | Height: | Size: 941 KiB |
BIN
party-cathedral/textures/musician4.png
Normal file
|
After Width: | Height: | Size: 1.0 MiB |
BIN
party-cathedral/textures/spark.png
Normal file
|
After Width: | Height: | Size: 950 B |
|
Before Width: | Height: | Size: 2.3 MiB After Width: | Height: | Size: 2.0 MiB |
|
Before Width: | Height: | Size: 2.3 MiB After Width: | Height: | Size: 2.4 MiB |