diff --git a/app.js b/app.js index b510289..bcdae7d 100644 --- a/app.js +++ b/app.js @@ -101,14 +101,27 @@ const forwardVector = new THREE.Vector3(); const ambientLight = new THREE.AmbientLight(0x0a1428, 1.4); scene.add(ambientLight); -const overheadLight = new THREE.PointLight(0x8899bb, 0.6, 60); +// SpotLight replaces PointLight so shadows can be cast with a single depth map +// (PointLights require 6 cube-face renders; SpotLights need only 1) +const overheadLight = new THREE.SpotLight(0x8899bb, 0.6, 80, Math.PI / 3.5, 0.5, 1.0); overheadLight.position.set(0, 25, 0); +overheadLight.target.position.set(0, 0, 0); +overheadLight.castShadow = true; +overheadLight.shadow.mapSize.set(2048, 2048); +overheadLight.shadow.camera.near = 5; +overheadLight.shadow.camera.far = 60; +overheadLight.shadow.bias = -0.001; scene.add(overheadLight); +scene.add(overheadLight.target); const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true }); renderer.setClearColor(0x000000, 0); renderer.setPixelRatio(window.devicePixelRatio); renderer.setSize(window.innerWidth, window.innerHeight); +// === SHADOW SYSTEM === +// PCFSoftShadowMap provides smooth penumbra edges matching the holographic aesthetic. +renderer.shadowMap.enabled = true; +renderer.shadowMap.type = THREE.PCFSoftShadowMap; document.body.appendChild(renderer.domElement); // === STAR FIELD === @@ -216,12 +229,16 @@ const platformFrameMat = new THREE.MeshStandardMaterial({ const platformRimGeo = new THREE.RingGeometry(4.7, 5.3, 64); const platformRim = new THREE.Mesh(platformRimGeo, platformFrameMat); platformRim.rotation.x = -Math.PI / 2; +platformRim.castShadow = true; +platformRim.receiveShadow = true; glassPlatformGroup.add(platformRim); // Raised border torus for visible 3-D thickness const borderTorusGeo = new THREE.TorusGeometry(5.0, 0.1, 6, 64); const borderTorus = new THREE.Mesh(borderTorusGeo, platformFrameMat); borderTorus.rotation.x = Math.PI / 2; +borderTorus.castShadow = true; +borderTorus.receiveShadow = true; glassPlatformGroup.add(borderTorus); // Glass tile material — highly transmissive to reveal the void below @@ -388,12 +405,15 @@ const perlin = createPerlinNoise(); metalness: 0.04, }); const topMesh = new THREE.Mesh(geo, topMat); + topMesh.castShadow = true; + topMesh.receiveShadow = true; // Underside — tapered cylinder giving the island its rocky underbelly const bottomGeo = new THREE.CylinderGeometry(ISLAND_RADIUS * 0.82, ISLAND_RADIUS * 0.35, 2.0, 64, 1); const bottomMat = new THREE.MeshStandardMaterial({ color: 0x0c0a08, roughness: 0.92, metalness: 0.03 }); const bottomMesh = new THREE.Mesh(bottomGeo, bottomMat); bottomMesh.position.y = -1.0; + bottomMesh.castShadow = true; const islandGroup = new THREE.Group(); islandGroup.add(topMesh); @@ -2730,7 +2750,13 @@ const tomeSpine = new THREE.Mesh(new THREE.BoxGeometry(0.06, 0.12, 1.4), tomeSpi tomeSpine.position.set(-0.52, 0, 0); tomeGroup.add(tomeSpine); -tomeGroup.traverse(o => { if (o.isMesh) o.userData.zoomLabel = 'The Oath'; }); +tomeGroup.traverse(o => { + if (o.isMesh) { + o.userData.zoomLabel = 'The Oath'; + o.castShadow = true; + o.receiveShadow = true; + } +}); scene.add(tomeGroup); // Gentle glow beneath the tome @@ -2742,6 +2768,11 @@ scene.add(tomeGlow); const oathSpot = new THREE.SpotLight(0xffd700, 0, 40, Math.PI / 7, 0.4, 1.2); oathSpot.position.set(0, 22, 0); oathSpot.target.position.set(0, 0, 0); +oathSpot.castShadow = true; +oathSpot.shadow.mapSize.set(1024, 1024); +oathSpot.shadow.camera.near = 1; +oathSpot.shadow.camera.far = 50; +oathSpot.shadow.bias = -0.002; scene.add(oathSpot); scene.add(oathSpot.target);