|
| 1 | +/** |
| 2 | + * Calculates sky light level based on Minecraft time of day. |
| 3 | + * |
| 4 | + * Minecraft time reference: |
| 5 | + * - 0 ticks = 6:00 AM (sunrise complete) |
| 6 | + * - 6000 ticks = 12:00 PM (noon) - brightest |
| 7 | + * - 12000 ticks = 6:00 PM (sunset begins) |
| 8 | + * - 13000 ticks = 7:00 PM (dusk/night begins) |
| 9 | + * - 18000 ticks = 12:00 AM (midnight) - darkest |
| 10 | + * - 23000 ticks = 5:00 AM (dawn begins) |
| 11 | + * - 24000 ticks = 6:00 AM (same as 0) |
| 12 | + * |
| 13 | + * Sky light ranges from 4 (night) to 15 (day). |
| 14 | + */ |
| 15 | + |
| 16 | +/** |
| 17 | + * Calculate celestial angle from time of day (0-1 range representing sun position) |
| 18 | + */ |
| 19 | +export const getCelestialAngle = (timeOfDay: number): number => { |
| 20 | + // Normalize time to 0-1 range |
| 21 | + let angle = ((timeOfDay % 24_000) / 24_000) - 0.25 |
| 22 | + |
| 23 | + if (angle < 0) angle += 1 |
| 24 | + if (angle > 1) angle -= 1 |
| 25 | + |
| 26 | + // Vanilla Minecraft applies a smoothing curve |
| 27 | + const smoothedAngle = angle + (1 - Math.cos(angle * Math.PI)) / 2 |
| 28 | + return smoothedAngle |
| 29 | +} |
| 30 | + |
| 31 | +/** |
| 32 | + * Calculate sky light level (0-15) based on time of day in ticks. |
| 33 | + * Matches Minecraft vanilla behavior. |
| 34 | + * |
| 35 | + * @param timeOfDay - Time in ticks (0-24000) |
| 36 | + * @returns Sky light level (4-15, where 15 is brightest day, 4 is darkest night) |
| 37 | + */ |
| 38 | +export const calculateSkyLight = (timeOfDay: number): number => { |
| 39 | + // Normalize time to 0-24000 range |
| 40 | + const normalizedTime = ((timeOfDay % 24_000) + 24_000) % 24_000 |
| 41 | + |
| 42 | + // Calculate celestial angle (0-1, where 0.25 is noon, 0.75 is midnight) |
| 43 | + const celestialAngle = getCelestialAngle(normalizedTime) |
| 44 | + |
| 45 | + // Calculate brightness factor based on celestial angle |
| 46 | + // cos gives us smooth day/night transition |
| 47 | + const cos = Math.cos(celestialAngle * Math.PI * 2) |
| 48 | + |
| 49 | + // Map cos (-1 to 1) to brightness (0 to 1) |
| 50 | + // At noon (celestialAngle ~0.25): cos(0.5π) = 0, but we want max brightness |
| 51 | + // At midnight (celestialAngle ~0.75): cos(1.5π) = 0, but we want min brightness |
| 52 | + |
| 53 | + // Vanilla-like calculation: |
| 54 | + // brightness goes from 0 (dark) to 1 (bright) |
| 55 | + const brightness = cos * 0.5 + 0.5 |
| 56 | + |
| 57 | + // Apply threshold - night should be darker |
| 58 | + // Vanilla has minimum sky light of 4 during night |
| 59 | + const skyLight = Math.round(4 + brightness * 11) |
| 60 | + |
| 61 | + return Math.max(4, Math.min(15, skyLight)) |
| 62 | +} |
| 63 | + |
| 64 | +/** |
| 65 | + * Simplified sky light calculation that more closely matches vanilla behavior. |
| 66 | + * Uses piecewise linear interpolation based on known Minecraft light levels. |
| 67 | + * |
| 68 | + * @param timeOfDay - Time in ticks (0-24000) |
| 69 | + * @returns Sky light level (4-15) |
| 70 | + */ |
| 71 | +export const calculateSkyLightSimple = (timeOfDay: number): number => { |
| 72 | + // Normalize to 0-24000 |
| 73 | + const time = ((timeOfDay % 24_000) + 24_000) % 24_000 |
| 74 | + |
| 75 | + // Vanilla Minecraft approximate sky light levels: |
| 76 | + // 0-12000 (6AM-6PM): Day, sky light = 15 |
| 77 | + // 12000-13000 (6PM-7PM): Sunset transition, 15 -> 4 |
| 78 | + // 13000-23000 (7PM-5AM): Night, sky light = 4 |
| 79 | + // 23000-24000 (5AM-6AM): Sunrise transition, 4 -> 15 |
| 80 | + |
| 81 | + if (time >= 0 && time < 12_000) { |
| 82 | + // Day time - full brightness |
| 83 | + return 15 |
| 84 | + } else if (time >= 12_000 && time < 13_000) { |
| 85 | + // Sunset transition (6PM to 7PM) |
| 86 | + const progress = (time - 12_000) / 1000 |
| 87 | + return Math.round(15 - progress * 11) |
| 88 | + } else if (time >= 13_000 && time < 23_000) { |
| 89 | + // Night time - minimum brightness |
| 90 | + return 4 |
| 91 | + } else { |
| 92 | + // Sunrise transition (5AM to 6AM) |
| 93 | + const progress = (time - 23_000) / 1000 |
| 94 | + return Math.round(4 + progress * 11) |
| 95 | + } |
| 96 | +} |
| 97 | + |
| 98 | +// Test/debug helper - run this to see values at different times |
| 99 | +export const debugSkyLight = () => { |
| 100 | + const testTimes = [ |
| 101 | + { ticks: 0, label: '6:00 AM (sunrise)' }, |
| 102 | + { ticks: 6000, label: '12:00 PM (noon)' }, |
| 103 | + { ticks: 12_000, label: '6:00 PM (sunset starts)' }, |
| 104 | + { ticks: 12_500, label: '6:30 PM (sunset mid)' }, |
| 105 | + { ticks: 13_000, label: '7:00 PM (night begins)' }, |
| 106 | + { ticks: 18_000, label: '12:00 AM (midnight)' }, |
| 107 | + { ticks: 19_000, label: '1:00 AM' }, |
| 108 | + { ticks: 23_000, label: '5:00 AM (dawn begins)' }, |
| 109 | + { ticks: 23_500, label: '5:30 AM (dawn mid)' }, |
| 110 | + ] |
| 111 | + |
| 112 | + console.log('Sky Light Debug:') |
| 113 | + console.log('================') |
| 114 | + for (const { ticks, label } of testTimes) { |
| 115 | + const smooth = calculateSkyLight(ticks) |
| 116 | + const simple = calculateSkyLightSimple(ticks) |
| 117 | + console.log(`${ticks.toString().padStart(5)} ticks (${label}): smooth=${smooth}, simple=${simple}`) |
| 118 | + } |
| 119 | +} |
| 120 | + |
| 121 | +// Export for global access in console |
| 122 | +if (typeof window !== 'undefined') { |
| 123 | + (window as any).debugSkyLight = debugSkyLight |
| 124 | +} |
0 commit comments