diff --git a/src/renderer/src/intl/en-US.ts b/src/renderer/src/intl/en-US.ts index c95d1f4..7a42de9 100644 --- a/src/renderer/src/intl/en-US.ts +++ b/src/renderer/src/intl/en-US.ts @@ -417,6 +417,9 @@ export default { legendBackgroundColor: 'Background Color', systemMapOrbitStroke: 'Orbit Lines', systemMapOrbitColor: 'Orbit Lines Color', + systemMapOrbitDistanceExponent: 'Exponential Orbit Scale', + systemMapOrbitDistanceExponent_tooltip: + 'Exponentially scales the distances between between objects and the things they orbit, so, for example, the outer planets are spaced further apart from each than the inner planets.', systemMapStarScale: 'Star Scale', systemMapPlanetScale: 'Planet Scale', systemMapMoonScale: 'Moon Scale', diff --git a/src/renderer/src/lib/map/solarSystemMap/SolarSystemMap.svelte b/src/renderer/src/lib/map/solarSystemMap/SolarSystemMap.svelte index 185df95..e3e43ae 100644 --- a/src/renderer/src/lib/map/solarSystemMap/SolarSystemMap.svelte +++ b/src/renderer/src/lib/map/solarSystemMap/SolarSystemMap.svelte @@ -384,10 +384,9 @@ ); } - function getPlanetLabelPathAttributes(planet: Planet, settings: MapSettings) { + function getPlanetLabelPathAttributes(planet: Planet, planets: Planet[], settings: MapSettings) { const r = getPlanetRadius(planet, settings); - let x = -planet.coordinate.x; - let y = planet.coordinate.y; + let { x, y } = getPlanetCoordinate(planet, planets, settings); let position = settings.systemMapLabelPlanetsPosition; if (position === 'orbit' && !planet.orbit) { position = settings.systemMapLabelPlanetsFallbackPosition; @@ -412,9 +411,11 @@ return { d: `M ${x} ${y} h 1000`, pathLength: 1 }; } case 'orbit': { - const cx = -(planets.find((p) => p.id === planet.moon_of)?.coordinate.x ?? 0); - const cy = planets.find((p) => p.id === planet.moon_of)?.coordinate.y ?? 0; - const orbitRadius = planet.orbit; + const primaryBody = getPrimaryBodies(planet, planets)[0]; + const { x: cx, y: cy } = primaryBody + ? getPlanetCoordinate(primaryBody, planets, settings) + : { x: 0, y: 0 }; + const orbitRadius = getPlanetOrbitDistance(planet, planets, settings); if (cy > y) { return { d: `M ${x} ${y} A ${orbitRadius} ${orbitRadius} 0 0 1 ${cx + (cx - x)} ${cy + (cy - y)}`, @@ -495,6 +496,104 @@ return angle; } + function getScaledDistance(unscaledDistance: number, settings: MapSettings) { + const exponent = settings.systemMapOrbitDistanceExponent; + return (unscaledDistance ** exponent / 400 ** exponent) * 400; + } + + function getScaledCoordinate( + coordinate: { x: number; y: number }, + relativeTo: { x: number; y: number; r: number }[], + settings: MapSettings, + ): { x: number; y: number } { + const unscaledDistance = Math.hypot( + coordinate.x - (relativeTo[0]?.x ?? 0), + coordinate.y - (relativeTo[0]?.y ?? 0), + ); + const scaledDistance = + (relativeTo[0]?.r ?? 0) + + getScaledDistance(unscaledDistance - (relativeTo[0]?.r ?? 0), settings); + const theta = Math.atan2( + coordinate.y - (relativeTo[0]?.y ?? 0), + coordinate.x - (relativeTo[0]?.x ?? 0), + ); + const scaledRelativeTo = relativeTo[0] + ? getScaledCoordinate(relativeTo[0], relativeTo.slice(1), settings) + : { x: 0, y: 0 }; + const x = scaledRelativeTo.x + Math.cos(theta) * scaledDistance; + const y = scaledRelativeTo.y + Math.sin(theta) * scaledDistance; + return { x, y }; + } + + function flipX(coordinate: T): T { + return { ...coordinate, x: -coordinate.x }; + } + + function getPrimaryBodies(planet: Planet, planets: Planet[]) { + const primaryBodies: Planet[] = []; + let done = false; + let planetToCheck: Planet = planet; + while (!done) { + let primaryBody = planets.find((p) => p.id === planetToCheck.moon_of); + if (!primaryBody && !(planetToCheck.coordinate.x === 0 && planetToCheck.coordinate.y === 0)) { + primaryBody = planets.find((p) => p.coordinate.x === 0 && p.coordinate.y === 0); + } + if (primaryBody) { + primaryBodies.push(primaryBody); + planetToCheck = primaryBody; + } else { + done = true; + } + } + return primaryBodies; + } + + function getPlanetCoordinate(planet: Planet, planets: Planet[], settings: MapSettings) { + return getScaledCoordinate( + flipX(planet.coordinate), + getPrimaryBodies(planet, planets).map((p) => ({ + ...flipX(p.coordinate), + r: getPlanetRadius(p, settings), + })), + settings, + ); + } + + function getPlanetOrbitDistance(planet: Planet, planets: Planet[], settings: MapSettings) { + const primary = getPrimaryBodies(planet, planets)[0]; + const primaryR = primary ? getPlanetRadius(primary, settings) : 0; + const unscaledDistance = Math.hypot( + planet.coordinate.x - (primary?.coordinate.x ?? 0), + planet.coordinate.y - (primary?.coordinate.y ?? 0), + ); + const scaledDistance = primaryR + getScaledDistance(unscaledDistance - primaryR, settings); + return scaledDistance; + } + + function getFleetCoordinate( + fleet: (typeof fleets)[number], + planets: Planet[], + settings: MapSettings, + ) { + const closestPlanet = planets.toSorted( + (a, b) => + (-a.coordinate.x - fleet.coordinate.x) ** 2 + + (a.coordinate.y - fleet.coordinate.y) ** 2 - + (-b.coordinate.x - fleet.coordinate.x) ** 2 - + (b.coordinate.y - fleet.coordinate.y) ** 2, + )[0]; + return getScaledCoordinate( + fleet.coordinate, + closestPlanet + ? [closestPlanet, ...getPrimaryBodies(closestPlanet, planets)].map((p) => ({ + ...flipX(p.coordinate), + r: getPlanetRadius(p, settings), + })) + : [], + settings, + ); + } + const PLANET_RING_PATTERN = ( [ [0.3, 0], @@ -540,7 +639,10 @@ width="1100%" height="1100%" > - + @@ -548,10 +650,14 @@ {#if $mapSettings.systemMapOrbitStroke.enabled} {#each planets.filter((p) => !isAsteroid(p) && p.orbit > 0) as planet (planet.id)} + {@const primary = getPrimaryBodies(planet, planets)[0]} + {@const primaryCoordinate = primary + ? getPlanetCoordinate(primary, planets, $mapSettings) + : { x: 0, y: 0 }} p.id === planet.moon_of)?.coordinate.x ?? 0)} - cy={planets.find((p) => p.id === planet.moon_of)?.coordinate.y ?? 0} - r={planet.orbit} + cx={primaryCoordinate.x} + cy={primaryCoordinate.y} + r={getPlanetOrbitDistance(planet, planets, $mapSettings)} fill="none" {...getStrokeAttributes($mapSettings.systemMapOrbitStroke)} {...getStrokeColorAttributes({ @@ -565,73 +671,72 @@ {/if} {#each system.asteroid_belts as belt} + {@const centralStar = planets.find((p) => p.coordinate.x === 0 && p.coordinate.y === 0)} + {@const centralStarR = centralStar ? getPlanetRadius(centralStar, $mapSettings) : 0} + {@const beltDistance = + getScaledDistance(belt.inner_radius - centralStarR, $mapSettings) + centralStarR} {/each} {#each planets as planet (planet.id)} + {@const coordinate = getPlanetCoordinate(planet, planets, $mapSettings)} {#if isStar(planet)} {:else} {@const radius = getPlanetRadius(planet, $mapSettings)} - + {#if planet.has_ring} {#each PLANET_RING_PATTERN as ring} 'start') .with('left', () => 'end') diff --git a/src/renderer/src/lib/settings/mapSettings.ts b/src/renderer/src/lib/settings/mapSettings.ts index a586465..9d1bc9d 100644 --- a/src/renderer/src/lib/settings/mapSettings.ts +++ b/src/renderer/src/lib/settings/mapSettings.ts @@ -12,6 +12,7 @@ export type NumberMapSettings = | 'legendFontSize' | 'systemMapLabelFleetsFontSize' | 'systemMapLabelPlanetsFontSize' + | 'systemMapOrbitDistanceExponent' | 'systemNamesFontSize' | 'terraIncognitaBrightness' | 'unionLeaderSymbolSize' @@ -502,6 +503,7 @@ export const defaultMapSettings: MapSettings = { color: 'planet', colorAdjustments: [{ type: 'OPACITY', value: 0.75 }], }, + systemMapOrbitDistanceExponent: 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 8646a11..b09f887 100644 --- a/src/renderer/src/lib/settings/mapSettingsConfig.ts +++ b/src/renderer/src/lib/settings/mapSettingsConfig.ts @@ -729,6 +729,13 @@ export const mapSettingsConfig: MapSettingConfigGroup[] = [ allowedDynamicColors: [], hideIf: (settings) => !settings.systemMapOrbitStroke.enabled, }, + { + id: 'systemMapOrbitDistanceExponent', + type: 'number', + min: 1, + step: 0.1, + tooltip: 'setting.systemMapOrbitDistanceExponent_tooltip', + }, { id: 'systemMapStarScale', type: 'number',