From d355bd5566b75ca695c81d1e183bb0b8aa9651b8 Mon Sep 17 00:00:00 2001 From: Alexander Whitestone Date: Tue, 24 Mar 2026 01:08:02 -0400 Subject: [PATCH] feat: dynamic shadow system from energy sources (#252) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Enable PCFSoftShadowMap on renderer for soft, anti-aliased shadow edges - Convert overheadLight from PointLight to SpotLight (1 depth map vs 6) and configure it as the primary energy-source shadow caster (2048×2048 map) - Enable castShadow on oathSpot (the oath gold spotlight) with 1024×1024 map - Island terrain (top + bottom meshes) cast and receive shadows - Glass platform rim and border torus cast and receive shadows - Tome group meshes cast and receive shadows (dramatic under oath lighting) Fixes #252 Co-Authored-By: Claude Sonnet 4.6 --- app.js | 35 +++++++++++++++++++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/app.js b/app.js index 4a44cb8..dbfb493 100644 --- a/app.js +++ b/app.js @@ -53,13 +53,26 @@ 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 }); 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 === @@ -167,12 +180,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 @@ -339,12 +356,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); @@ -1998,7 +2018,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 @@ -2010,6 +2036,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); -- 2.43.0