|
30 | 30 | let composer = null; // postprocesado (bloom + gamma); null => render directo |
31 | 31 | let bloomPass = null; // pase de bloom para el pulso dinámico por cordura baja |
32 | 32 | let fogBase = 0.08; |
| 33 | + let ambBase = 0.35; |
| 34 | + let dlightBase = 0.35; |
| 35 | + let exposureBase = 1.1; |
33 | 36 | let nivelClaro = 0; // [0,1] cuánto se pasa de CLARA la paleta del nivel (v30.2) |
34 | 37 | let glCanvas, overlay, octx, W, H; |
35 | 38 | let levelKey = null; |
|
1237 | 1240 | const cSuelo = new THREE.Color(pal.suelo); |
1238 | 1241 | const lumSuelo = 0.2126 * cSuelo.r + 0.7152 * cSuelo.g + 0.0722 * cSuelo.b; |
1239 | 1242 | nivelClaro = Math.max(0, Math.min(1, (lumSuelo - 0.55) / 0.35)); |
1240 | | - amb.intensity = esLevel0 ? 0.22 |
| 1243 | + ambBase = esLevel0 ? 0.22 |
1241 | 1244 | : Math.max(0.12, 0.55 - world.level.oscuridad * 0.4) * (1 - 0.35 * nivelClaro); |
1242 | | - dlight.intensity = esLevel0 ? 0.14 : 0.35 * (1 - 0.3 * nivelClaro); |
1243 | | - renderer.toneMappingExposure = esLevel0 ? 0.96 : 1.15 - 0.25 * nivelClaro; |
| 1245 | + dlightBase = esLevel0 ? 0.14 : 0.35 * (1 - 0.3 * nivelClaro); |
| 1246 | + exposureBase = esLevel0 ? 0.96 : 1.15 - 0.25 * nivelClaro; |
| 1247 | + amb.intensity = ambBase; |
| 1248 | + dlight.intensity = dlightBase; |
| 1249 | + renderer.toneMappingExposure = exposureBase; |
1244 | 1250 | plight.color = new THREE.Color(pal.luz); |
1245 | 1251 | plight.distance = (world.visionActual() + 3) * 1.6; |
1246 | 1252 | plight.castShadow = !esLevel0; |
|
1501 | 1507 |
|
1502 | 1508 | const p = world.player; |
1503 | 1509 | const px = p.rx + 0.5, pz = p.ry + 0.5; |
| 1510 | + const apagon = world.apagonIntensidad ? world.apagonIntensidad(t) : 0; |
1504 | 1511 | if (!playerSprite) { |
1505 | 1512 | playerSprite = new THREE.Sprite(new THREE.SpriteMaterial({ transparent: true })); |
1506 | 1513 | playerSprite.scale.set(1, SPRITE_H, 1); |
|
1683 | 1690 | const locuraFondo = new THREE.Color(0x13010b); |
1684 | 1691 | baseFondo.lerp(locuraFondo, sc * 0.85); |
1685 | 1692 | } |
| 1693 | + if (apagon > 0) baseFondo.multiplyScalar(1 - apagon * 0.78); |
1686 | 1694 | scene.background.copy(baseFondo); |
1687 | 1695 | scene.fog.color.copy(baseFondo); |
1688 | 1696 | } |
|
1699 | 1707 | targetBloom = 0.55 + sc * 0.65 + pulso; |
1700 | 1708 | targetThreshold = 0.82 - sc * 0.3; // con locura baja hasta 0.52: brilla, no ciega |
1701 | 1709 | } |
| 1710 | + targetBloom *= 1 - apagon * 0.72; |
1702 | 1711 | bloomPass.strength = targetBloom; |
1703 | 1712 | bloomPass.threshold = targetThreshold; |
1704 | 1713 | } |
|
1713 | 1722 | // en paletas casi blancas la luz del jugador quema un disco alrededor |
1714 | 1723 | // (albedo claro × 1.7 satura el ACES): se normaliza con nivelClaro |
1715 | 1724 | const luzJugador = world.level.id === 'level-0' ? 0.72 : 1.7 * (1 - 0.5 * nivelClaro); |
1716 | | - plight.intensity = plight.intensity * 0.85 + (luzJugador * flicker) * 0.15; |
| 1725 | + const luzApagon = p.luz ? 1 - apagon * 0.5 : 1 - apagon * 0.92; |
| 1726 | + plight.intensity = plight.intensity * 0.85 + (luzJugador * flicker * luzApagon) * 0.15; |
1717 | 1727 | plight.position.set(px, 1.6, pz); |
1718 | 1728 | plight.distance = (world.visionActual() + 3) * (p.luz ? 2.4 : 1.6); |
1719 | 1729 |
|
|
1746 | 1756 | const sc = (50 - cordura) / 50; |
1747 | 1757 | targetFogBase = fogBase * (1 + sc * 0.9); // hasta +90% de niebla (claustrofobia) |
1748 | 1758 | } |
| 1759 | + if (!luzOn) targetFogBase *= 1 + apagon * 0.38; |
1749 | 1760 | // espectador (v30): desde 14 de altura la niebla normal funde el suelo |
1750 | 1761 | // a negro — casi fuera, que el guardián vea el plano entero |
1751 | 1762 | if (world.espectador) targetFogBase = Math.min(fogBase, 0.015); |
|
1789 | 1800 | } |
1790 | 1801 | actualizarLucesTecho(world, px, pz, fase0); |
1791 | 1802 |
|
| 1803 | + // Apagón sincronizado de Level 1: caen todas las luminarias del mapa, |
| 1804 | + // mientras el foco de la linterna sigue siendo útil. |
| 1805 | + if (world.level.id === 'level-1') { |
| 1806 | + const corriente = Math.max(0.025, 1 - apagon); |
| 1807 | + const objetivoAmb = ambBase * (0.06 + corriente * 0.94); |
| 1808 | + amb.intensity += (objetivoAmb - amb.intensity) * 0.28; |
| 1809 | + const objetivoCenital = dlightBase * corriente; |
| 1810 | + dlight.intensity += (objetivoCenital - dlight.intensity) * 0.35; |
| 1811 | + for (const l of ceilingLights) l.intensity *= corriente; |
| 1812 | + panelMats.forEach((m, i) => { |
| 1813 | + if (!m) return; |
| 1814 | + const falla = i === flkGrupo && !flkOn ? 0.18 : 1; |
| 1815 | + const f = corriente * falla; |
| 1816 | + m.color.setRGB(f, 0.965 * f, 0.863 * f); |
| 1817 | + }); |
| 1818 | + const objetivoExposicion = exposureBase * (0.48 + corriente * 0.52); |
| 1819 | + renderer.toneMappingExposure += |
| 1820 | + (objetivoExposicion - renderer.toneMappingExposure) * 0.24; |
| 1821 | + } |
| 1822 | + |
1792 | 1823 | // ---------- modo espectador (v30): casa de muñecas ---------- |
1793 | 1824 | // el techo y sus fluorescentes taparían TODO desde una cámara cenital: |
1794 | 1825 | // sus materiales se apagan mientras dure (se re-aplica cada frame porque |
|
1977 | 2008 | } else { |
1978 | 2009 | renderer.render(scene, camera); |
1979 | 2010 | } |
1980 | | - drawOverlay(world, t); |
| 2011 | + drawOverlay(world, t, apagon); |
1981 | 2012 |
|
1982 | 2013 | if (window.DEBUG3D_ON) { |
1983 | 2014 | window.DEBUG3D = { |
|
1999 | 2030 | return [(v.x * 0.5 + 0.5) * W, (-v.y * 0.5 + 0.5) * H, v.z > 1]; |
2000 | 2031 | } |
2001 | 2032 |
|
2002 | | - function drawOverlay(world, t) { |
| 2033 | + function drawOverlay(world, t, apagon) { |
2003 | 2034 | octx.clearRect(0, 0, W, H); |
2004 | 2035 | if (!window.NOFX) Effects.draw(octx, 0, 0, t, 48, project); |
2005 | 2036 | // capa social del MMO: nombres flotantes y bocadillos de chat |
|
2028 | 2059 | octx.fillStyle = `rgba(235,110,30,${resp})`; |
2029 | 2060 | octx.fillRect(0, 0, W, H); |
2030 | 2061 | } |
| 2062 | + if (apagon > 0.001) { |
| 2063 | + const sombra = octx.createRadialGradient( |
| 2064 | + W / 2, H * 0.56, H * 0.08, |
| 2065 | + W / 2, H * 0.56, Math.max(W, H) * 0.72); |
| 2066 | + sombra.addColorStop(0, `rgba(0,2,8,${apagon * (world.player.luz ? 0.02 : 0.13)})`); |
| 2067 | + sombra.addColorStop(1, `rgba(0,1,6,${apagon * 0.42})`); |
| 2068 | + octx.fillStyle = sombra; |
| 2069 | + octx.fillRect(0, 0, W, H); |
| 2070 | + } |
2031 | 2071 | const fase0 = level0Phase(world); |
2032 | 2072 | if (fase0 > 0) { |
2033 | 2073 | octx.fillStyle = `rgba(95,115,125,${0.055 * fase0})`; |
|
0 commit comments