From 005f3bbe541d8fe6297f40b8e87d2d447d337a04 Mon Sep 17 00:00:00 2001 From: Michael Moore Date: Sun, 1 Sep 2024 23:51:55 -0500 Subject: [PATCH] feat(map): add basic display of solar system maps --- src/renderer/src/intl/en-US.ts | 6 + src/renderer/src/lib/ExportModal.svelte | 144 ++++--- src/renderer/src/lib/GameState.ts | 15 + src/renderer/src/lib/map/Glow.svelte | 3 +- src/renderer/src/lib/map/MapContainer.svelte | 235 ++++++----- src/renderer/src/lib/map/MapTooltip.svelte | 3 + .../map/solarSystemMap/SolarSystemMap.svelte | 372 ++++++++++++++++++ src/renderer/src/lib/settings/mapSettings.ts | 15 +- .../src/lib/settings/mapSettingsConfig.ts | 24 ++ 9 files changed, 660 insertions(+), 157 deletions(-) create mode 100644 src/renderer/src/lib/map/solarSystemMap/SolarSystemMap.svelte diff --git a/src/renderer/src/intl/en-US.ts b/src/renderer/src/intl/en-US.ts index 279d9a4..6cdb181 100644 --- a/src/renderer/src/intl/en-US.ts +++ b/src/renderer/src/intl/en-US.ts @@ -24,11 +24,13 @@ export default { select_save: 'Select a save in the top left', loading: 'This could take a few seconds', error: 'Something has gone wrong', + click_to_view_system: 'Click to open map', }, // various generic messages generic: { enabled: 'Enabled', disabled: 'Disabled', + back_button: 'Back', cancel_button: 'Cancel', close_button: 'Close', loading: 'Loading...', @@ -257,6 +259,7 @@ export default { advancedBorder: 'Advanced Border Settings', starscape: 'Starscape', legend: 'Legend', + solarSystemMap: 'Solar System Maps', }, mapMode: 'Map Mode', mapModePointOfView: 'Point of View', @@ -402,6 +405,9 @@ export default { legendBorderStroke: 'Border', legendBorderColor: 'Border Color', legendBackgroundColor: 'Background Color', + systemMapOrbitStroke: 'Orbit Lines', + systemMapOrbitColor: 'Orbit Lines Color', + systemMapPlanetScale: 'Planet Scale', appLocale: 'StellarMaps Language', appLocale_tooltip: 'Join the Discord server (link in the top bar) if you want to help translate!', diff --git a/src/renderer/src/lib/ExportModal.svelte b/src/renderer/src/lib/ExportModal.svelte index 37a6836..94f187e 100644 --- a/src/renderer/src/lib/ExportModal.svelte +++ b/src/renderer/src/lib/ExportModal.svelte @@ -6,11 +6,13 @@ getToastStore, localStorageStore, } from '@skeletonlabs/skeleton'; + import { onDestroy } from 'svelte'; import { t } from '../intl'; - import type { GameState } from './GameState'; + import type { GalacticObject, GameState } from './GameState'; import convertSvgToPng from './convertSvgToPng'; import type { MapData } from './map/data/processMapData'; import { getBackgroundColor, getFillColorAttributes, resolveColor } from './map/mapUtils'; + import SolarSystemMap from './map/solarSystemMap/SolarSystemMap.svelte'; import processStarScape from './map/starScape/renderStarScape'; import { mapSettings } from './settings'; import stellarMapsApi from './stellarMapsApi'; @@ -19,10 +21,26 @@ const _props = $$props; // this suppresses warning about unknown prop 'parent' const modalStore = getModalStore(); const toastStore = getToastStore(); - const svg: SVGGElement = $modalStore[0]?.meta?.svg; + const galaxyMapSvg: SVGElement = $modalStore[0]?.meta?.svg; const colors: Record = $modalStore[0]?.meta?.colors; const mapData: MapData = $modalStore[0]?.meta?.mapData; const gameState: GameState = $modalStore[0]?.meta?.gameState; + const openedSystem: GalacticObject | undefined = $modalStore[0]?.meta?.openedSystem; + + const solarSystemMapTarget = document.createElement('div'); + const solarSystemMap = openedSystem + ? new SolarSystemMap({ + target: solarSystemMapTarget, + props: { + id: 'exportSystemMap', + colors: colors, + gameState: gameState, + system: openedSystem, + exportMode: true, + }, + }) + : null; + onDestroy(() => solarSystemMap?.$destroy()); const defaultExportSettings = { lockAspectRatio: true, @@ -99,31 +117,36 @@ } async function exportPng() { - const backgroundImageUrl = await processStarScape( - gameState, - $mapSettings, - colors, + const backgroundImageUrl = openedSystem + ? undefined + : await processStarScape( + gameState, + $mapSettings, + colors, + { + left: mapLeft, + top: mapTop, + width: mapWidth, + height: mapHeight, + }, + { + width: imageWidth, + height: imageHeight, + }, + ); + const buffer = await convertSvgToPng( + solarSystemMap ? (solarSystemMapTarget.firstChild as SVGElement) : galaxyMapSvg, { left: mapLeft, top: mapTop, width: mapWidth, height: mapHeight, + outputWidth: imageWidth, + outputHeight: imageHeight, + backgroundImageUrl, + backgroundColor: getBackgroundColor(colors, $mapSettings), }, - { - width: imageWidth, - height: imageHeight, - }, - ); - const buffer = await convertSvgToPng(svg, { - left: mapLeft, - top: mapTop, - width: mapWidth, - height: mapHeight, - outputWidth: imageWidth, - outputHeight: imageHeight, - backgroundImageUrl, - backgroundColor: getBackgroundColor(colors, $mapSettings), - }).then((blob) => blob.arrayBuffer()); + ).then((blob) => blob.arrayBuffer()); const savePath = await stellarMapsApi.dialog.save({ defaultPath: await stellarMapsApi.path.join( await stellarMapsApi.path.pictureDir(), @@ -131,7 +154,7 @@ ), filters: [{ extensions: ['png'], name: 'Image' }], }); - if (savePath && svg) { + if (savePath && galaxyMapSvg) { await stellarMapsApi.fs.writeBinaryFile(savePath, new Uint8Array(buffer)).then(() => { toastStore.trigger({ background: 'variant-filled-success', @@ -154,34 +177,39 @@ } async function exportSvg() { - svg.setAttribute('width', imageWidth.toString()); - svg.setAttribute('height', imageHeight.toString()); - svg.setAttribute('viewBox', `${mapLeft} ${mapTop} ${mapWidth} ${mapHeight}`); + const svgToExport = openedSystem + ? (solarSystemMapTarget.firstChild as SVGElement) + : galaxyMapSvg; + svgToExport.setAttribute('width', imageWidth.toString()); + svgToExport.setAttribute('height', imageHeight.toString()); + svgToExport.setAttribute('viewBox', `${mapLeft} ${mapTop} ${mapWidth} ${mapHeight}`); const bgImage = document.createElementNS('http://www.w3.org/2000/svg', 'image'); - bgImage.setAttribute('x', mapLeft.toString()); - bgImage.setAttribute('y', mapTop.toString()); - bgImage.setAttribute('width', mapWidth.toString()); - bgImage.setAttribute('height', mapHeight.toString()); - bgImage.setAttribute( - 'xlink:href', - await processStarScape( - gameState, - $mapSettings, - colors, - { - left: mapLeft, - top: mapTop, - width: mapWidth, - height: mapHeight, - }, - { - width: imageWidth, - height: imageHeight, - }, - ), - ); - svg.prepend(bgImage); + if (!openedSystem) { + bgImage.setAttribute('x', mapLeft.toString()); + bgImage.setAttribute('y', mapTop.toString()); + bgImage.setAttribute('width', mapWidth.toString()); + bgImage.setAttribute('height', mapHeight.toString()); + bgImage.setAttribute( + 'xlink:href', + await processStarScape( + gameState, + $mapSettings, + colors, + { + left: mapLeft, + top: mapTop, + width: mapWidth, + height: mapHeight, + }, + { + width: imageWidth, + height: imageHeight, + }, + ), + ); + svgToExport.prepend(bgImage); + } const bgRect = document.createElementNS('http://www.w3.org/2000/svg', 'rect'); bgRect.setAttribute('x', mapLeft.toString()); @@ -189,11 +217,11 @@ bgRect.setAttribute('width', mapWidth.toString()); bgRect.setAttribute('height', mapHeight.toString()); bgRect.setAttribute('fill', getBackgroundColor(colors, $mapSettings)); - svg.prepend(bgRect); + svgToExport.prepend(bgRect); - const svgString = svg.outerHTML; - svg.removeChild(bgImage); - svg.removeChild(bgRect); + const svgString = svgToExport.outerHTML; + if (!openedSystem) svgToExport.removeChild(bgImage); + svgToExport.removeChild(bgRect); const savePath = await stellarMapsApi.dialog.save({ defaultPath: await stellarMapsApi.path @@ -201,7 +229,7 @@ .catch(() => ''), filters: [{ extensions: ['svg'], name: 'Image' }], }); - if (savePath && svg) { + if (savePath && svgToExport) { await stellarMapsApi.fs.writeFile(savePath, svgString).then(() => { toastStore.trigger({ background: 'variant-filled-success', @@ -377,7 +405,15 @@ role="button" style:cursor="pointer" > - {#if mapData} + {#if openedSystem} + + {:else if mapData} {#each mapData.borders as border} export let enabled: boolean; + export let filterId = 'glow'; {#if enabled} - + {/if} diff --git a/src/renderer/src/lib/map/MapContainer.svelte b/src/renderer/src/lib/map/MapContainer.svelte index fdc117d..da0f5f0 100644 --- a/src/renderer/src/lib/map/MapContainer.svelte +++ b/src/renderer/src/lib/map/MapContainer.svelte @@ -31,6 +31,7 @@ import MapTooltip from './MapTooltip.svelte'; import processMapData from './data/processMapData'; import { getBackgroundColor } from './mapUtils'; + import SolarSystemMap from './solarSystemMap/SolarSystemMap.svelte'; import processStarScape from './starScape/renderStarScape'; const modalStore = getModalStore(); @@ -73,7 +74,13 @@ modalStore.trigger({ type: 'component', component: 'export', - meta: { colors, mapData, gameState: gameStateOrNull, svg: mapTarget.firstChild }, + meta: { + colors, + mapData, + gameState: gameStateOrNull, + svg: mapTarget.firstChild, + openedSystem, + }, }); }); } @@ -375,35 +382,52 @@ onMouseMoveInnerDebounced(e); } - function onMapClick() { - const countryId = tooltip?.countryId; - if (countryId != null) { - editedMapSettings.update((value) => ({ ...value, mapModePointOfView: countryId.toString() })); - mapSettings.update((value) => ({ ...value, mapModePointOfView: countryId.toString() })); - lastProcessedMapSettings.update((value) => ({ - ...value, - mapModePointOfView: countryId.toString(), - })); + let openedSystem: GalacticObject | undefined = undefined; + function closeSystemMap() { + openedSystem = undefined; + } + $: if (gameStateOrNull || !gameStateOrNull) { + closeSystemMap(); + } + + function onMapClick(e: MouseEvent) { + if (e.shiftKey) { + const countryId = tooltip?.countryId; + if (countryId != null) { + editedMapSettings.update((value) => ({ + ...value, + mapModePointOfView: countryId.toString(), + })); + mapSettings.update((value) => ({ ...value, mapModePointOfView: countryId.toString() })); + lastProcessedMapSettings.update((value) => ({ + ...value, + mapModePointOfView: countryId.toString(), + })); + } + } else { + openedSystem = tooltip?.system; } }
- {#if dataOrNull && colorsOrNull} + {#if dataOrNull && colorsOrNull && openedSystem == null}
{/if} - {#if transform != null} - - {/if} +
+ {#if openedSystem} + + {/if} + {#if transform != null && !openedSystem} + + {/if} +
{#if dataOrNull}
{/await} - - { - tooltip = null; - }} - on:click={onMapClick} - class:cursor-pointer={tooltip?.countryId != null} - class="h-full w-full" - > - - {#if unzoomedStarScapeDataUrl} - - {/if} - - - {#if pngDataUrl} - - {/if} - - {#if starScapeDataUrl} - - - {:else if lastRenderedTransformStarScapePngDataUrl} + {#if openedSystem && gameStateOrNull && colorsOrNull} + + {:else} + + { + tooltip = null; + }} + on:click={onMapClick} + class:cursor-pointer={tooltip?.countryId != null} + class="h-full w-full" + > + {#if unzoomedStarScapeDataUrl} + + {/if} + + + {#if pngDataUrl} + + {/if} + + {#if starScapeDataUrl} - - - {/if} - {#if zoomedPngDataUrl} - - {:else if lastRenderedTransformPngDataUrl} - - - - {/if} - + + {:else if lastRenderedTransformStarScapePngDataUrl} + + + + + {/if} + {#if zoomedPngDataUrl} + + {:else if lastRenderedTransformPngDataUrl} + + + + {/if} + + {/if} {/if} diff --git a/src/renderer/src/lib/map/MapTooltip.svelte b/src/renderer/src/lib/map/MapTooltip.svelte index 01e8d04..9752ed7 100644 --- a/src/renderer/src/lib/map/MapTooltip.svelte +++ b/src/renderer/src/lib/map/MapTooltip.svelte @@ -92,4 +92,7 @@ {/each} {/if} + + {$t('map.click_to_view_system')} + diff --git a/src/renderer/src/lib/map/solarSystemMap/SolarSystemMap.svelte b/src/renderer/src/lib/map/solarSystemMap/SolarSystemMap.svelte new file mode 100644 index 0000000..d70edc4 --- /dev/null +++ b/src/renderer/src/lib/map/solarSystemMap/SolarSystemMap.svelte @@ -0,0 +1,372 @@ + + + + + + + + + + + + + {#if $mapSettings.systemMapOrbitStroke.enabled} + {#each planets.filter((p) => !isAsteroid(p)) as planet (planet.id)} + + p.id === planet.moon_of)?.coordinate.x ?? 0)} + cy={planets.find((p) => p.id === planet.moon_of)?.coordinate.y ?? 0} + r={planet.orbit} + fill="none" + {...getStrokeAttributes($mapSettings.systemMapOrbitStroke)} + {...getStrokeColorAttributes({ + colors, + mapSettings: $mapSettings, + colorStack: [$mapSettings.systemMapOrbitColor], + })} + /> + + {/each} + {/if} + + {#each system.asteroid_belts as belt} + + + + + + {/each} + {#each planets as planet (planet.id)} + {#if isStar(planet)} + + + + {:else} + + + {/if} + {/each} + + diff --git a/src/renderer/src/lib/settings/mapSettings.ts b/src/renderer/src/lib/settings/mapSettings.ts index 0d471a5..61963d1 100644 --- a/src/renderer/src/lib/settings/mapSettings.ts +++ b/src/renderer/src/lib/settings/mapSettings.ts @@ -24,7 +24,8 @@ export type NumberOptionalMapSettings = | 'countryNamesSecondaryRelativeSize' | 'claimVoidMaxSize' | 'frontierBubbleThreshold' - | 'starScapeStarsCount'; + | 'starScapeStarsCount' + | 'systemMapPlanetScale'; export type StringMapSettings = | 'countryNamesFont' @@ -71,6 +72,7 @@ export type ColorMapSettings = | 'legendBorderColor' | 'lGateStrokeColor' | 'occupationColor' + | 'systemMapOrbitColor' | 'sectorBorderColor' | 'sectorCoreBorderColor' | 'sectorFrontierBorderColor' @@ -92,6 +94,7 @@ export type StrokeMapSettings = | 'hyperRelayStroke' | 'legendBorderStroke' | 'lGateStroke' + | 'systemMapOrbitStroke' | 'sectorBorderStroke' | 'sectorCoreBorderStroke' | 'sectorFrontierBorderStroke' @@ -418,6 +421,16 @@ export const defaultMapSettings: MapSettings = { color: 'border', colorAdjustments: [{ type: 'MIN_CONTRAST', value: 0.25 }], }, + systemMapOrbitColor: { color: 'dark_grey', colorAdjustments: [] }, + systemMapOrbitStroke: { + dashArray: '3 3', + dashed: false, + enabled: true, + glow: false, + smoothing: false, + width: 0.5, + }, + systemMapPlanetScale: 1, }; export const mapSettings = localStorageStore('mapSettings', defaultMapSettings); diff --git a/src/renderer/src/lib/settings/mapSettingsConfig.ts b/src/renderer/src/lib/settings/mapSettingsConfig.ts index 1890127..8ea10c4 100644 --- a/src/renderer/src/lib/settings/mapSettingsConfig.ts +++ b/src/renderer/src/lib/settings/mapSettingsConfig.ts @@ -693,4 +693,28 @@ export const mapSettingsConfig: MapSettingConfigGroup[] = [ }, ], }, + { + id: 'solarSystemMap', + name: 'setting.group.solarSystemMap', + settings: [ + { + id: 'systemMapOrbitStroke', + type: 'stroke', + noSmoothing: true, + }, + { + id: 'systemMapOrbitColor', + type: 'color', + allowedDynamicColors: [], + hideIf: (settings) => !settings.systemMapOrbitStroke.enabled, + }, + { + id: 'systemMapPlanetScale', + type: 'number', + optional: true, + min: 0, + step: 0.1, + }, + ], + }, ];