Feature: glass windows
This commit is contained in:
parent
bc961119b6
commit
3b97978a50
@ -7,32 +7,32 @@ function updateCamera() {
|
||||
const globalTime = Date.now() * 0.0001;
|
||||
const lookAtTime = Date.now() * 0.0002;
|
||||
|
||||
const camAmplitude = 4.0;
|
||||
const lookAmplitude = 15.4;
|
||||
const camAmplitude = 1.0;
|
||||
const lookAmplitude = 8.0;
|
||||
|
||||
// Base Camera Position in front of the TV
|
||||
const baseX = 0;
|
||||
const baseY = 1.6;
|
||||
const baseZ = 0.0;
|
||||
const baseZ = 10.0;
|
||||
|
||||
// Base LookAt target (Center of the screen)
|
||||
const baseTargetX = 0;
|
||||
const baseTargetY = 1.6;
|
||||
const baseTargetZ = 5.0;
|
||||
const baseTargetZ = -10.0;
|
||||
|
||||
// Camera Position Offsets (Drift)
|
||||
const camOffsetX = Math.sin(globalTime * 3.1) * camAmplitude;
|
||||
const camOffsetY = Math.cos(globalTime * 2.5) * camAmplitude * 0.1;
|
||||
const camOffsetZ = Math.cos(globalTime * 3.2) * camAmplitude * 1.2;
|
||||
const camOffsetZ = Math.cos(globalTime * 3.2) * camAmplitude;
|
||||
|
||||
state.camera.position.x = baseX + camOffsetX;
|
||||
state.camera.position.y = baseY + camOffsetY;
|
||||
state.camera.position.z = baseZ + camOffsetZ;
|
||||
|
||||
// LookAt Target Offsets (Subtle Gaze Shift)
|
||||
const lookOffsetX = Math.sin(lookAtTime * 1.5) * lookAmplitude * 4;
|
||||
const lookOffsetZ = Math.cos(lookAtTime * 2.5) * lookAmplitude * 4;
|
||||
const lookOffsetY = Math.cos(lookAtTime * 1.2) * lookAmplitude;
|
||||
const lookOffsetX = Math.sin(lookAtTime * 1.5) * lookAmplitude;
|
||||
const lookOffsetZ = Math.cos(lookAtTime * 2.5) * lookAmplitude;
|
||||
const lookOffsetY = Math.cos(lookAtTime * 1.2) * lookAmplitude * 0.5;
|
||||
|
||||
// Apply lookAt to the subtly shifted target
|
||||
state.camera.lookAt(baseTargetX + lookOffsetX, baseTargetY + lookOffsetY, baseTargetZ + lookOffsetZ);
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import * as THREE from 'three';
|
||||
import { init } from './core/init.js';
|
||||
import { StainedGlass } from './scene/stained-glass-window.js';
|
||||
|
||||
// Start everything
|
||||
init();
|
||||
61
party-cathedral/src/scene/light-ball.js
Normal file
61
party-cathedral/src/scene/light-ball.js
Normal file
@ -0,0 +1,61 @@
|
||||
import * as THREE from 'three';
|
||||
import { state } from '../state.js';
|
||||
import { SceneFeature } from './SceneFeature.js';
|
||||
import sceneFeatureManager from './SceneFeatureManager.js';
|
||||
|
||||
export class LightBall extends SceneFeature {
|
||||
constructor() {
|
||||
super();
|
||||
this.ball = null;
|
||||
this.light = null;
|
||||
sceneFeatureManager.register(this);
|
||||
}
|
||||
|
||||
init() {
|
||||
// --- Dimensions from room-walls.js for positioning ---
|
||||
const naveWidth = 12;
|
||||
const naveHeight = 15;
|
||||
const length = 40;
|
||||
|
||||
// --- Ball Properties ---
|
||||
const ballRadius = 1.0;
|
||||
const ballColor = 0xffffff; // White light
|
||||
const lightIntensity = 6.0;
|
||||
|
||||
// --- Create the Ball ---
|
||||
const ballGeometry = new THREE.SphereGeometry(ballRadius, 32, 32);
|
||||
const ballMaterial = new THREE.MeshBasicMaterial({ color: ballColor, emissive: ballColor, emissiveIntensity: 1.0 });
|
||||
this.ball = new THREE.Mesh(ballGeometry, ballMaterial);
|
||||
this.ball.castShadow = false;
|
||||
this.ball.receiveShadow = false;
|
||||
|
||||
// --- Create the Light ---
|
||||
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 ---
|
||||
this.ball.position.set(0, naveHeight * 0.7, 0); // Near the ceiling
|
||||
this.light.position.copy(this.ball.position);
|
||||
|
||||
state.scene.add(this.ball);
|
||||
state.scene.add(this.light);
|
||||
}
|
||||
|
||||
update(deltaTime) {
|
||||
// --- Animate the Ball ---
|
||||
const time = state.clock.getElapsedTime();
|
||||
const driftSpeed = 0.5;
|
||||
const driftAmplitude = 10.0;
|
||||
|
||||
this.ball.position.x = Math.sin(time * driftSpeed) * driftAmplitude;
|
||||
this.ball.position.y = 10 + Math.cos(time * driftSpeed * 1.3) * driftAmplitude * 0.5; // bobbing
|
||||
this.ball.position.z = Math.cos(time * driftSpeed * 0.7) * driftAmplitude;
|
||||
this.light.position.copy(this.ball.position);
|
||||
}
|
||||
}
|
||||
|
||||
new LightBall();
|
||||
@ -30,10 +30,12 @@ export class RoomWalls extends SceneFeature {
|
||||
wallTexture.wrapS = THREE.RepeatWrapping;
|
||||
wallTexture.wrapT = THREE.RepeatWrapping;
|
||||
|
||||
const wallMaterial = new THREE.MeshPhongMaterial({
|
||||
const wallMaterial = new THREE.MeshStandardMaterial({
|
||||
map: wallTexture,
|
||||
side: THREE.DoubleSide,
|
||||
shininess: 5,
|
||||
roughness: 0.2,
|
||||
metalness: 0.1,
|
||||
specular: 0x111111
|
||||
});
|
||||
|
||||
|
||||
@ -2,7 +2,10 @@ import * as THREE from 'three';
|
||||
import { state } from '../state.js';
|
||||
import floorTextureUrl from '/textures/stone_floor.png';
|
||||
import sceneFeatureManager from './SceneFeatureManager.js';
|
||||
// Scene Features registered here:
|
||||
import { RoomWalls } from './room-walls.js';
|
||||
import { LightBall } from './light-ball.js';
|
||||
// Scene Features ^^^
|
||||
|
||||
// --- Scene Modeling Function ---
|
||||
export function createSceneObjects() {
|
||||
@ -26,11 +29,11 @@ export function createSceneObjects() {
|
||||
state.scene.add(floor);
|
||||
|
||||
// 3. Lighting (Minimal and focused)
|
||||
const ambientLight = new THREE.AmbientLight(0x606060, 1.5); // Increased ambient light for a larger space
|
||||
const ambientLight = new THREE.AmbientLight(0x606060, 0.1); // Increased ambient light for a larger space
|
||||
state.scene.add(ambientLight);
|
||||
|
||||
// Add a HemisphereLight for more natural, general illumination in a large space.
|
||||
const hemisphereLight = new THREE.HemisphereLight(0xffffff, 0x444444, 0.7);
|
||||
const hemisphereLight = new THREE.HemisphereLight(0xffffff, 0x444444, 0.2);
|
||||
|
||||
state.scene.add(hemisphereLight);
|
||||
}
|
||||
|
||||
@ -1 +1,147 @@
|
||||
// This file will contain the Three.js code for creating colorful stained glass windows with light effects.
|
||||
import * as THREE from 'three';
|
||||
import { state } from '../state.js';
|
||||
import { SceneFeature } from './SceneFeature.js';
|
||||
import sceneFeatureManager from './SceneFeatureManager.js';
|
||||
|
||||
export class StainedGlass extends SceneFeature {
|
||||
constructor() {
|
||||
super();
|
||||
this.windows = [];
|
||||
sceneFeatureManager.register(this);
|
||||
}
|
||||
|
||||
init() {
|
||||
// --- Dimensions from room-walls.js for positioning ---
|
||||
const length = 40;
|
||||
const naveWidth = 12;
|
||||
const aisleWidth = 6;
|
||||
const totalWidth = naveWidth + 2 * aisleWidth;
|
||||
const aisleHeight = 8;
|
||||
|
||||
// --- Window Properties ---
|
||||
const windowWidth = 3;
|
||||
const windowBaseHeight = 5;
|
||||
const windowArchHeight = 1.5;
|
||||
const numWindowsPerSide = 4;
|
||||
const windowSpacing = length / numWindowsPerSide;
|
||||
|
||||
// --- Procedural Material ---
|
||||
const material = new THREE.MeshStandardMaterial({
|
||||
vertexColors: true, // Use colors assigned to vertices
|
||||
side: THREE.DoubleSide,
|
||||
metalness: 0.1, // Glass is not very metallic
|
||||
roughness: 0.3, // Glass is smooth
|
||||
clearcoat: 1.0,
|
||||
emissive: 0x000000, // We will control emissiveness via update
|
||||
});
|
||||
|
||||
// --- Procedural Geometry Generation ---
|
||||
const createProceduralWindowGeometry = () => {
|
||||
const segmentsX = 8;
|
||||
const segmentsY = 12;
|
||||
const vertices = [];
|
||||
const colors = [];
|
||||
const normals = [];
|
||||
|
||||
const colorPalette = [
|
||||
new THREE.Color(0x6A0DAD), // Purple
|
||||
new THREE.Color(0x00008B), // Dark Blue
|
||||
new THREE.Color(0xB22222), // Firebrick Red
|
||||
new THREE.Color(0xFFD700), // Gold
|
||||
new THREE.Color(0x006400), // Dark Green
|
||||
new THREE.Color(0x8B0000), // Dark Red
|
||||
new THREE.Color(0x4B0082), // Indigo
|
||||
];
|
||||
|
||||
const randomnessFactor = 0.4; // How much to vary the normals
|
||||
|
||||
const addTriangle = (v1, v2, v3) => {
|
||||
const color = 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(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
|
||||
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();
|
||||
|
||||
// Introduce a random vector to alter the normal
|
||||
const randomVec = new THREE.Vector3(Math.random() - 0.5, Math.random() - 0.5, Math.random() - 0.5).normalize();
|
||||
faceNormal.add(randomVec.multiplyScalar(randomnessFactor)).normalize();
|
||||
|
||||
// Apply the same randomized normal to all 3 vertices for a faceted look
|
||||
normals.push(faceNormal.x, faceNormal.y, faceNormal.z, faceNormal.x, faceNormal.y, faceNormal.z, faceNormal.x, faceNormal.y, faceNormal.z);
|
||||
};
|
||||
|
||||
// Create rectangular part
|
||||
for (let i = 0; i < segmentsX; i++) {
|
||||
for (let j = 0; j < segmentsY; j++) {
|
||||
const x = -windowWidth / 2 + (i * windowWidth) / segmentsX;
|
||||
const y = (j * windowBaseHeight) / segmentsY;
|
||||
const x2 = x + windowWidth / segmentsX;
|
||||
const y2 = y + windowBaseHeight / segmentsY;
|
||||
|
||||
const v1 = new THREE.Vector3(x, y, 0);
|
||||
const v2 = new THREE.Vector3(x2, y, 0);
|
||||
const v3 = new THREE.Vector3(x, y2, 0);
|
||||
const v4 = new THREE.Vector3(x2, y2, 0);
|
||||
addTriangle(v1, v2, v3);
|
||||
addTriangle(v2, v4, v3);
|
||||
}
|
||||
}
|
||||
|
||||
// Create arch part
|
||||
const archCenter = new THREE.Vector3(0, windowBaseHeight, 0);
|
||||
for (let i = 0; i < segmentsX * 2; i++) {
|
||||
const angle1 = (i / (segmentsX * 2)) * Math.PI;
|
||||
const angle2 = ((i + 1) / (segmentsX * 2)) * Math.PI;
|
||||
const v1 = archCenter;
|
||||
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);
|
||||
addTriangle(v1, v2, v3);
|
||||
}
|
||||
|
||||
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 ---
|
||||
const createAndPlaceWindow = (position, rotationY) => {
|
||||
const geometry = createProceduralWindowGeometry(); // Generate unique geometry for each window
|
||||
const windowMesh = new THREE.Mesh(geometry, material);
|
||||
windowMesh.position.copy(position);
|
||||
windowMesh.rotation.y = rotationY;
|
||||
state.scene.add(windowMesh);
|
||||
this.windows.push(windowMesh);
|
||||
};
|
||||
|
||||
for (let i = 0; i < numWindowsPerSide; i++) {
|
||||
const z = -length / 2 + windowSpacing * (i + 0.5);
|
||||
const y = 0; // Place them starting from the floor
|
||||
|
||||
// Left side
|
||||
createAndPlaceWindow(new THREE.Vector3(-totalWidth / 2 + 0.01, y, z), Math.PI / 2);
|
||||
// Right side
|
||||
createAndPlaceWindow(new THREE.Vector3(totalWidth / 2 - 0.01, y, z), -Math.PI / 2);
|
||||
}
|
||||
}
|
||||
|
||||
update(deltaTime) {
|
||||
// Add a subtle pulsing glow to the windows
|
||||
const pulseSpeed = 0.5;
|
||||
const minIntensity = 0.5;
|
||||
const maxIntensity = 0.9;
|
||||
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
|
||||
// and modulate its intensity. The final glow color will be vertexColor * emissive * emissiveIntensity.
|
||||
this.windows.forEach(w => {
|
||||
w.material.emissiveIntensity = intensity;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
new StainedGlass();
|
||||
Loading…
Reference in New Issue
Block a user