// ==UserScript== // @author Falenone, Codex // @name GlobeView // @category Map // @version 1.0 // @description A Cesium-based 3D globe view for IITC with portals, links, fields, visual effects, and configurable performance options. // @id globe-view@Falenone // @namespace https://github.com/IITC-CE/ingress-intel-total-conversion // @homepageURL https://github.com/Falenone/IITC-Globeview // @issueTracker https://github.com/Falenone/IITC-Globeview/issues // @updateURL https://raw.githubusercontent.com/IITC-CE/Community-plugins/master/dist/Falenone/globe-view.meta.js // @downloadURL https://raw.githubusercontent.com/IITC-CE/Community-plugins/master/dist/Falenone/globe-view.user.js // @icon https://raw.githubusercontent.com/Falenone/IITC-Globeview/b3f346f0b708a24e6a905325523dc4ef9dcbf093/assets/globeicon.svg // @match https://intel.ingress.com/* // @match https://intel-x.ingress.com/* // @grant none // ==/UserScript== function wrapper(plugin_info) { // ensure plugin framework is there, even if iitc is not yet loaded if(typeof window.plugin !== 'function') window.plugin = function() {}; //PLUGIN AUTHORS: writing a plugin outside of the IITC build environment? if so, delete these lines!! //(leaving them in place might break the 'About IITC' page or break update checks) plugin_info.buildName = 'local'; plugin_info.dateTimeVersion = 'None'; plugin_info.pluginId = 'globe-view'; //END PLUGIN AUTHORS NOTE /* global Cesium, IITC, L -- eslint */ /* exported setup --eslint */ var globeView = {}; window.plugin.globeView = globeView; globeView.CESIUM_VERSION = '1.144'; globeView.PROJECT_URL = 'https://github.com/'; globeView.CESIUM_URL = 'https://cesium.com/'; globeView.CESIUM_FAVICON_DATA_URI = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI1MTIiIGhlaWdodD0iNTEyIiB2aWV3Qm94PSIwIDAgNTEyIDUxMiI+CiAgICA8Y2lyY2xlIGN4PSIyNTYiIGN5PSIyNTYiIHI9IjI1NiIgZmlsbD0iI2ZmZiIvPgogICAgPHBhdGggZD0iTTI1NiAzMkMxMzIuMjg4IDMyIDMyIDEzMi4yODYgMzIgMjU2YTIyNCAyMjQgMCAwIDAgNy40MSA1Ni41NjRjLjEyNy4xMTQuMjYuMjM3LjM3OS4zMzYgNC4xMjggMy40MSA4Ljk0IDUuMTYgMTMuOCA1LjE2IDcuNDMgMCAxNC41OTUtNC4wOTQgMjAuMi0xMS41bDcwLjYwMS05My4zMDFjMTEuNjgyLTE1LjQzOCAyOC4zLTI0LjI5OSA0NS41LTI0LjI5OXMzMy43NCA4Ljg5NSA0NS41IDI0LjI5OWMxMC40MjggMTMuNjU5IDQ1LjA3MyA1OS42NjUgNjcuNzk5IDg5LjcwMS45MzIgMS4yMzIgMS45NDcgMi40IDIuOTIgMy42IDYuMTYgNy41OTQgMTIuNyAxMS41IDIwIDExLjUgNy41OTEgMCAxMy41MDUtMy42NTYgMjAtMTEuNS45OTMtMS4yIDIuMDM4LTIuMzYgMi45OC0zLjYgMjIuOTIyLTMwLjE1OSA1Ny4zNTUtNzUuOTM1IDY3LjgwMS04OS43MDEgMTEuNzAzLTE1LjQyMiAyOC4zLTI0LjI5OSA0NS41LTI0LjI5OSAyLjQzNyAwIDQuODc4LjMgNy4yODcuNjJDNDQwLjYwOCA5NS45NTIgMzU0LjAzNCAzMi4xMDcgMjU2IDMyem03NS4wNSA5NS4wNWEyMy41MjUgMjMuNTI1IDAgMCAxIDIzLjUyNiAyMy41MjcgMjMuNTI1IDIzLjUyNSAwIDAgMS0yMy41MjYgMjMuNTI2IDIzLjUyNSAyMy41MjUgMCAwIDEtMjMuNTI1LTIzLjUyNiAyMy41MjUgMjMuNTI1IDAgMCAxIDIzLjUyNS0yMy41Mjd6IiBmaWxsPSIjNmRhYmU0Ii8+CiAgICA8cGF0aCBkPSJNNDc4LjAxMSAyMjguMzk4Yy00LjY2Ni00LjM5OS0xMC4wOS02LjUzOC0xNi4xNTgtNi41MzgtOS4xMTQgMC0xNS4wMjcgNC43OS0yMC45NTMgMTEuNjRsLTcwLjQgOTMuM2MtMTEuNjk4IDE1LjUwMi0yOC4yIDI0LjI5OS00NS40IDI0LjI5OWgtLjI2OGMtMTcuMiAwLTMzLjc2Mi04Ljg3Ny00NS4zOTktMjQuM2wtNzAuNC05My4zYy01LjU5Ni03LjQxNi0xMi43LTExLjUtMjAuMS0xMS41LTcuMzYgMC0xNC41NTMgNC4xNDYtMjAuMTAxIDExLjVsLTcwLjM5OSA5My4zYy0xMS41NiAxNS4zMi0yNy44ODUgMjQuMzI5LTQ0Ljk3NiAyNC4zQzkwLjMgNDI5LjY3MyAxNjkuMjE2IDQ3OS44OTUgMjU2IDQ3OS45OTljMTIzLjcxMSAwIDIyNC0xMDAuMjg4IDIyNC0yMjRhMjI0LjAwNyAyMjQuMDA3IDAgMCAwLTEuOTg5LTI3LjYwMXoiIGZpbGw9IiM3MDljNDkiLz4KPC9zdmc+Cg=='; globeView.CESIUM_BASE_URL = 'https://cesium.com/downloads/cesiumjs/releases/' + globeView.CESIUM_VERSION + '/Build/Cesium/'; globeView.CESIUM_SCRIPT_URL = globeView.CESIUM_BASE_URL + 'Cesium.js'; globeView.FALLBACK_TILE_URL = 'https://tile.openstreetmap.org/'; globeView.active = false; globeView.viewer = null; globeView.container = null; globeView.linkEntities = {}; globeView.linkBaseCollection = null; globeView.linkBaseMaterials = {}; globeView.fieldEntities = {}; globeView.areaSelectionEntity = null; globeView.areaSelectionActive = false; globeView.areaSelectionPoints = []; globeView.excludedFieldGuids = {}; globeView.pinnedPortalGuids = {}; globeView.portalCompositionCache = null; globeView.compositionDataRevision = 0; globeView.selectedFieldGuid = null; globeView.linkEndpointPrimitives = null; globeView.syncTimer = null; globeView.linkGeometryTimer = null; globeView.lastLinkGeometryHeight = null; globeView.lastPortalDetailMode = null; globeView.fieldSyncTimer = null; globeView.mapSyncTimer = null; globeView.commPortalFollowTimer = null; globeView.cesiumPromise = null; globeView.activationToken = 0; globeView.INITIAL_CAMERA_HEIGHT = 9000000; globeView.initialMapZoom = null; globeView.ARC_HEIGHT_RATIO = 0.015; globeView.MIN_ARC_HEIGHT = 5; globeView.MAX_ARC_HEIGHT = 75000; globeView.OVERVIEW_ARC_CAMERA_HEIGHT = 1500000; globeView.OVERVIEW_ARC_LIFT_RATIO = 0.012; globeView.SCREENSHOT_MSAA_SAMPLES = 4; globeView.NEBULA_MIN_CAMERA_HEIGHT = 8500000; globeView.CLOSE_SPACE_BACKGROUND_HEIGHT = 300000; globeView.MIN_TILE_BRIGHTNESS = 0.25; globeView.MAX_TILE_BRIGHTNESS = 0.6; globeView.PORTAL_BILLBOARD_LIMIT = 400; globeView.PORTAL_DETAIL_CAMERA_HEIGHT = 2000000; globeView.FIELD_HEIGHT = 15000; globeView.LINK_SURFACE_HEIGHT = globeView.FIELD_HEIGHT + 1000; globeView.LINK_HEIGHT_LOD_END = 6000000; globeView.GLOBE_RESONANCE_TRAVEL_MS = 10000; globeView.GLOBE_RESONANCE_CYCLE_MS = 30000; globeView.GLOBE_RESONANCE_MIN_CAMERA_HEIGHT = 250000; // A full-degree ring keeps the sweep round even when the globe fills the viewport. globeView.GLOBE_RESONANCE_SEGMENTS = 360; globeView.debugFocusEntity = null; globeView.debugViewEntity = null; globeView.debugCoverageEntity = null; globeView.debugTileEntities = []; globeView.MAX_DEBUG_TILES = 200; globeView.portalPrimitives = null; globeView.polarAuroraCollection = null; globeView.portalBeaconCollection = null; globeView.portalBeaconMaterials = {}; globeView.globeGridCollection = null; globeView.cloudVeilLayer = null; globeView.globeResonanceEntities = []; globeView.globeResonanceStartedAt = 0; globeView.resonanceEntities = []; globeView.entryGlowTimer = null; globeView.portalRenderMode = null; globeView.portalSyncTimer = null; globeView.portalHeightTimer = null; globeView.portalPositionHeight = null; globeView.portalCount = 0; globeView.fieldCount = 0; globeView.portalIconCache = {}; globeView.nebulaStage = null; globeView.lastSunUpdateTime = 0; globeView.SUN_CSS_UPDATE_INTERVAL = 16; globeView.solarScreenPosition = null; globeView.solarEyePosition = null; globeView.globeRimScreenPosition = null; globeView.hoverCursorTimer = null; globeView.hoverCursorPosition = null; globeView.cameraMoving = false; globeView.autoStartTimer = null; globeView.autoStartPending = false; globeView.fps = 0; globeView.fpsFrameCount = 0; globeView.fpsSampleTime = 0; globeView.normalMsaaSamples = null; globeView.activeMsaaSamples = 1; globeView.selectedPortalEntity = null; globeView.lastAutoRotateTime = 0; globeView.lastScreenshotPresentationTime = 0; globeView.SETTINGS_KEY = 'plugin-globeview-settings'; globeView.CAMERA_STATE_KEY = 'plugin-globeview-last-camera'; globeView.restoreSavedView = false; globeView.VOLATILE_FIELD_FILTER_MODES = ['area', 'selectedPortal', 'pinnedPortals', 'selectedField', 'nested', 'parents', 'family']; globeView.DEFAULT_SETTINGS = { arcHeight: 1, autoStartGlobe: false, atmosphere: true, atmosphericEntryGlow: true, autoRotate: false, autoRotateSpeed: 0.03, bloom: false, bloomIntensity: 1, cinematicFlyIn: false, debugCoverage: false, debugFocus: false, debugPanel: false, debugTiles: false, debugView: false, fog: false, fieldOpacity: 1, fieldMotion: true, fieldDepthHaze: false, fieldCaustics: false, fieldShimmerIntensity: 0.45, fieldAreaRule: 'vertices', fieldFilterMode: 'all', fieldStyle: 'glass', factionResonance: true, fxaa: true, globeGrid: true, globeResonanceSweep: true, globeUi: true, linkOpacity: 0.9, linkEndpointColor: '#ffffff', linkEndpoints: false, linkFlow: false, linkFlowEnlColor: '#71ff71', linkFlowResColor: '#00aaff', linkFlowMachinaColor: '#ff1744', linkFlowOpacity: 1, linkFlowPulse: 'long', linkStyle: 'arc', linkWidth: 1, manualTime: 12, nightShading: true, portals: 'auto', portalSize: 12, showLinks: true, showFields: true, showEnlightenedFields: true, showResistanceFields: true, screenshotMode: false, screenshotGlow: true, screenshotPresentation: 'off', screenshotOrbitSpeed: 0.012, cloudVeil: true, spaceBackdrop: true, spaceMotion: true, maxPortals: 3000, nebula: true, advancedNebula: true, nebulaDust: 0.5, nebulaDensity: 1, nebulaMotion: true, nebulaPalette: 'cosmic', performanceProfile: 'balanced', portalLod: true, portalOutline: '#05080b', portalOutlineWidth: 1, portalBeacons: true, portalBeaconIntensity: 0.55, portalBeaconMode: 'selected', polarAurora: true, polarAuroraIntensity: 0.45, polarAuroraMotion: true, polarAuroraPalette: 'arctic', shootingStars: true, shootingStarDensity: 0.35, selectedPulse: true, showPortalOutline: true, starDensity: 1.25, starFlicker: true, starFlickerSpeed: 0.0175, starSize: 3, sun: true, sunGlow: 1, solarRimFlare: true, timeMode: 'live', flyToSelected: false, tileBrightness: 0.5, tileContrast: 1, tileSaturation: 1, }; globeView.loadSettings = function () { var stored = {}; var resetVolatileFilter = false; try { stored = JSON.parse(localStorage[globeView.SETTINGS_KEY] || '{}'); } catch (error) { console.warn('Globe view: unable to read settings', error); } var isFirstInstall = Object.keys(stored).length === 0; if (stored.performanceProfile === 'battery') stored.performanceProfile = 'low'; // Render scale is now always Device; discard the retired stored control. delete stored.renderScale; delete stored.cloudVeilMotion; delete stored.linkAtmosphericFade; delete stored.atmosphericScattering; globeView.settings = Object.assign({}, globeView.DEFAULT_SETTINGS, stored); if (isFirstInstall) globeView.settings = Object.assign(globeView.settings, globeView.getPerformanceProfile('balanced')); // New visual accents inherit the active named profile on upgrade, while a // Custom profile keeps the feature defaults until the user chooses them. var storedProfile = globeView.getPerformanceProfile(globeView.settings.performanceProfile); ['atmosphericEntryGlow', 'globeGrid', 'globeResonanceSweep', 'cloudVeil', 'factionResonance'].forEach(function (name) { if (!Object.prototype.hasOwnProperty.call(stored, name) && storedProfile && Object.prototype.hasOwnProperty.call(storedProfile, name)) { globeView.settings[name] = storedProfile[name]; } }); // Preserve explicit choices, but soften the old bright flow defaults on upgrade. if (Number(stored.linkFlowOpacity) === 0.35) globeView.settings.linkFlowOpacity = globeView.DEFAULT_SETTINGS.linkFlowOpacity; if (['short', 'medium', 'long'].indexOf(globeView.settings.linkFlowPulse) === -1) globeView.settings.linkFlowPulse = globeView.DEFAULT_SETTINGS.linkFlowPulse; if (globeView.settings.linkStyle === 'tactical') globeView.settings.linkStyle = 'flatter'; if (['arc', 'flatter', 'flat'].indexOf(globeView.settings.linkStyle) === -1) globeView.settings.linkStyle = globeView.DEFAULT_SETTINGS.linkStyle; globeView.settings.tileBrightness = Math.min( globeView.MAX_TILE_BRIGHTNESS, Math.max(globeView.MIN_TILE_BRIGHTNESS, Number(globeView.settings.tileBrightness) || globeView.DEFAULT_SETTINGS.tileBrightness) ); globeView.settings.bloomIntensity = Math.max( 0.25, Math.min(2, Number(globeView.settings.bloomIntensity) || globeView.DEFAULT_SETTINGS.bloomIntensity) ); var auroraIntensity = Number(globeView.settings.polarAuroraIntensity); globeView.settings.polarAuroraIntensity = Math.max( 0, Math.min(1, Number.isFinite(auroraIntensity) ? auroraIntensity : globeView.DEFAULT_SETTINGS.polarAuroraIntensity) ); if (['arctic', 'violet', 'ice'].indexOf(globeView.settings.polarAuroraPalette) === -1) { globeView.settings.polarAuroraPalette = globeView.DEFAULT_SETTINGS.polarAuroraPalette; } delete globeView.settings.twilightBand; delete globeView.settings.twilightIntensity; delete globeView.settings.fieldBoundaryTracer; delete globeView.settings.fieldTracerIntensity; if (globeView.settings.fieldStyle === 'aurora') globeView.settings.fieldStyle = 'shimmer'; if (['glass', 'shimmer', 'prism', 'canopy'].indexOf(globeView.settings.fieldStyle) === -1) { globeView.settings.fieldStyle = globeView.DEFAULT_SETTINGS.fieldStyle; } var shimmerIntensity = Number(globeView.settings.fieldShimmerIntensity); globeView.settings.fieldShimmerIntensity = Math.max(0, Math.min(1, Number.isFinite(shimmerIntensity) ? shimmerIntensity : globeView.DEFAULT_SETTINGS.fieldShimmerIntensity)); var nebulaDust = Number(globeView.settings.nebulaDust); globeView.settings.nebulaDust = Math.max(0, Math.min(1, Number.isFinite(nebulaDust) ? nebulaDust : globeView.DEFAULT_SETTINGS.nebulaDust)); var shootingDensity = Number(globeView.settings.shootingStarDensity); globeView.settings.shootingStarDensity = Math.max(0, Math.min(1, Number.isFinite(shootingDensity) ? shootingDensity : globeView.DEFAULT_SETTINGS.shootingStarDensity)); if (['off', 'orbit', 'flyIn'].indexOf(globeView.settings.screenshotPresentation) === -1) { globeView.settings.screenshotPresentation = globeView.DEFAULT_SETTINGS.screenshotPresentation; } var orbitSpeed = Number(globeView.settings.screenshotOrbitSpeed); globeView.settings.screenshotOrbitSpeed = Math.max(0.002, Math.min(0.06, Number.isFinite(orbitSpeed) ? orbitSpeed : globeView.DEFAULT_SETTINGS.screenshotOrbitSpeed)); var beaconIntensity = Number(globeView.settings.portalBeaconIntensity); globeView.settings.portalBeaconIntensity = Math.max(0, Math.min(1, Number.isFinite(beaconIntensity) ? beaconIntensity : globeView.DEFAULT_SETTINGS.portalBeaconIntensity)); if (['selected', 'highLevel'].indexOf(globeView.settings.portalBeaconMode) === -1) { globeView.settings.portalBeaconMode = globeView.DEFAULT_SETTINGS.portalBeaconMode; } if (['live', 'manual'].indexOf(globeView.settings.timeMode) === -1) globeView.settings.timeMode = 'live'; if (globeView.VOLATILE_FIELD_FILTER_MODES.indexOf(globeView.settings.fieldFilterMode) !== -1) { globeView.settings.fieldFilterMode = 'all'; resetVolatileFilter = true; } if (resetVolatileFilter) globeView.saveSettings(); }; globeView.saveSettings = function () { try { localStorage[globeView.SETTINGS_KEY] = JSON.stringify(globeView.settings); } catch (error) { console.warn('Globe view: unable to save settings', error); } }; globeView.getPerformanceProfile = function (name) { var energyFlowDefaults = { linkFlowEnlColor: '#71ff71', linkFlowResColor: '#00aaff', linkFlowMachinaColor: '#ff1744', linkFlowOpacity: 1, linkFlowPulse: 'long', }; var profiles = { low: Object.assign({ atmosphere: false, atmosphericEntryGlow: false, bloom: false, fxaa: false, fog: false, globeGrid: false, nightShading: false, sun: false, solarRimFlare: false, spaceBackdrop: false, spaceMotion: false, nebula: false, cloudVeil: false, advancedNebula: false, nebulaDust: 0, nebulaMotion: false, starFlicker: false, polarAurora: false, polarAuroraMotion: false, fieldDepthHaze: false, fieldCaustics: false, screenshotMode: false, screenshotGlow: false, screenshotPresentation: 'off', fieldFilterMode: 'all', fieldAreaRule: 'vertices', cinematicFlyIn: false, linkStyle: 'flat', linkEndpoints: false, linkFlow: false, fieldOpacity: 1, fieldStyle: 'glass', fieldMotion: false, factionResonance: false, globeResonanceSweep: false, portalBeacons: false, selectedPulse: false, showPortalOutline: false, portalLod: false, shootingStars: false, shootingStarDensity: 0, }, energyFlowDefaults), balanced: Object.assign({ atmosphere: true, atmosphericEntryGlow: true, bloom: false, bloomIntensity: 1, fog: false, globeGrid: false, fxaa: true, nightShading: true, sun: true, sunGlow: 1, solarRimFlare: false, spaceBackdrop: true, spaceMotion: false, nebula: false, cloudVeil: false, advancedNebula: false, nebulaDust: 0, nebulaDensity: 1, nebulaMotion: false, nebulaPalette: 'cosmic', polarAurora: false, polarAuroraIntensity: 0.45, polarAuroraMotion: false, polarAuroraPalette: 'arctic', starFlicker: false, starFlickerSpeed: 0.0175, starDensity: 1.25, starSize: 3, tileBrightness: 0.6, tileContrast: 1, tileSaturation: 1, showLinks: true, linkStyle: 'flatter', arcHeight: 1, linkWidth: 1, linkOpacity: 1, linkEndpoints: false, linkEndpointColor: '#ffffff', showFields: true, showEnlightenedFields: true, showResistanceFields: true, fieldOpacity: 1, fieldStyle: 'glass', fieldMotion: false, fieldDepthHaze: false, fieldCaustics: false, factionResonance: false, globeResonanceSweep: false, fieldShimmerIntensity: 0.45, screenshotMode: false, screenshotGlow: false, screenshotPresentation: 'off', cinematicFlyIn: false, screenshotOrbitSpeed: 0.012, fieldFilterMode: 'all', fieldAreaRule: 'vertices', portals: 'auto', portalSize: 12, maxPortals: 3000, portalLod: false, selectedPulse: false, showPortalOutline: false, portalOutline: '#05080b', portalOutlineWidth: 1, portalBeacons: false, portalBeaconIntensity: 0.55, portalBeaconMode: 'selected', shootingStars: false, shootingStarDensity: 0, }, energyFlowDefaults), quality: Object.assign({ atmosphere: true, atmosphericEntryGlow: true, bloom: true, bloomIntensity: 0.75, fog: true, globeGrid: true, fxaa: true, nightShading: true, sun: true, sunGlow: 1, solarRimFlare: true, spaceBackdrop: true, spaceMotion: true, nebula: true, cloudVeil: true, advancedNebula: true, nebulaDust: 0.5, nebulaDensity: 1, nebulaMotion: true, nebulaPalette: 'cosmic', polarAurora: true, polarAuroraIntensity: 0.45, polarAuroraMotion: true, polarAuroraPalette: 'arctic', starFlicker: true, starFlickerSpeed: 0.0175, starDensity: 1.25, starSize: 4.5, tileBrightness: 0.6, tileContrast: 1, tileSaturation: 1, showLinks: true, linkStyle: 'flatter', arcHeight: 1, linkWidth: 1, linkOpacity: 1, linkEndpoints: false, linkEndpointColor: '#ffffff', linkFlow: true, showFields: true, showEnlightenedFields: true, showResistanceFields: true, fieldOpacity: 1, fieldStyle: 'glass', fieldMotion: false, fieldDepthHaze: false, fieldCaustics: false, factionResonance: true, globeResonanceSweep: true, fieldShimmerIntensity: 0.45, screenshotMode: false, screenshotGlow: false, screenshotPresentation: 'off', cinematicFlyIn: false, screenshotOrbitSpeed: 0.012, fieldFilterMode: 'all', fieldAreaRule: 'vertices', portals: 'auto', portalSize: 12, maxPortals: 3000, portalLod: false, selectedPulse: false, showPortalOutline: false, portalOutline: '#05080b', portalOutlineWidth: 1, portalBeacons: true, portalBeaconIntensity: 0.55, portalBeaconMode: 'selected', shootingStars: true, shootingStarDensity: 0.35, timeMode: 'live', manualTime: 12, autoRotate: false, autoRotateSpeed: 0.03, flyToSelected: false, }, energyFlowDefaults), }; return profiles[name]; }; globeView.isPerformanceProfileSetting = function (name) { return ['low', 'balanced', 'quality'].some(function (profileName) { return Object.prototype.hasOwnProperty.call(globeView.getPerformanceProfile(profileName), name); }); }; globeView.applyPerformanceProfileToForm = function (form) { var profile = globeView.getPerformanceProfile(form.elements.performanceProfile.value); if (!profile) return; Object.keys(profile).forEach(function (name) { var input = form.elements[name]; if (!input) return; if (input.type === 'checkbox') input.checked = profile[name]; else input.value = profile[name]; }); }; globeView.getNebulaPalette = function () { var palettes = { cosmic: [[0.025, 0.29, 0.43], [0.34, 0.045, 0.38]], emerald: [[0.02, 0.38, 0.26], [0.04, 0.16, 0.42]], sunset: [[0.48, 0.16, 0.08], [0.36, 0.04, 0.28]], }; var palette = palettes[globeView.settings.nebulaPalette] || palettes.cosmic; return { a: new Cesium.Cartesian3(palette[0][0], palette[0][1], palette[0][2]), b: new Cesium.Cartesian3(palette[1][0], palette[1][1], palette[1][2]), }; }; globeView.getPolarAuroraColors = function () { var palettes = { arctic: ['#40ffad', '#29d9ff', '#8affcf'], violet: ['#62f4ff', '#ae72ff', '#60ffa5'], ice: ['#d8ffff', '#62d9ff', '#8eacff'], }; return (palettes[globeView.settings.polarAuroraPalette] || palettes.arctic).map(function (value) { return Cesium.Color.fromCssColorString(value).withAlpha(globeView.settings.polarAuroraIntensity); }); }; globeView.polarAuroraPositions = function (hemisphere, ribbon) { var positions = []; for (var degree = 0; degree <= 360; degree += 4) { var phase = Cesium.Math.toRadians(degree); var latitude = hemisphere * ( 69 + ribbon * 3.2 + Math.sin(phase * 3 + ribbon * 1.7) * 2.5 + Math.sin(phase * 7 + ribbon) * 0.75 ); var longitude = degree - 180; var height = 70000 + ribbon * 18000 + (Math.sin(phase * 5 + ribbon * 0.8) + 1) * 12000; positions.push(Cesium.Cartesian3.fromDegrees(longitude, latitude, height)); } return positions; }; globeView.polarAuroraMaterial = function (color, ribbon) { return new Cesium.Material({ fabric: { uniforms: { color: color, motion: globeView.settings.polarAuroraMotion ? 1 : 0, ribbon: ribbon }, source: [ 'czm_material czm_getMaterial(czm_materialInput materialInput) {', ' czm_material material = czm_getDefaultMaterial(materialInput);', ' float edge = 1.0 - abs(materialInput.st.t * 2.0 - 1.0);', ' float curtain = pow(max(0.0, edge), 0.72);', // Three complete cycles around a closed ribbon keep the moving pattern // in phase where its first and last positions meet at the date line. ' float ripple = 0.68 + 0.32 * sin(materialInput.st.s * 18.8495559 + ribbon * 2.1 + czm_frameNumber * 0.006 * motion);', ' material.diffuse = color.rgb * (0.58 + ripple * 0.42);', ' material.alpha = color.a * curtain * ripple;', ' return material;', '}', ].join('\n'), }, translucent: function () { return true; }, }); }; globeView.clearPolarAurora = function () { if (globeView.polarAuroraCollection && globeView.viewer) { globeView.viewer.scene.primitives.remove(globeView.polarAuroraCollection); } globeView.polarAuroraCollection = null; }; globeView.updatePolarAurora = function () { globeView.clearPolarAurora(); if (!globeView.active || !globeView.viewer || !globeView.settings.screenshotMode || !globeView.settings.polarAurora) return; var collection = globeView.viewer.scene.primitives.add(new Cesium.PolylineCollection()); var colors = globeView.getPolarAuroraColors(); [-1, 1].forEach(function (hemisphere) { colors.forEach(function (color, ribbon) { collection.add({ material: globeView.polarAuroraMaterial(color, ribbon), positions: globeView.polarAuroraPositions(hemisphere, ribbon), width: 24 - ribbon * 3, }); }); }); globeView.polarAuroraCollection = collection; }; globeView.clearGlobeGrid = function () { if (globeView.globeGridCollection && globeView.viewer) { globeView.viewer.scene.primitives.remove(globeView.globeGridCollection); } globeView.globeGridCollection = null; }; globeView.gridLinePositions = function (latitude, longitude, isMeridian) { var positions = []; var start = isMeridian ? -89 : -180; var end = isMeridian ? 89 : 180; for (var degree = start; degree <= end; degree += 1) { positions.push(Cesium.Cartesian3.fromDegrees(isMeridian ? longitude : degree, isMeridian ? degree : latitude, 700)); } return positions; }; globeView.updateGlobeGridVisibility = function () { if (!globeView.globeGridCollection || !globeView.viewer) return; if (globeView.globeGridCollection.show !== globeView.settings.globeGrid) { globeView.globeGridCollection.show = globeView.settings.globeGrid; } }; globeView.updateGlobeGrid = function () { globeView.clearGlobeGrid(); if (!globeView.active || !globeView.viewer || !globeView.settings.globeGrid) return; var collection = globeView.viewer.scene.primitives.add(new Cesium.PolylineCollection()); var material = Cesium.Material.fromType('Color'); material.uniforms.color = new Cesium.Color(0.34, 0.72, 1.0, 0.22); for (var latitude = -75; latitude <= 75; latitude += 15) { collection.add({ material: material, positions: globeView.gridLinePositions(latitude, 0, false), width: latitude === 0 ? 1.5 : 1 }); } for (var longitude = -165; longitude < 180; longitude += 15) { collection.add({ material: material, positions: globeView.gridLinePositions(0, longitude, true), width: longitude === 0 ? 1.5 : 1 }); } globeView.globeGridCollection = collection; globeView.updateGlobeGridVisibility(); }; globeView.clearGlobeResonance = function () { if (globeView.viewer) { globeView.globeResonanceEntities.forEach(function (entity) { globeView.viewer.entities.remove(entity); }); } globeView.globeResonanceEntities = []; globeView.globeResonanceStartedAt = 0; }; globeView.getGlobeResonanceProgress = function () { if (!globeView.globeResonanceStartedAt) return null; var elapsed = (performance.now() - globeView.globeResonanceStartedAt) % globeView.GLOBE_RESONANCE_CYCLE_MS; if (elapsed > globeView.GLOBE_RESONANCE_TRAVEL_MS) return null; return elapsed / globeView.GLOBE_RESONANCE_TRAVEL_MS; }; globeView.globeResonancePositions = function (progress, startLongitude, endLongitude) { var height = globeView.getFieldHeight() + Math.max(20, Math.min(2000, globeView.viewer.camera.positionCartographic.height * 0.0005)); var latitude = 84 - progress * 168; var segments = Math.max(8, Math.round(globeView.GLOBE_RESONANCE_SEGMENTS * Math.abs(endLongitude - startLongitude) / 360)); var positions = []; for (var index = 0; index <= segments; index++) { positions.push(Cesium.Cartesian3.fromDegrees(startLongitude + index * (endLongitude - startLongitude) / segments, latitude, height)); } return positions; }; globeView.updateGlobeResonance = function () { globeView.clearGlobeResonance(); if (!globeView.active || !globeView.viewer || !globeView.settings.globeResonanceSweep) return; globeView.globeResonanceStartedAt = performance.now(); var color = Cesium.Color.fromCssColorString('#71ff71'); var progress = function () { return globeView.getGlobeResonanceProgress(); }; var active = function () { return globeView.viewer.camera.positionCartographic.height >= globeView.GLOBE_RESONANCE_MIN_CAMERA_HEIGHT && progress() !== null; }; var addSegment = function (startLongitude, endLongitude) { return globeView.viewer.entities.add({ polyline: { positions: new Cesium.CallbackProperty(function () { var value = progress(); return value === null ? [] : globeView.globeResonancePositions(value, startLongitude, endLongitude); }, false), show: new Cesium.CallbackProperty(active, false), width: new Cesium.CallbackProperty(function () { var value = progress(); return value === null ? 1 : 4 + Math.sin(Math.PI * value) * 1.5; }, false), material: new Cesium.PolylineGlowMaterialProperty({ glowPower: 0.16, // Cesium disables tapering at 1.0. The sweep is rendered as two // date-line-safe halves, so tapering either half makes its shared // join look thin and broken. taperPower: 1, color: new Cesium.CallbackProperty(function () { var value = progress(); var alpha = value === null ? 0 : Math.sin(Math.PI * value) * 0.34; return color.withAlpha(alpha); }, false), }), }, }); }; globeView.globeResonanceEntities = [addSegment(-180, 0), addSegment(0, 180)]; }; globeView.applyTimeSettings = function () { if (!globeView.viewer) return; var clock = globeView.viewer.clock; if (globeView.settings.timeMode === 'manual') { var date = Cesium.JulianDate.toDate(Cesium.JulianDate.now()); date.setUTCHours(Math.floor(globeView.settings.manualTime), Math.round((globeView.settings.manualTime % 1) * 60), 0, 0); clock.currentTime = Cesium.JulianDate.fromDate(date); clock.shouldAnimate = false; return; } clock.currentTime = Cesium.JulianDate.now(); clock.multiplier = 1; clock.clockStep = Cesium.ClockStep.SYSTEM_CLOCK_MULTIPLIER; clock.shouldAnimate = true; }; globeView.resetNorth = function () { if (!globeView.viewer) return; globeView.viewer.camera.setView({ orientation: { heading: 0, pitch: -Cesium.Math.PI_OVER_TWO, roll: 0, }, }); }; globeView.updateAutoRotate = function () { if (!globeView.viewer || !globeView.settings.autoRotate) { globeView.lastAutoRotateTime = 0; return; } var now = performance.now(); if (globeView.lastAutoRotateTime) { var seconds = Math.min(0.1, (now - globeView.lastAutoRotateTime) / 1000); globeView.viewer.camera.rotate(Cesium.Cartesian3.UNIT_Z, globeView.settings.autoRotateSpeed * seconds); } globeView.lastAutoRotateTime = now; }; globeView.updateScreenshotPresentation = function () { if (!globeView.viewer || !globeView.settings.screenshotMode || globeView.settings.screenshotPresentation !== 'orbit') { globeView.lastScreenshotPresentationTime = 0; return; } var now = performance.now(); if (globeView.lastScreenshotPresentationTime) { var seconds = Math.min(0.1, (now - globeView.lastScreenshotPresentationTime) / 1000); globeView.viewer.camera.rotate(Cesium.Cartesian3.UNIT_Z, globeView.settings.screenshotOrbitSpeed * seconds); } globeView.lastScreenshotPresentationTime = now; }; globeView.startCinematicFlyIn = function () { globeView.lastScreenshotPresentationTime = 0; var screenshotFlyIn = globeView.settings.screenshotMode && globeView.settings.screenshotPresentation === 'flyIn'; if (!globeView.viewer || (!globeView.settings.cinematicFlyIn && !screenshotFlyIn)) return; var focus = globeView.getGlobeFocus(); var height = globeView.viewer.camera.positionCartographic.height; if (!focus || !Number.isFinite(height)) return; var targetHeight = Math.max(1000, height); globeView.viewer.camera.setView({ destination: Cesium.Cartesian3.fromRadians(focus.longitude, focus.latitude, targetHeight * 1.35), orientation: { heading: 0, pitch: -Cesium.Math.PI_OVER_TWO, roll: 0 }, }); globeView.viewer.camera.flyTo({ destination: Cesium.Cartesian3.fromRadians(focus.longitude, focus.latitude, targetHeight), duration: 2.2, easingFunction: Cesium.EasingFunction.QUADRATIC_OUT, orientation: { heading: 0, pitch: -Cesium.Math.PI_OVER_TWO, roll: 0 }, }); }; globeView.NEBULA_FRAGMENT_SHADER = [ 'uniform sampler2D colorTexture;', 'uniform sampler2D depthTexture;', 'uniform float nebulaDensity;', 'uniform float advancedNebula;', 'uniform float nebulaDust;', 'uniform float nebulaMotion;', 'uniform vec3 nebulaColorA;', 'uniform vec3 nebulaColorB;', 'uniform float starDensity;', 'uniform float starFlickerSpeed;', 'uniform float starFlickerEnabled;', 'uniform float starSize;', 'uniform float animationTime;', 'uniform float sunEnabled;', 'uniform float sunGlow;', 'uniform float solarRimFlare;', 'uniform vec2 globeRimCenter;', 'uniform float globeRimRadius;', 'uniform vec2 sunScreenCenter;', 'uniform float sunScreenVisible;', 'uniform float shootingStars;', 'uniform float shootingStarDensity;', 'uniform float nebulaEnabled;', 'uniform float cssBackdrop;', 'in vec2 v_textureCoordinates;', 'float hash(vec2 point) {', ' return fract(sin(dot(point, vec2(127.1, 311.7))) * 43758.5453123);', '}', 'float noise(vec2 point) {', ' vec2 cell = floor(point);', ' vec2 local = fract(point);', ' local = local * local * (3.0 - 2.0 * local);', ' return mix(mix(hash(cell), hash(cell + vec2(1.0, 0.0)), local.x), mix(hash(cell + vec2(0.0, 1.0)), hash(cell + vec2(1.0, 1.0)), local.x), local.y);', '}', 'float cloud(vec2 point) {', ' return noise(point) * 0.55 + noise(point * 2.03 + 14.7) * 0.30 + noise(point * 4.11 - 8.2) * 0.15;', '}', 'vec3 applySolarRim(vec3 color, vec2 uv) {', ' if (solarRimFlare < 0.5 || sunEnabled < 0.5) return color;', ' if (globeRimRadius <= 0.0) return color;', ' if (sunScreenVisible < 0.5) return color;', ' vec2 sunUv = sunScreenCenter;', ' vec2 fromGlobe = uv - globeRimCenter;', ' fromGlobe.x *= czm_viewport.z / czm_viewport.w;', ' float rimDistance = length(fromGlobe);', ' float band = 5.0 / czm_viewport.w;', ' float rim = 1.0 - smoothstep(0.0, band, abs(rimDistance - globeRimRadius));', ' if (rim <= 0.0) return color;', ' vec2 outward = fromGlobe / max(rimDistance, 0.00001);', ' vec2 toSun = sunUv - uv;', ' toSun.x *= czm_viewport.z / czm_viewport.w;', ' float facing = smoothstep(0.18, 0.92, dot(normalize(outward), normalize(toSun)));', ' float flare = facing * rim;', ' return color + vec3(1.0, 0.48, 0.12) * flare * 0.85;', '}', 'vec3 applyShootingStar(vec3 color, vec2 point) {', ' if (shootingStars < 0.5) return color;', ' float cycle = floor(animationTime * 0.075);', ' float progress = fract(animationTime * 0.075);', ' float seed = hash(vec2(cycle, 17.3));', ' float eventVisible = step(1.0 - shootingStarDensity, seed);', ' vec2 origin = vec2(hash(vec2(cycle, 4.1)) * 1.5 - 0.75, hash(vec2(cycle, 8.6)) * 1.1 - 0.55);', ' vec2 direction = normalize(vec2(0.88, -0.34 + hash(vec2(cycle, 12.9)) * 0.20));', ' vec2 head = origin + direction * (progress * 2.6 - 1.3);', ' vec2 relative = point - head;', ' float along = dot(relative, direction);', ' float across = abs(relative.x * direction.y - relative.y * direction.x);', ' float tail = smoothstep(-0.25, -0.015, along) * (1.0 - smoothstep(-0.015, 0.018, along));', ' float width = 1.0 - smoothstep(0.0015, 0.0065, across);', ' return color + vec3(0.70, 0.87, 1.0) * eventVisible * tail * width * 1.8;', '}', 'vec3 applySun(vec3 color, vec2 uv) {', ' if (sunEnabled < 0.5 || sunScreenVisible < 0.5) return color;', ' vec2 sunUv = sunScreenCenter;', ' vec2 delta = uv - sunUv;', ' delta.x *= czm_viewport.z / czm_viewport.w;', ' float glow = max(0.35, sunGlow);', ' float maximumHaloRadius = 0.118 * glow;', ' if (abs(delta.x) >= maximumHaloRadius || abs(delta.y) >= maximumHaloRadius) return color;', ' float distance = length(delta);', ' float core = 1.0 - smoothstep(0.004, 0.014, distance);', ' float corona = 1.0 - smoothstep(0.012, 0.052 * glow, distance);', ' float halo = 1.0 - smoothstep(0.040, 0.118 * glow, distance);', ' float angle = atan(delta.y, delta.x);', ' float rays = pow(max(0.0, cos(angle * 6.0 + animationTime * 0.8)), 13.0) * halo;', ' return color + vec3(1.0, 0.985, 0.91) * (core * 4.5 + corona * 0.85 + halo * 0.18 + rays * 0.20);', '}', 'void main() {', ' vec4 sceneColor = texture(colorTexture, v_textureCoordinates);', ' float depth = czm_readDepth(depthTexture, v_textureCoordinates);', ' if (depth < 0.999999) {', ' out_FragColor = sceneColor;', ' return;', ' }', ' if (nebulaEnabled < 0.5) {', // Cesium bloom composites an opaque black background. When the lightweight // CSS backdrop is active, restore transparency only behind depth-tested map // content so that the CSS layer can remain visible beneath the canvas. ' out_FragColor = vec4(sceneColor.rgb, cssBackdrop > 0.5 ? 0.0 : sceneColor.a);', ' return;', ' }', ' if (max(sceneColor.r, max(sceneColor.g, sceneColor.b)) > 0.12) {', ' out_FragColor = sceneColor;', ' return;', ' }', ' float time = animationTime * 0.055 * nebulaMotion;', ' vec2 point = v_textureCoordinates - 0.5;', ' point.x *= czm_viewport.z / czm_viewport.w;', ' float blueCloud = smoothstep(0.45, 0.78, cloud(point * 2.25 + vec2(time, -time * 0.5)));', ' float violetCloud = smoothstep(0.54, 0.83, cloud(point * 1.68 + vec2(-time * 0.6, time * 0.35) + 7.0));', ' vec3 color = vec3(0.010, 0.018, 0.065);', ' color = mix(color, nebulaColorA, blueCloud * 0.62 * nebulaDensity);', ' color = mix(color, nebulaColorB, violetCloud * 0.55 * nebulaDensity);', ' if (advancedNebula > 0.5 && nebulaDust > 0.001) {', ' float dustLane = smoothstep(0.48, 0.82, cloud(point * 5.2 + vec2(-time * 0.35, time * 0.18) + 21.0));', ' float filament = smoothstep(0.66, 0.91, cloud(point * 7.6 + vec2(time * 0.55, -time * 0.25) - 11.0));', ' color = mix(color, color * 0.32, dustLane * nebulaDust);', ' color += mix(nebulaColorA, nebulaColorB, 0.5) * filament * nebulaDust * 0.18;', ' }', ' vec2 starGrid = point * 150.0 * starDensity + vec2(time * 7.0, -time * 4.0);', ' vec2 cell = floor(starGrid);', ' vec2 local = fract(starGrid) - 0.5;', ' float starSeed = hash(cell);', ' if (starSeed >= 0.991) {', ' float star = 1.0 - smoothstep(0.018 * starSize, 0.055 * starSize, length(local));', ' if (star > 0.0) {', ' float twinkle = 1.0;', ' if (starFlickerEnabled > 0.5) twinkle = 0.65 + 0.35 * sin(animationTime * starFlickerSpeed * 100.0 + starSeed * 18.0);', ' color += vec3(0.55, 0.75, 1.0) * star * twinkle;', ' }', ' }', ' color = applySolarRim(color, v_textureCoordinates);', ' color = applyShootingStar(color, point);', ' out_FragColor = vec4(applySun(color, v_textureCoordinates), 1.0);', '}', ].join('\n'); globeView.getLinkFlowPreset = function () { var presets = { short: { density: 3, pulseLength: 0.09, speed: 0.16 }, medium: { density: 3, pulseLength: 0.16, speed: 0.16 }, long: { density: 3, pulseLength: 0.24, speed: 0.16 }, }; return presets[globeView.settings.linkFlowPulse] || presets.medium; }; globeView.ensureLinkBaseCollection = function () { if (globeView.linkBaseCollection || !globeView.viewer) return globeView.linkBaseCollection; globeView.linkBaseCollection = globeView.viewer.scene.primitives.add(new Cesium.PolylineCollection()); return globeView.linkBaseCollection; }; globeView.getLinkTeam = function (link) { var team = link && link.options && link.options.team; var name = String(team || '').toUpperCase(); if ((typeof window.TEAM_ENL !== 'undefined' && team === window.TEAM_ENL) || name.indexOf('ENL') !== -1) return 'enl'; if ((typeof window.TEAM_MACHINA !== 'undefined' && team === window.TEAM_MACHINA) || (typeof window.TEAM_MAC !== 'undefined' && team === window.TEAM_MAC) || name.indexOf('MACH') !== -1 || name.indexOf('MAC') !== -1) return 'machina'; return 'res'; }; globeView.getLinkFlowColor = function (link) { var setting = { enl: 'linkFlowEnlColor', res: 'linkFlowResColor', machina: 'linkFlowMachinaColor', }[globeView.getLinkTeam(link)]; return Cesium.Color.fromCssColorString(globeView.settings[setting]) .withAlpha(Math.max(0, Math.min(1, globeView.settings.linkFlowOpacity))); }; globeView.getLinkBaseMaterial = function (color, flowColor) { var preset = globeView.getLinkFlowPreset(); var edgeFeather = globeView.settings.screenshotMode ? 1 : 0; var key = (globeView.settings.linkFlow ? 'flow:' + flowColor.toCssColorString() + ':' + preset.pulseLength + ':' : 'color:') + color.toCssColorString() + ':edge:' + edgeFeather; if (globeView.linkBaseMaterials[key]) return globeView.linkBaseMaterials[key]; if (globeView.settings.linkFlow) { var flowSource = [ 'czm_material czm_getMaterial(czm_materialInput materialInput) {', ' czm_material material = czm_getDefaultMaterial(materialInput);', ' float phase = fract(materialInput.s * density - czm_frameNumber * (speed / 60.0));', ' float center = 0.22;', ' float halfLength = pulseLength * 0.5;', ' float tail = smoothstep(center - halfLength, center - halfLength + pulseLength * 0.24, phase);', ' float head = 1.0 - smoothstep(center + halfLength - pulseLength * 0.20, center + halfLength, phase);', ' float pulse = tail * head;', ' float core = 1.0 - smoothstep(0.0, halfLength * 0.72, abs(phase - center));', ' vec3 energy = min(vec3(1.0), flowColor.rgb * (0.82 + core * 0.42));', ' material.diffuse = mix(baseColor.rgb, energy, pulse * flowColor.a);', ]; if (edgeFeather) { flowSource.push( ' float edgeDistance = 1.0 - abs(materialInput.st.t * 2.0 - 1.0);', ' material.alpha = baseColor.a * smoothstep(0.0, 0.16, edgeDistance);' ); } else { flowSource.push(' material.alpha = baseColor.a;'); } flowSource.push(' return material;', '}'); globeView.linkBaseMaterials[key] = new Cesium.Material({ fabric: { uniforms: { baseColor: color, flowColor: flowColor, density: preset.density, pulseLength: preset.pulseLength, speed: preset.speed }, source: flowSource.join('\n'), }, translucent: function () { return color.alpha < 1 || edgeFeather > 0; }, }); return globeView.linkBaseMaterials[key]; } var material; if (edgeFeather) { material = new Cesium.Material({ fabric: { uniforms: { baseColor: color }, source: [ 'czm_material czm_getMaterial(czm_materialInput materialInput) {', ' czm_material material = czm_getDefaultMaterial(materialInput);', ' float edgeDistance = 1.0 - abs(materialInput.st.t * 2.0 - 1.0);', ' material.diffuse = baseColor.rgb;', ' material.alpha = baseColor.a * smoothstep(0.0, 0.16, edgeDistance);', ' return material;', '}', ].join('\n'), }, translucent: function () { return true; }, }); } else { material = Cesium.Material.fromType('Color'); material.uniforms.color = color; } globeView.linkBaseMaterials[key] = material; return globeView.linkBaseMaterials[key]; }; globeView.updateAnimationTime = function () { var now = performance.now() * 0.001; if (globeView.nebulaStage && globeView.nebulaStage.enabled) globeView.nebulaStage.uniforms.animationTime = now; }; globeView.addStyles = function () { $('