diff --git a/src/BubblingGalaxies.vue b/src/BubblingGalaxies.vue index a9ff2fa..0394dfc 100644 --- a/src/BubblingGalaxies.vue +++ b/src/BubblingGalaxies.vue @@ -84,154 +84,113 @@ > Skip Intro - +
- - -
- - About - - - + + About + + - - - View in 3D! - +
+
+ + + +
+
+ + View in 3D! + + + + +
-
- - - - - - + - - - - +
+
+ + + +
+
+ + + +
@@ -319,7 +278,7 @@ - - +
@@ -435,13 +402,45 @@ view the 3D simulation in augmented reality!

- - + class="mb-3" + :color="accentColorDarker" + elevation="0" + > + + + Foreground: {{ showSimulation ? 'Simulation' : selectedGalleryItem?.get_name() }} + + + + + + + + + + Background: {{ backgroundPlace[0].get_name() }} + + + + + + + @@ -463,7 +462,7 @@ import SplitScreenSvg from "./components/SplitScreenSvg.vue"; import { WWTControl } from "@wwtelescope/engine"; import Gallery from "./components/Gallery.vue"; - +import PlaceGallery from "./components/PlaceGallery.vue"; import SplashScreen from "./components/SplashScreen.vue"; import InformationSheet from "./components/InformationSheet.vue"; @@ -536,6 +535,7 @@ watch(showSplashScreen, (showing) => { const layersLoaded = ref(false); const positionSet = ref(false); const accentColor = ref("#d957db"); +const accentColorDarker = ref("#4d1c4e"); const buttonColor = ref("#ffffff"); const showModel = ref(false); @@ -598,7 +598,7 @@ const labelTitles: Record = { content: '', }, 'Simulation on Sky': { - title: 'Simulation on the Sky', + title: 'Simulation', content: '', }, "2011 Infrared Dust (WISE)": { @@ -618,6 +618,38 @@ const currentLabel = computed(() => { const showSimulation = ref(false); +const backgroundPlaceNames = ['2023 Infrared (Spitzer)', 'Optical (Kitt Peak)']; +const useIrBase = ref(false); +const persistantImage = computed(() => { return useIrBase.value ? backgroundPlaceNames[0] : backgroundPlaceNames[1];}); +function switchBaseImage() { + useIrBase.value = !useIrBase.value; +} + +const backgroundPlace = computed({ + get() { + const place = useIrBase.value + ? galleryPlaces.value.find(p => p.get_name() === backgroundPlaceNames[0]) + : galleryPlaces.value.find(p => p.get_name() === backgroundPlaceNames[1]); + console.log("Computed background place as", place?.get_name()); + return place ? [place] : []; + }, + set(newPlace) { + console.log("Setting background place to", newPlace.map(p => p.get_name())); + if (newPlace[0] == null) return; + const name = newPlace[0].get_name(); + if (name === backgroundPlaceNames[0]) { + useIrBase.value = true; + } else if (name === backgroundPlaceNames[1]) { + useIrBase.value = false; + } + } +}); +watch(useIrBase, (useIr) => { + const _back = galleryPlaces.value.find(p => p.get_name() === (useIr ? backgroundPlaceNames[0] : backgroundPlaceNames[1])); + if (_back) { + backgroundPlace.value = [_back]; + } +}); import { BoxGeometry, DoubleSide, Mesh, MeshBasicMaterial, MeshPhysicalMaterial, Object3D, PerspectiveCamera, Scene, SpotLight, WebGLRenderer } from "three"; import { storeToRefs } from "pinia"; @@ -782,6 +814,23 @@ function updateCurrentLayersOpacity(opacity: number) { } } +// view has changed means either +// - the view is distant from the initial view (more than 10 arcsec) +// - or the shown selectedGallery item is not the default +// - or we have a different useIrBase value than the default +const viewHasChanged = computed(() => { + const defaultRA = props.initialCameraParams.raRad; + const defaultDec = props.initialCameraParams.decRad; + const currentRA = store.raRad; + const currentDec = store.decRad; + // 1 arcsecond + const maxDistance = (1 / 3600) * D2R; + const distance = Math.sqrt((currentRA - defaultRA) ** 2 + (currentDec - defaultDec) ** 2); + const selectedItemIsDefault = selectedGalleryItem.value?.get_name() === "Infrared Stars & Dust (JWST)"; + const usingDefaultBase = useIrBase.value === false; + return distance > maxDistance || !selectedItemIsDefault || !usingDefaultBase; +}); + watch(showSimulation, (showingSimulation) => { // if we are switching off the simulation while playing, pause it if (!showingSimulation && playing.value) { @@ -807,6 +856,9 @@ function goToGalleryItem(name: string, instant=false) { } function resetView() { + // showImageCard.value = false; + showSimulation.value = false; + useIrBase.value = false; const name = "Infrared Stars & Dust (JWST)"; const place = galleryPlaces.value.find(p => p.get_name() === name) || null; if (place === null) { @@ -833,6 +885,7 @@ watch(showImageCard, (showing) => { togglePlayPause(); } showSimulation.value = false; + useIrBase.value = false; showInfoSheet.value = false; galleryOpen.value = false; goToGalleryItem("Infrared Stars & Dust (JWST)"); @@ -895,6 +948,7 @@ function rollView(angleDegrees: number, zoomDeg: number | null = null) { } #app.app-is-small { + font-size: 0.9em; .v-application__wrap { flex-direction: column; // add for the side panel max-height: 100svh; // force the application to be 100% @@ -1042,14 +1096,18 @@ and remember, position:absolute is still a positioned parent, so children can be .top-buttons-row, .second-buttons-row { - display: flex; + display: grid; + grid-template-columns: auto 1fr auto; + column-gap: 1rem; flex-direction: row; - justify-content: space-between; + // justify-content: space-between; align-items: center; width: 100%; margin-bottom: 10px; } - +.second-buttons-row { + grid-template-columns: 40px 1fr 40px; +} #center-buttons { display: flex; flex-direction: row; @@ -1257,4 +1315,114 @@ and remember, position:absolute is still a positioned parent, so children can be margin-block: -100px; margin-inline: auto; } + +.base-switch-button { + justify-self: end; + display: flex; + flex-direction: column; + pointer-events: auto; +} + +.current-base-label { + font-size: 0.8em; +} + +select.base-switch-select { + appearance: auto; + font-size: 0.9em; + border: none; + padding-inline: 4px; + padding-block: 2px; + border: none; + width: fit-content; + position: relative; +} + +select.base-switch-select::before { + content: "Base Image: "; + font-size: 0.8em; + margin-right: 4px; +} +select.base-switch-select option { + background-color: black; + color: white; +} + +.base-switch-fieldset { + padding: 0; + margin: auto; + backdrop-filter: blur(6px); + border: 1px solid white; + border-radius: 4px; +} +.base-switch-fieldset legend { + font-size: 0.7em; + padding-inline: 0.3em; + margin-left: 0.5em; + text-align: left; +} + +/* Make v-select.v-select-base-switch resemble the plain select.base-switch-select: + smaller font, less padding, blurred translucent background, no chunky solo bg. */ +.v-select.v-select-base-switch { + font-size: 0.9em; + pointer-events: auto; + width: max-content; + + .v-field { + background: transparent; + border-radius: 4px; + backdrop-filter: blur(6px); + -webkit-backdrop-filter: blur(6px); + box-shadow: none; + min-height: 0; + padding-right:0; + } + + .v-field__overlay { + background: rgba(0, 0, 0, 0.25); + outline: 1px solid white; + } + + + + .v-field__input { + font-size: 0.9em; + padding-inline: 4px; + padding-bottom: 2px; + padding-top: 24px; + min-height: 0; + color: white; + + } + + .v-field-label.v-field-label--floating { + font-size: 0.9em; + color: white; + font-weight: bold; + // they apply a 0.7 apacity to this + top: 0; + margin-left:5px; + } + + .v-select__selection-text { + overflow: visible; + text-overflow: clip; + white-space: nowrap; + font-size: 1.1em; + } + + .v-field__append-inner { + padding-top: 0; + align-items: center; + } + +} + +.crawl-scroll-backdrop { + background-color: rgba(0, 0, 0, 0.65); + inset: 0; + position: absolute; + pointer-events: auto; +} diff --git a/src/components/DetailSummary.vue b/src/components/DetailSummary.vue index cb456c8..e2ab2b0 100644 --- a/src/components/DetailSummary.vue +++ b/src/components/DetailSummary.vue @@ -1,10 +1,11 @@ + + diff --git a/src/components/StarWarsCrawl.vue b/src/components/StarWarsCrawl.vue index 1b268b0..02f4864 100644 --- a/src/components/StarWarsCrawl.vue +++ b/src/components/StarWarsCrawl.vue @@ -175,7 +175,6 @@ Version: 1.0 font-family: "Droid Sans", arial, verdana, sans-serif; font-weight: 700; color: var(--sw-yellow); - background-color: rgba(0, 0, 0, 0.65); overflow: hidden; position: absolute; top: 0; diff --git a/src/composables/useLayerOrdering.ts b/src/composables/useLayerOrdering.ts new file mode 100644 index 0000000..54dbe0d --- /dev/null +++ b/src/composables/useLayerOrdering.ts @@ -0,0 +1,86 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +import { engineStore } from '@wwtelescope/engine-pinia'; +import { WWTControl, LayerManager, Place, Layer, ImageSetLayer } from '@wwtelescope/engine'; + +export function useLayerOrdering() { + const store = engineStore(); + + + function getImageSetLayerOrder(id) { + const layer = LayerManager.get_layerList()[id]; + const layers = LayerManager.get_allMaps()[layer.get_referenceFrame()].layers; + const order = layers.indexOf(layer); + return order > -1 ? order : null; + } + + function setImagesetLayerOrder(id, order) { + store.setImageSetLayerOrder({ + id: id, + order: order + }); + } + + function getPlaceImageSetLayer(place: Place) { + const imgset = place.get_backgroundImageset() ?? place.get_studyImageset(); + if (!imgset) { + return null; + } + for (const layerState of store.activeImagesetLayerStates) { + const iset = store.imagesetForLayer(layerState.getGuid()); // just returns the Imageset not the layer + if (iset && iset.get_imageSetID() === imgset.get_imageSetID()) { + return store.imagesetLayerById(layerState.getGuid()); // returns plain, non-reactive ImageSetLayer + } + } + return null; + } + + function getPlaceImagesetOrder(place: Place) { + const layer = getPlaceImageSetLayer(place); + if (!layer) { + return null; + } + return getImageSetLayerOrder(layer.id.toString()); + } + + function setPlaceImagesetOrder(place: Place, order: number) { + const layer = getPlaceImageSetLayer(place); + if (!layer) { + return; + } + setImagesetLayerOrder(layer.id.toString(), order); + } + + + function setOrderForLayers(layers: ImageSetLayer[]) { + const currentOrders = layers.map(layer => getImageSetLayerOrder(layer.id.toString())); + let min = currentOrders.reduce((pre, curr, index) => { + return Math.min(pre ?? Infinity, curr ?? Infinity); + }, Infinity); + min = min ?? 0; + const newOrder = Array(layers.length).fill(0).map((_, index) => min + index); + layers.forEach((layer, index) => setImagesetLayerOrder(layer.id.toString(), newOrder[index])); + } + + function getOrderForLayers(layers: ImageSetLayer[]) { + return layers.map(layer => getImageSetLayerOrder(layer.id.toString())); + } + + function getOrderForPlaces(places: Place[]) { + const layers = places.map(place => getPlaceImageSetLayer(place)).filter((layer) => layer !== null); + return getOrderForLayers(layers); + } + + function setOrderForPlaces(places: Place[]) { + const layers = places.map(place => getPlaceImageSetLayer(place)).filter((layer) => layer !== null); + setOrderForLayers(layers); + } + + return { + setOrderForLayers, + setPlaceImagesetOrder, + getPlaceImageSetLayer, + setOrderForPlaces, + getOrderForLayers, + getOrderForPlaces, + }; +}; \ No newline at end of file diff --git a/src/styles/main.css b/src/styles/main.css index 5338fc1..4d8204c 100644 --- a/src/styles/main.css +++ b/src/styles/main.css @@ -124,3 +124,15 @@ fieldset > legend, fieldset label { user-select: none; } + + +.justify-self-start { + justify-self: start; +} + +.justify-self-end { + justify-self: end; +} +.justify-self-center { + justify-self: center; +} \ No newline at end of file