From bd27a7e09e3fdc22f9997998a1b6a1adf42c216b Mon Sep 17 00:00:00 2001 From: Jeran Date: Tue, 14 Jul 2026 15:49:50 +0200 Subject: [PATCH 01/14] First stash --- src/GlobalStates/GlobalStore.ts | 2 + src/components/plots/DataCube.tsx | 8 +- src/components/textures/ProjectionTexture.ts | 80 ++++++++++++++++++++ src/hooks/useDataFetcher.tsx | 2 + 4 files changed, 89 insertions(+), 3 deletions(-) create mode 100644 src/components/textures/ProjectionTexture.ts diff --git a/src/GlobalStates/GlobalStore.ts b/src/GlobalStates/GlobalStore.ts index bea8ab47..46a53f5c 100644 --- a/src/GlobalStates/GlobalStore.ts +++ b/src/GlobalStates/GlobalStore.ts @@ -20,6 +20,7 @@ type StoreState = { shape: THREE.Vector3; valueScales: { maxVal: number; minVal: number }; colormap: THREE.DataTexture; + remapTexture: THREE.DataTexture | undefined; timeSeries: Record>; strides: number[]; metadata: Record | null; @@ -89,6 +90,7 @@ export const useGlobalStore = create((set, get) => ({ shape: new THREE.Vector3(2, 2, 2), valueScales: { maxVal: 1, minVal: -1 }, colormap: GetColorMapTexture(), + remapTexture: undefined, timeSeries: {}, strides: [10368,144,1], metadata: null, diff --git a/src/components/plots/DataCube.tsx b/src/components/plots/DataCube.tsx index bc97a87a..fa543867 100644 --- a/src/components/plots/DataCube.tsx +++ b/src/components/plots/DataCube.tsx @@ -15,11 +15,12 @@ interface DataCubeProps { } export const DataCube = ({ volTexture }: DataCubeProps ) => { - const {shape, colormap, flipY, textureArrayDepths} = useGlobalStore(useShallow(state=>({ + const {shape, colormap, flipY, textureArrayDepths, remapTexture} = useGlobalStore(useShallow(state=>({ shape:state.shape, colormap:state.colormap, flipY:state.flipY, - textureArrayDepths: state.textureArrayDepths + textureArrayDepths: state.textureArrayDepths, + remapTexture: state.remapTexture }))) //We have to useShallow when returning an object instead of a state. I don't fully know the logic yet const { valueRange, xRange, yRange, zRange, quality, useOrtho, @@ -47,7 +48,8 @@ export const DataCube = ({ volTexture }: DataCubeProps ) => { glslVersion: THREE.GLSL3, uniforms: { modelViewMatrixInverse: { value: new THREE.Matrix4() }, // Used for Orthographic RayMarcher - map: { value: Array.from({ length: 14 }, (_, idx) => volTexture?.[idx])}, + map: { value: volTexture}, + remap: { value: remapTexture}, maskTexture: { value: maskTexture }, maskValue: {value: maskValue }, textureDepths: {value: new THREE.Vector3(textureArrayDepths[2], textureArrayDepths[1], textureArrayDepths[0])}, diff --git a/src/components/textures/ProjectionTexture.ts b/src/components/textures/ProjectionTexture.ts new file mode 100644 index 00000000..15f014ba --- /dev/null +++ b/src/components/textures/ProjectionTexture.ts @@ -0,0 +1,80 @@ +import { useGlobalStore } from '@/GlobalStates/GlobalStore'; +import * as THREE from 'three'; + +function normalizeArray(array: Array){ + const len = array.length; + let min = Infinity; + let max = -Infinity; + for (let i = 0; i < len; i++){ + const v = array[i]; + if (v < min) min = v; + if (v > max) max = v; + } + const range = max - min; + const scaler = 1/range; + const out = new Float32Array(len); + for (let i = 0; i < array.length; i++){ + out[i] = (array[i]-min)* scaler; + } + return out; +} + +function isUniformStep(array: Array): boolean { + const len = array.length; + if (len < 3) return true; // any 0–2 element array trivially qualifies + + const step = array[1] - array[0]; + + for (let i = 2; i < len; i++) { + if (array[i] - array[i - 1] !== step) { + return false; + } + } + return true; +} + +function createRemapTexture(xArray: Array, yArray: Array) { + const width = xArray.length; + const height = yArray.length; + + const normX = normalizeArray(xArray); + const normY = normalizeArray(yArray); + + const data = new Float32Array(width * height * 2); + let ptr = 0; + for (let j = 0; j < height; j++) { + const y = normY[j]; + for (let i = 0; i < width; i++) { + const x = normX[i]; + data[ptr++] = x; + data[ptr++] = y; + } + } + + const texture = new THREE.DataTexture( + data, + width, + height, + THREE.RGFormat, + THREE.FloatType + ); + + texture.needsUpdate = true; + texture.magFilter = THREE.LinearFilter; + texture.minFilter = THREE.LinearFilter; + texture.wrapS = THREE.ClampToEdgeWrapping; + texture.wrapT = THREE.ClampToEdgeWrapping; + + return texture; +} + +export function SetReprojectionTexture(dimArrays: Array[]){ + const dimCount = dimArrays.length; + const xArray = dimArrays[dimCount-1]; + const yArray = dimArrays[dimCount-2]; + const isRegular = isUniformStep(xArray) && isUniformStep(yArray) + if (isRegular) return; + + const remapTexture = createRemapTexture(xArray, yArray); + useGlobalStore.setState({remapTexture}); +} \ No newline at end of file diff --git a/src/hooks/useDataFetcher.tsx b/src/hooks/useDataFetcher.tsx index 850a6110..e53f67a0 100644 --- a/src/hooks/useDataFetcher.tsx +++ b/src/hooks/useDataFetcher.tsx @@ -8,6 +8,7 @@ import { ParseExtent, GetDimInfo } from '@/utils/HelperFuncs'; import { GetAttributes } from '@/components/zarr/ZarrLoaderLRU'; import { GetArray } from '@/components/zarr/GetArray'; import { ArrayToTexture } from '@/components/textures'; +import { SetReprojectionTexture } from '@/components/textures/ProjectionTexture'; export const useDataFetcher = () => { const { @@ -134,6 +135,7 @@ export const useDataFetcher = () => { setDimUnits(dimUnits); ParseExtent(dimUnits, dimArrays); + SetReprojectionTexture(dimArrays); }); } else { From 7f000d767e43a60509b3b5d0846d567aba526960 Mon Sep 17 00:00:00 2001 From: Jeran Date: Tue, 14 Jul 2026 17:48:04 +0200 Subject: [PATCH 02/14] Working in Flat and Volume at the moment --- package.json | 1 + src/GlobalStates/PlotStore.ts | 4 ++ src/components/plots/DataCube.tsx | 6 +- src/components/plots/FlatMap.tsx | 16 +++-- src/components/textures/ProjectionTexture.ts | 71 +++++++++++++++++++ src/components/textures/shaders/flatFrag.glsl | 12 ++-- .../textures/shaders/volFragment.glsl | 11 +-- src/components/ui/MainPanel/AdjustPlot.tsx | 30 +++++++- 8 files changed, 132 insertions(+), 19 deletions(-) diff --git a/package.json b/package.json index 565e69fd..0012a882 100644 --- a/package.json +++ b/package.json @@ -84,6 +84,7 @@ "lucide-react": "^0.562.0", "next": "^16.2.7", "next-themes": "^0.4.6", + "proj4": "^2.20.9", "radix-ui": "1.4.3", "react": "^19.0.0", "react-day-picker": "^9.14.0", diff --git a/src/GlobalStates/PlotStore.ts b/src/GlobalStates/PlotStore.ts index 39219a62..310fdac0 100644 --- a/src/GlobalStates/PlotStore.ts +++ b/src/GlobalStates/PlotStore.ts @@ -65,6 +65,8 @@ type PlotState ={ cameraPosition: THREE.Vector3; disablePointScale: boolean; camera: THREE.Camera | undefined; + defaultProjection: string; + projection: string; setQuality: (quality: number) => void; setTimeScale: (timeScale : number) =>void; @@ -189,6 +191,8 @@ export const usePlotStore = create((set, get) => ({ cameraPosition: new THREE.Vector3(0, 0, 5), disablePointScale: false, camera: undefined, + defaultProjection: "EPSG:4326", + projection: "EPSG:4326", setVTransferRange: (vTransferRange) => set({ vTransferRange }), setVTransferScale: (vTransferScale) => set({ vTransferScale }), diff --git a/src/components/plots/DataCube.tsx b/src/components/plots/DataCube.tsx index fa543867..9790bb12 100644 --- a/src/components/plots/DataCube.tsx +++ b/src/components/plots/DataCube.tsx @@ -48,12 +48,12 @@ export const DataCube = ({ volTexture }: DataCubeProps ) => { glslVersion: THREE.GLSL3, uniforms: { modelViewMatrixInverse: { value: new THREE.Matrix4() }, // Used for Orthographic RayMarcher - map: { value: volTexture}, - remap: { value: remapTexture}, + map: { value: Array.from({ length: 11 }, (_, idx) => volTexture?.[idx] ?? volTexture?.[0])}, maskTexture: { value: maskTexture }, maskValue: {value: maskValue }, textureDepths: {value: new THREE.Vector3(textureArrayDepths[2], textureArrayDepths[1], textureArrayDepths[0])}, cmap:{value: colormap}, + remapTexture: { value: remapTexture}, cOffset:{value: cOffset}, cScale: {value: cScale}, threshold: {value: new THREE.Vector2(valueRange[0],valueRange[1])}, @@ -77,7 +77,7 @@ export const DataCube = ({ volTexture }: DataCubeProps ) => { blending: THREE.NormalBlending, depthWrite: false, side: useOrtho ? THREE.FrontSide : THREE.BackSide, - }),[useFragOpt, useOrtho, volTexture]); + }),[useFragOpt, useOrtho, volTexture, remapTexture]); const geometry = useMemo(() => new THREE.BoxGeometry(shape.x, shape.y, shape.z), [shape]); useEffect(() => { diff --git a/src/components/plots/FlatMap.tsx b/src/components/plots/FlatMap.tsx index 9d86eca7..2e01d1df 100644 --- a/src/components/plots/FlatMap.tsx +++ b/src/components/plots/FlatMap.tsx @@ -12,7 +12,7 @@ import { ThreeEvent } from '@react-three/fiber'; import { coarsenFlatArray, GetCurrentArray, GetTimeSeries, parseUVCoords, deg2rad } from '@/utils/HelperFuncs'; import { evaluate_cmap } from 'js-colormaps-es'; import { useCoordBounds } from '@/hooks/useCoordBounds'; -import { GetFrag } from '../textures'; +import { flatFrag } from '../textures/shaders'; import { SquareMeshes } from './TransectMeshes'; interface InfoSettersProps{ setLoc: React.Dispatch>; @@ -24,13 +24,13 @@ interface InfoSettersProps{ const FlatMap = ({textures, infoSetters} : {textures : THREE.DataTexture[] | THREE.Data3DTexture[], infoSetters : InfoSettersProps}) => { const {setLoc, setShowInfo, val, coords} = infoSetters; const {flipY, colormap, dimArrays, dimNames, dimUnits, - isFlat, dataShape, textureArrayDepths, strides, + isFlat, dataShape, textureArrayDepths, strides, remapTexture, setPlotDim,updateDimCoords, updateTimeSeries} = useGlobalStore(useShallow(state => ({ flipY: state.flipY, colormap: state.colormap, dimArrays: state.dimArrays, strides: state.strides, dimNames:state.dimNames, dimUnits: state.dimUnits, isFlat: state.isFlat, dataShape: state.dataShape, - textureArrayDepths: state.textureArrayDepths, + textureArrayDepths: state.textureArrayDepths,remapTexture:state.remapTexture, setPlotDim:state.setPlotDim, updateDimCoords:state.updateDimCoords, updateTimeSeries: state.updateTimeSeries @@ -168,7 +168,8 @@ const FlatMap = ({textures, infoSetters} : {textures : THREE.DataTexture[] | THR uniforms:{ cScale: {value: cScale}, cOffset: {value: cOffset}, - map : {value: Array.from({ length: 14 }, (_, idx) => textures?.[idx])}, + map : {value: Array.from({ length: 12 }, (_, idx) => textures?.[idx])}, + remapTexture: { value: remapTexture}, maskTexture: {value: maskTexture}, maskValue: {value: maskValue}, threshold: {value: new THREE.Vector2(valueRange[0],valueRange[1])}, @@ -181,10 +182,13 @@ const FlatMap = ({textures, infoSetters} : {textures : THREE.DataTexture[] | THR nanAlpha: {value: 1 - nanTransparency}, fillValue: {value: fillValue?? NaN}, }, + defines:{ + IS_FLAT: isFlat + }, vertexShader: vertShader, - fragmentShader: GetFrag("flatFrag", isFlat), + fragmentShader: flatFrag, side: THREE.DoubleSide, - }),[isFlat, textures]) + }),[isFlat, remapTexture, textures]) useEffect(()=>{ if(shaderMaterial){ diff --git a/src/components/textures/ProjectionTexture.ts b/src/components/textures/ProjectionTexture.ts index 15f014ba..452fd3d5 100644 --- a/src/components/textures/ProjectionTexture.ts +++ b/src/components/textures/ProjectionTexture.ts @@ -1,6 +1,9 @@ import { useGlobalStore } from '@/GlobalStates/GlobalStore'; +import { usePlotStore } from '@/GlobalStates/PlotStore'; import * as THREE from 'three'; +import proj4 from 'proj4'; + function normalizeArray(array: Array){ const len = array.length; let min = Infinity; @@ -77,4 +80,72 @@ export function SetReprojectionTexture(dimArrays: Array[]){ const remapTexture = createRemapTexture(xArray, yArray); useGlobalStore.setState({remapTexture}); +} + +export function reproject(){ + const {defaultProjection, projection} = usePlotStore.getState() + const {dimArrays, remapTexture } = useGlobalStore.getState() + if (remapTexture) remapTexture.dispose(); + const dimCount = dimArrays.length; + const xArray = dimArrays[dimCount-1]; + const yArray = dimArrays[dimCount-2]; + + const width = xArray.length; + const height = yArray.length; + + const coords = []; + for (let j = 0; j < height; j++) { + const y = yArray[j]; + for (let i = 0; i < width; i++) { + const x = xArray[i]; + coords.push([x,y]) + } + } + + const proj = proj4(defaultProjection, projection); + const floatData = new Float64Array(width * height * 2); + const data = new Uint16Array(width * height * 2); + let [xMin, xMax] = [Infinity, -Infinity]; + let [yMin, yMax] = [Infinity, -Infinity]; + + let ptr = 0; + for (let j = 0; j < height; j++) { + const y = yArray[j]; + for (let i = 0; i < width; i++) { + const [px, py] = proj.forward([xArray[i], y]); + if (py > yMax) yMax = py; + if (py < yMin) yMin = py; + if (px > xMax) xMax = px; + if (px < xMin) xMin = px; + floatData[ptr++] = px; + floatData[ptr++] = py; + } + } + const xRange = xMax - xMin; + const xScaler = 1/xRange; + const yRange = yMax - yMin; + const yScaler = 1/yRange; + + for (let i = 0; i < width * height; i++){ + const idx = i * 2; + const nx = (floatData[idx] - xMin) * xScaler; // normalized to [0, 1] + const ny = (floatData[idx + 1] - yMin) * yScaler; // normalized to [0, 1] + data[idx] = THREE.DataUtils.toHalfFloat(nx); + data[idx + 1] = THREE.DataUtils.toHalfFloat(ny); + } + + const texture = new THREE.DataTexture( + data, + width, + height, + THREE.RGFormat, + THREE.HalfFloatType + ); + texture.needsUpdate = true; + texture.magFilter = THREE.LinearFilter; + texture.minFilter = THREE.LinearFilter; + texture.wrapS = THREE.ClampToEdgeWrapping; + texture.wrapT = THREE.ClampToEdgeWrapping; + + useGlobalStore.setState({remapTexture: texture}) } \ No newline at end of file diff --git a/src/components/textures/shaders/flatFrag.glsl b/src/components/textures/shaders/flatFrag.glsl index bab39875..3a783e36 100644 --- a/src/components/textures/shaders/flatFrag.glsl +++ b/src/components/textures/shaders/flatFrag.glsl @@ -1,12 +1,13 @@ //This is for Flat Textures but with 3D textures to sample from i,e; animation #ifdef IS_FLAT - uniform sampler2D map[14]; + uniform sampler2D map[12]; #else - uniform sampler3D map[14]; + uniform sampler3D map[12]; #endif uniform sampler2D maskTexture; uniform sampler2D cmap; +uniform sampler2D remapTexture; uniform vec3 textureDepths; @@ -58,8 +59,8 @@ float sample1( else if (index == 9) return texture(map[9], p).r; else if (index == 10) return texture(map[10], p).r; else if (index == 11) return texture(map[11], p).r; - else if (index == 12) return texture(map[12], p).r; - else if (index == 13) return texture(map[13], p).r; + // else if (index == 12) return texture(map[12], p).r; + // else if (index == 13) return texture(map[13], p).r; else return 0.0; } @@ -78,12 +79,14 @@ void main() { int yStepSize = int(textureDepths.x); #ifdef IS_FLAT vec2 texCoord = vUv; + texCoord.xy = texture(remapTexture,texCoord.xy).rg; texCoord.xy = clamp(texCoord.xy, vec2(0.0), 1. - vec2(epsilon)); // This prevent the very edges from looping around and causing line artifacts ivec2 idx = clamp(ivec2(texCoord * textureDepths.xy), ivec2(0), ivec2(textureDepths.xy) - 1); int textureIdx = idx.y * yStepSize + idx.x; vec2 localCoord = texCoord * (textureDepths.xy); // Scale up #else vec3 texCoord = vec3(vUv, animateProg); + texCoord.xy = texture(remapTexture,texCoord.xy).rg; texCoord.xy = clamp(texCoord.xy, vec2(0.0), 1. - vec2(epsilon)); // This prevent the very edges from looping around and causing line artifacts ivec3 idx = clamp(ivec3(texCoord * textureDepths), ivec3(0), ivec3(textureDepths) - 1); int textureIdx = idx.z * zStepSize + idx.y * yStepSize + idx.x; @@ -101,4 +104,5 @@ void main() { float sampLoc = isNaN ? strength: (strength)*cScale; sampLoc = isNaN ? strength : min(sampLoc+cOffset,0.995); Color = isNaN ? vec4(nanColor, nanAlpha) : vec4(texture2D(cmap, vec2(sampLoc, 0.5)).rgb, 1.); + // Color = vec4(texture(remapTexture,texCoord.xy).rg, 0. , 1.); } \ No newline at end of file diff --git a/src/components/textures/shaders/volFragment.glsl b/src/components/textures/shaders/volFragment.glsl index fa128c0d..c4fab8cb 100644 --- a/src/components/textures/shaders/volFragment.glsl +++ b/src/components/textures/shaders/volFragment.glsl @@ -7,9 +7,10 @@ in vec3 vDirection; out vec4 color; -uniform sampler3D map[14]; // We are limited to 16 textures. Cmap counts as one. 15 is weird so we use 14. +uniform sampler3D map[11]; // We are limited to 16 textures. Cmap counts as one. 15 is weird so we use 14. uniform sampler2D maskTexture; uniform sampler2D cmap; +uniform sampler2D remapTexture; uniform vec3 textureDepths; uniform float cOffset; @@ -73,9 +74,8 @@ float sample1(vec3 p, int index) { // Shader doesn't support dynamic indexing so else if (index == 8) return texture(map[8], p).r; else if (index == 9) return texture(map[9], p).r; else if (index == 10) return texture(map[10], p).r; - else if (index == 11) return texture(map[11], p).r; - else if (index == 12) return texture(map[12], p).r; - else if (index == 13) return texture(map[13], p).r; + // else if (index == 11) return texture(map[11], p).r; + // else if (index == 12) return texture(map[12], p).r; else return 0.0; } @@ -112,6 +112,9 @@ void main() { continue; } vec3 texCoord = p / scale + 0.5; + vec2 newV = texCoord.xy; + vec2 repTex = texture2D(remapTexture, newV).rg; + texCoord.xy = repTex; if (maskValue != 0){ vec2 newV = texCoord.xy; diff --git a/src/components/ui/MainPanel/AdjustPlot.tsx b/src/components/ui/MainPanel/AdjustPlot.tsx index 4233513e..9d4b085d 100644 --- a/src/components/ui/MainPanel/AdjustPlot.tsx +++ b/src/components/ui/MainPanel/AdjustPlot.tsx @@ -20,6 +20,7 @@ import { BsFillQuestionCircleFill } from "react-icons/bs"; import { ChevronDown } from 'lucide-react'; import {Select, SelectTrigger, SelectContent, SelectItem, SelectValue} from '@/components/ui' import { RiCloseLargeLine } from "react-icons/ri"; +import { reproject } from '@/components/textures/ProjectionTexture'; function DeNorm(val : number, min : number, max : number){ const range = max-min; @@ -501,12 +502,12 @@ const SpatialExtent = () =>{ } const GlobalOptions = () =>{ - const {valueRange, showBorders, borderColor, nanColor, nanTransparency, plotType, interpPixels, fillValue, useBorderTexture, + const {valueRange, showBorders, borderColor, nanColor, nanTransparency, plotType, interpPixels, fillValue, useBorderTexture, defaultProjection, setValueRange, setShowBorders, setBorderColor, setNanColor, setNanTransparency, setInterpPixels, setFillValue} = usePlotStore(useShallow(state => ({ showBorders: state.showBorders, borderColor: state.borderColor, nanColor: state.nanColor, nanTransparency: state.nanTransparency, plotType: state.plotType, interpPixels: state.interpPixels, - fillValue: state.fillValue, useBorderTexture:state.useBorderTexture, + fillValue: state.fillValue, useBorderTexture:state.useBorderTexture, defaultProjection: state.defaultProjection, valueRange: state.valueRange, setValueRange: state.setValueRange, setShowBorders: state.setShowBorders, setBorderColor: state.setBorderColor, setNanColor: state.setNanColor, setNanTransparency: state.setNanTransparency, @@ -521,8 +522,10 @@ const GlobalOptions = () =>{ }))) const [thisFillVal, setThisFillValue] = useState(denormalize(fillValue, valueScales.minVal, valueScales.maxVal)) const [showMasks, setShowMasks] = useState(false) + const [showRepro, setShowRepro] = useState(false) const masks = ["None", "Land", "Water"] const isPC = plotType == 'point-cloud' + return (
@@ -600,6 +603,29 @@ const GlobalOptions = () =>{
+ + + usePlotStore.setState({projection:e.target.value})} + /> + + {!(analysisMode && axis != 0) && // Hide if Analysismode and Axis != 0 <> From 8bd7e72355630c80900ab016495a8a4d234d31a8 Mon Sep 17 00:00:00 2001 From: Jeran Date: Wed, 15 Jul 2026 14:37:27 +0200 Subject: [PATCH 03/14] Correct Reprojection. Need Nan info --- src/GlobalStates/PlotStore.ts | 8 +- src/app/globals.css | 10 ++ src/components/plots/DataCube.tsx | 4 + src/components/plots/FlatMap.tsx | 3 +- src/components/textures/ProjectionTexture.ts | 102 +++++++++++------- src/components/textures/shaders/flatFrag.glsl | 22 +++- .../textures/shaders/volFragment.glsl | 12 ++- src/components/ui/Elements/Reprojection.tsx | 88 +++++++++++++++ src/components/ui/MainPanel/AdjustPlot.tsx | 27 +---- 9 files changed, 202 insertions(+), 74 deletions(-) create mode 100644 src/components/ui/Elements/Reprojection.tsx diff --git a/src/GlobalStates/PlotStore.ts b/src/GlobalStates/PlotStore.ts index 310fdac0..66cabea7 100644 --- a/src/GlobalStates/PlotStore.ts +++ b/src/GlobalStates/PlotStore.ts @@ -65,8 +65,8 @@ type PlotState ={ cameraPosition: THREE.Vector3; disablePointScale: boolean; camera: THREE.Camera | undefined; - defaultProjection: string; - projection: string; + defaultProjection: string | undefined; + projection: string | undefined; setQuality: (quality: number) => void; setTimeScale: (timeScale : number) =>void; @@ -191,8 +191,8 @@ export const usePlotStore = create((set, get) => ({ cameraPosition: new THREE.Vector3(0, 0, 5), disablePointScale: false, camera: undefined, - defaultProjection: "EPSG:4326", - projection: "EPSG:4326", + defaultProjection: undefined, + projection: undefined, setVTransferRange: (vTransferRange) => set({ vTransferRange }), setVTransferScale: (vTransferScale) => set({ vTransferScale }), diff --git a/src/app/globals.css b/src/app/globals.css index 1c4604b8..f0b90a9e 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -162,6 +162,16 @@ .no-spinner { -moz-appearance: textfield; /* Firefox */ } + + .no-scrollbar { + -ms-overflow-style: none; /* IE + Edge */ + scrollbar-width: none; /* Firefox */ + } + + .no-scrollbar::-webkit-scrollbar { + display: none; /* Chrome, Safari */ + } + } /* Height fix for mobile browser UI */ diff --git a/src/components/plots/DataCube.tsx b/src/components/plots/DataCube.tsx index 9790bb12..1c7b283d 100644 --- a/src/components/plots/DataCube.tsx +++ b/src/components/plots/DataCube.tsx @@ -71,6 +71,9 @@ export const DataCube = ({ volTexture }: DataCubeProps ) => { nanColor: {value: new THREE.Color(nanColor)}, fillValue: {value: fillValue?? NaN} }, + defines:{ + REPROJECT: remapTexture ? true : false + }, vertexShader: useOrtho ? orthoVertex : vertexShader, fragmentShader: useFragOpt ? fragOpt : fragmentShader, transparent: true, @@ -79,6 +82,7 @@ export const DataCube = ({ volTexture }: DataCubeProps ) => { side: useOrtho ? THREE.FrontSide : THREE.BackSide, }),[useFragOpt, useOrtho, volTexture, remapTexture]); + const geometry = useMemo(() => new THREE.BoxGeometry(shape.x, shape.y, shape.z), [shape]); useEffect(() => { if (shaderMaterial) { diff --git a/src/components/plots/FlatMap.tsx b/src/components/plots/FlatMap.tsx index 2e01d1df..7d7fbb16 100644 --- a/src/components/plots/FlatMap.tsx +++ b/src/components/plots/FlatMap.tsx @@ -183,7 +183,8 @@ const FlatMap = ({textures, infoSetters} : {textures : THREE.DataTexture[] | THR fillValue: {value: fillValue?? NaN}, }, defines:{ - IS_FLAT: isFlat + IS_FLAT: isFlat, + REPROJECT: remapTexture ? true: false }, vertexShader: vertShader, fragmentShader: flatFrag, diff --git a/src/components/textures/ProjectionTexture.ts b/src/components/textures/ProjectionTexture.ts index 452fd3d5..f4602684 100644 --- a/src/components/textures/ProjectionTexture.ts +++ b/src/components/textures/ProjectionTexture.ts @@ -1,5 +1,6 @@ import { useGlobalStore } from '@/GlobalStates/GlobalStore'; import { usePlotStore } from '@/GlobalStates/PlotStore'; +import { ArrayMinMax, linspace } from '@/utils/HelperFuncs'; import * as THREE from 'three'; import proj4 from 'proj4'; @@ -82,62 +83,83 @@ export function SetReprojectionTexture(dimArrays: Array[]){ useGlobalStore.setState({remapTexture}); } -export function reproject(){ +export function reproject(resolution: number = 256){ const {defaultProjection, projection} = usePlotStore.getState() + if (!defaultProjection || !projection) return; // This shouldn't trigger as the button will be disabled for this same condition const {dimArrays, remapTexture } = useGlobalStore.getState() if (remapTexture) remapTexture.dispose(); const dimCount = dimArrays.length; + const xArray = dimArrays[dimCount-1]; const yArray = dimArrays[dimCount-2]; - const width = xArray.length; const height = yArray.length; - const coords = []; + const [xMin, xMax] = ArrayMinMax(xArray); + const [yMin, yMax] = ArrayMinMax(yArray); + // We need the border points as the min/max of the old CRS won't always be the min/max of the new CRS + const boundaryPoints: [number, number][] = []; + console.log(xMin, xMax, yMin, yMax) + // top edge: j = 0, all i + for (let i = 0; i < width; i++) { + boundaryPoints.push([xArray[i], yArray[0]]); + } + // bottom edge: j = height - 1, all i + for (let i = 0; i < width; i++) { + boundaryPoints.push([xArray[i], yArray[height - 1]]); + } + // left edge: i = 0, all j for (let j = 0; j < height; j++) { - const y = yArray[j]; - for (let i = 0; i < width; i++) { - const x = xArray[i]; - coords.push([x,y]) - } + boundaryPoints.push([xArray[0], yArray[j]]); } - - const proj = proj4(defaultProjection, projection); - const floatData = new Float64Array(width * height * 2); - const data = new Uint16Array(width * height * 2); - let [xMin, xMax] = [Infinity, -Infinity]; - let [yMin, yMax] = [Infinity, -Infinity]; - - let ptr = 0; + // right edge: i = width - 1, all j for (let j = 0; j < height; j++) { - const y = yArray[j]; - for (let i = 0; i < width; i++) { - const [px, py] = proj.forward([xArray[i], y]); - if (py > yMax) yMax = py; - if (py < yMin) yMin = py; - if (px > xMax) xMax = px; - if (px < xMin) xMin = px; - floatData[ptr++] = px; - floatData[ptr++] = py; - } + boundaryPoints.push([xArray[width - 1], yArray[j]]); } - const xRange = xMax - xMin; - const xScaler = 1/xRange; - const yRange = yMax - yMin; - const yScaler = 1/yRange; - - for (let i = 0; i < width * height; i++){ - const idx = i * 2; - const nx = (floatData[idx] - xMin) * xScaler; // normalized to [0, 1] - const ny = (floatData[idx + 1] - yMin) * yScaler; // normalized to [0, 1] - data[idx] = THREE.DataUtils.toHalfFloat(nx); - data[idx + 1] = THREE.DataUtils.toHalfFloat(ny); + + const proj = proj4(defaultProjection, projection); + let [minX, minY] = [Infinity, Infinity]; + let [maxX, maxY] = [-Infinity, -Infinity]; + + // Get min/max of new CRS for new Axis' + for (const [lon, lat] of boundaryPoints) { + const [px, py] = proj.forward([lon, lat]); + minX = Math.min(minX, px); maxX = Math.max(maxX, px); + minY = Math.min(minY, py); maxY = Math.max(maxY, py); + } + + // ---- Get Estimate of aspectRatio ---- // + const midX = Math.floor(width / 2); + const midY = Math.floor(height / 2); + const [x0, y0] = proj.forward([xArray[midX], yArray[midY]]); + const [x1, y1] = proj.forward([xArray[midX + 1], yArray[midY + 1]]); + const xSize = Math.abs(x1 - x0); + const ySize = Math.abs(y1 - y0); + const aspectRatio = xSize/ySize; + + // ---- Construct new CRS axis' ----// + const targetWidth = Math.ceil(resolution*aspectRatio); + const targetHeight = resolution; + const xTicks = linspace(minX, maxX, targetWidth); + const yTicks = linspace(minY, maxY, targetHeight); + const data = new Uint16Array(targetWidth * targetHeight * 2); + for (let j = 0; j < targetHeight; j++) { + for (let i = 0; i < targetWidth; i++) { + const [lon, lat] = proj.inverse([xTicks[i], yTicks[j]]); + const u = (lon - xMin) / (xMax - xMin); + const v = (lat - yMin) / (yMax - yMin); + const idx = (j * targetWidth + i) * 2; + const valid = u >= 0 && u <= 1 && v >= 0 && v <= 1 && isFinite(lon) && isFinite(lat); + data[idx] = THREE.DataUtils.toHalfFloat(valid ? u : -1); + data[idx + 1] = THREE.DataUtils.toHalfFloat(valid ? v : -1); + } } - + const sanity = proj4(projection, defaultProjection, [-17912187.334735576, -8989370.410319714]) + console.log(sanity) const texture = new THREE.DataTexture( data, - width, - height, + targetWidth, + targetHeight, THREE.RGFormat, THREE.HalfFloatType ); diff --git a/src/components/textures/shaders/flatFrag.glsl b/src/components/textures/shaders/flatFrag.glsl index 3a783e36..87842a02 100644 --- a/src/components/textures/shaders/flatFrag.glsl +++ b/src/components/textures/shaders/flatFrag.glsl @@ -79,14 +79,30 @@ void main() { int yStepSize = int(textureDepths.x); #ifdef IS_FLAT vec2 texCoord = vUv; - texCoord.xy = texture(remapTexture,texCoord.xy).rg; + #ifdef REPROJECT + texCoord.xy = texture(remapTexture,texCoord.xy).rg; + if ( + texCoord.x > 1. || + texCoord.x < 0. || + texCoord.y > 1. || + texCoord.y < 0. + ) discard; + #endif texCoord.xy = clamp(texCoord.xy, vec2(0.0), 1. - vec2(epsilon)); // This prevent the very edges from looping around and causing line artifacts ivec2 idx = clamp(ivec2(texCoord * textureDepths.xy), ivec2(0), ivec2(textureDepths.xy) - 1); int textureIdx = idx.y * yStepSize + idx.x; vec2 localCoord = texCoord * (textureDepths.xy); // Scale up #else vec3 texCoord = vec3(vUv, animateProg); - texCoord.xy = texture(remapTexture,texCoord.xy).rg; + #ifdef REPROJECT + texCoord.xy = texture(remapTexture,texCoord.xy).rg; + if ( + texCoord.x >= 1. || + texCoord.x <= 0. || + texCoord.y >= 1. || + texCoord.y <= 0. + ) discard; + #endif texCoord.xy = clamp(texCoord.xy, vec2(0.0), 1. - vec2(epsilon)); // This prevent the very edges from looping around and causing line artifacts ivec3 idx = clamp(ivec3(texCoord * textureDepths), ivec3(0), ivec3(textureDepths) - 1); int textureIdx = idx.z * zStepSize + idx.y * yStepSize + idx.x; @@ -104,5 +120,7 @@ void main() { float sampLoc = isNaN ? strength: (strength)*cScale; sampLoc = isNaN ? strength : min(sampLoc+cOffset,0.995); Color = isNaN ? vec4(nanColor, nanAlpha) : vec4(texture2D(cmap, vec2(sampLoc, 0.5)).rgb, 1.); + // float check = float(texture(remapTexture,texCoord.xy).g >= 0.); + // Color = vec4(check, 0., 0. , 1.); // Color = vec4(texture(remapTexture,texCoord.xy).rg, 0. , 1.); } \ No newline at end of file diff --git a/src/components/textures/shaders/volFragment.glsl b/src/components/textures/shaders/volFragment.glsl index c4fab8cb..8091be0f 100644 --- a/src/components/textures/shaders/volFragment.glsl +++ b/src/components/textures/shaders/volFragment.glsl @@ -112,9 +112,15 @@ void main() { continue; } vec3 texCoord = p / scale + 0.5; - vec2 newV = texCoord.xy; - vec2 repTex = texture2D(remapTexture, newV).rg; - texCoord.xy = repTex; + #ifdef REPROJECT + texCoord.xy = texture2D(remapTexture, texCoord.xy).rg; + if ( + texCoord.x > 1. || + texCoord.x < 0. || + texCoord.y > 1. || + texCoord.y < 0. + ) {continue;} + #endif if (maskValue != 0){ vec2 newV = texCoord.xy; diff --git a/src/components/ui/Elements/Reprojection.tsx b/src/components/ui/Elements/Reprojection.tsx new file mode 100644 index 00000000..8e80306f --- /dev/null +++ b/src/components/ui/Elements/Reprojection.tsx @@ -0,0 +1,88 @@ +import { usePlotStore } from '@/GlobalStates/PlotStore' +import React, { useRef, useState } from 'react' +import { useShallow } from 'zustand/shallow' +import { Hider } from '../Widgets/Hider' +import { ChevronDown } from 'lucide-react' +import { Input } from '../input' +import { Button } from '../button' +import { reproject } from '@/components/textures/ProjectionTexture' +import { TbReplace } from "react-icons/tb"; + +export const Reprojection = () => { + const {projection, defaultProjection} = usePlotStore(useShallow(state => ({ + projection: state.projection, + defaultProjection: state.defaultProjection + }))) + const [showRepro, setShowRepro] = useState(false) + const [changeNativeCRS, setChangeNativeCRS] = useState(false) + const nativeCRS = useRef('') + + return ( +
+ + + +
+
+
+ {defaultProjection + ? `Native CRS: ${defaultProjection}` + : "No CRS detected"} +
+ +
+ +
+ (nativeCRS.current = e.target.value)} + placeholder="Enter native CRS" + /> + +
+
+
+ usePlotStore.setState({ projection: e.target.value })} + placeholder="Target projection" + /> + +
+
+
+
+ ) +} + + diff --git a/src/components/ui/MainPanel/AdjustPlot.tsx b/src/components/ui/MainPanel/AdjustPlot.tsx index 9d4b085d..e0d35fa4 100644 --- a/src/components/ui/MainPanel/AdjustPlot.tsx +++ b/src/components/ui/MainPanel/AdjustPlot.tsx @@ -21,6 +21,7 @@ import { ChevronDown } from 'lucide-react'; import {Select, SelectTrigger, SelectContent, SelectItem, SelectValue} from '@/components/ui' import { RiCloseLargeLine } from "react-icons/ri"; import { reproject } from '@/components/textures/ProjectionTexture'; +import { Reprojection } from '../Elements/Reprojection'; function DeNorm(val : number, min : number, max : number){ const range = max-min; @@ -603,29 +604,6 @@ const GlobalOptions = () =>{
- - - usePlotStore.setState({projection:e.target.value})} - /> - - {!(analysisMode && axis != 0) && // Hide if Analysismode and Axis != 0 <> @@ -763,7 +741,7 @@ const AdjustPlot = () => { Close settings -
+
{ {plotType === 'sphere' && } {(plotType === 'volume' || plotType === 'point-cloud') && } {plotType === 'flat' && } + {['flat','volume'].includes(plotType) && }
From 9d26e39f82bbc0c1c858698c9db6d3c2d8c7d6f9 Mon Sep 17 00:00:00 2001 From: Jeran Date: Wed, 15 Jul 2026 17:00:28 +0200 Subject: [PATCH 04/14] Remote Get Functions --- src/components/plots/FlatBlocks.tsx | 8 +++++--- src/components/plots/Sphere.tsx | 9 ++++++--- src/components/plots/SphereBlocks.tsx | 8 +++++--- src/components/textures/GetFrag.ts | 15 --------------- src/components/textures/GetVert.ts | 18 ------------------ src/components/textures/ProjectionTexture.ts | 12 +++--------- src/components/textures/index.ts | 4 +--- 7 files changed, 20 insertions(+), 54 deletions(-) delete mode 100644 src/components/textures/GetFrag.ts delete mode 100644 src/components/textures/GetVert.ts diff --git a/src/components/plots/FlatBlocks.tsx b/src/components/plots/FlatBlocks.tsx index 2bddce35..8b1c1883 100644 --- a/src/components/plots/FlatBlocks.tsx +++ b/src/components/plots/FlatBlocks.tsx @@ -5,11 +5,10 @@ import { usePlotStore } from '@/GlobalStates/PlotStore'; import { useErrorStore } from '@/GlobalStates/ErrorStore'; import { useShallow } from 'zustand/shallow' import * as THREE from 'three' -import { sphereBlocksFrag } from '../textures/shaders' +import { sphereBlocksFrag, sphereBlocksVert } from '../textures/shaders' import { invalidate } from '@react-three/fiber' import { deg2rad } from '@/utils/HelperFuncs' import { useCoordBounds } from '@/hooks/useCoordBounds' -import { GetVert } from '../textures/GetVert'; const FlatBlocks = ({textures} : {textures: THREE.Data3DTexture[] | THREE.DataTexture[] | null}) => { const {colormap, isFlat, valueScales, flipY, @@ -99,7 +98,10 @@ const FlatBlocks = ({textures} : {textures: THREE.Data3DTexture[] | THREE.DataTe displacement: {value: displacement}, fillValue: {value: fillValue?? NaN}, }, - vertexShader: GetVert("flatBlocksVert", isFlat), + defines:{ + IS_FLAT: isFlat + }, + vertexShader: sphereBlocksVert, fragmentShader: sphereBlocksFrag, blending: THREE.NoBlending, depthWrite:true, diff --git a/src/components/plots/Sphere.tsx b/src/components/plots/Sphere.tsx index a9eb589e..08361259 100644 --- a/src/components/plots/Sphere.tsx +++ b/src/components/plots/Sphere.tsx @@ -7,8 +7,8 @@ import { useShallow } from 'zustand/shallow' import { parseUVCoords, GetTimeSeries, GetCurrentArray, deg2rad } from '@/utils/HelperFuncs'; import { evaluate_cmap } from 'js-colormaps-es'; import { useCoordBounds } from '@/hooks/useCoordBounds' -import { GetFrag, GetVert } from '../textures'; import { SquareMeshes } from './TransectMeshes'; +import { sphereVertex, sphereFrag } from '../textures/shaders'; function XYZtoRemap(xyz : THREE.Vector3, latBounds: number[], lonBounds : number[]){ const lon = Math.atan2(xyz.z,xyz.x) const lat = Math.asin(xyz.y); @@ -92,8 +92,11 @@ export const Sphere = ({textures} : {textures: THREE.Data3DTexture[] | THREE.Dat displacement: {value: sphereDisplacement}, fillValue: {value: NaN}, }, - vertexShader: GetVert('sphereVertex', isFlat), - fragmentShader: GetFrag('sphereFrag', isFlat), + defines:{ + IS_FLAT: isFlat + }, + vertexShader: sphereVertex, + fragmentShader: sphereFrag, blending: THREE.NormalBlending, side:THREE.FrontSide, transparent: true, diff --git a/src/components/plots/SphereBlocks.tsx b/src/components/plots/SphereBlocks.tsx index d32641ff..f3389a8b 100644 --- a/src/components/plots/SphereBlocks.tsx +++ b/src/components/plots/SphereBlocks.tsx @@ -4,11 +4,10 @@ import { usePlotStore } from '@/GlobalStates/PlotStore'; import { useErrorStore } from '@/GlobalStates/ErrorStore'; import { useShallow } from 'zustand/shallow' import * as THREE from 'three' -import { sphereBlocksFrag } from '../textures/shaders' +import { sphereBlocksFrag, sphereBlocksVert } from '../textures/shaders' import { invalidate } from '@react-three/fiber' import { deg2rad } from '@/utils/HelperFuncs' import { useCoordBounds } from '@/hooks/useCoordBounds' -import { GetVert } from '../textures'; const SphereBlocks = ({textures} : {textures: THREE.Data3DTexture[] | THREE.DataTexture[] | null}) => { const {colormap, isFlat, valueScales, dataShape, textureArrayDepths} = useGlobalStore(useShallow(state=>({ @@ -96,7 +95,10 @@ const SphereBlocks = ({textures} : {textures: THREE.Data3DTexture[] | THREE.Data displacement: {value: sphereDisplacement}, fillValue: {value: fillValue?? NaN}, }, - vertexShader: GetVert("sphereBlocksVert", isFlat), + defines:{ + IS_FLAT: isFlat + }, + vertexShader: sphereBlocksVert, fragmentShader: sphereBlocksFrag, blending:THREE.NoBlending, depthWrite:true, diff --git a/src/components/textures/GetFrag.ts b/src/components/textures/GetFrag.ts deleted file mode 100644 index 2dcf0b6a..00000000 --- a/src/components/textures/GetFrag.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { sphereFrag, flatFrag } from "./shaders" - -const shaders ={ - sphereFrag, - flatFrag -} - -export const GetFrag = (shader:keyof typeof shaders, isFlat: boolean) => { - const frag = shaders[shader] - const prefix = isFlat ? "#define IS_FLAT\n" : ""; - const output = prefix + frag; - return ( - output - ) -} \ No newline at end of file diff --git a/src/components/textures/GetVert.ts b/src/components/textures/GetVert.ts deleted file mode 100644 index 64c96464..00000000 --- a/src/components/textures/GetVert.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { flatBlocksVert, sphereVertex, sphereBlocksVert } from "@/components/textures/shaders" - -const shaders ={ - flatBlocksVert, - sphereVertex, - sphereBlocksVert -} - -export const GetVert = (shader:keyof typeof shaders, isFlat: boolean) => { - const vert = shaders[shader as keyof typeof shaders] - const prefix = isFlat ? "#define IS_FLAT\n" : ""; - const output = prefix + vert; - return ( - output - ) -} - - diff --git a/src/components/textures/ProjectionTexture.ts b/src/components/textures/ProjectionTexture.ts index f4602684..d7571fca 100644 --- a/src/components/textures/ProjectionTexture.ts +++ b/src/components/textures/ProjectionTexture.ts @@ -95,11 +95,12 @@ export function reproject(resolution: number = 256){ const width = xArray.length; const height = yArray.length; + const [xMin, xMax] = ArrayMinMax(xArray); const [yMin, yMax] = ArrayMinMax(yArray); // We need the border points as the min/max of the old CRS won't always be the min/max of the new CRS const boundaryPoints: [number, number][] = []; - console.log(xMin, xMax, yMin, yMax) + // top edge: j = 0, all i for (let i = 0; i < width; i++) { boundaryPoints.push([xArray[i], yArray[0]]); @@ -128,14 +129,7 @@ export function reproject(resolution: number = 256){ minY = Math.min(minY, py); maxY = Math.max(maxY, py); } - // ---- Get Estimate of aspectRatio ---- // - const midX = Math.floor(width / 2); - const midY = Math.floor(height / 2); - const [x0, y0] = proj.forward([xArray[midX], yArray[midY]]); - const [x1, y1] = proj.forward([xArray[midX + 1], yArray[midY + 1]]); - const xSize = Math.abs(x1 - x0); - const ySize = Math.abs(y1 - y0); - const aspectRatio = xSize/ySize; + const aspectRatio = Math.abs(maxX - minX)/ Math.abs(maxY - minY); // ---- Construct new CRS axis' ----// const targetWidth = Math.ceil(resolution*aspectRatio); diff --git a/src/components/textures/index.ts b/src/components/textures/index.ts index cc3d8d94..d9d0cda0 100644 --- a/src/components/textures/index.ts +++ b/src/components/textures/index.ts @@ -6,6 +6,4 @@ export { colormaps, ArrayToTexture, CreateTexture -} -export {GetFrag} from './GetFrag' -export {GetVert} from './GetVert' \ No newline at end of file +} \ No newline at end of file From 5acb8192de6642790c3b2cd1429949a33aafa3d5 Mon Sep 17 00:00:00 2001 From: Jeran Date: Thu, 16 Jul 2026 10:19:23 +0200 Subject: [PATCH 05/14] Projection working in shaders. Need UI options --- src/components/textures/ProjectionTexture.ts | 27 ++++++++++++------- src/components/textures/shaders/flatFrag.glsl | 20 +++++--------- .../textures/shaders/volFragment.glsl | 10 +++---- 3 files changed, 27 insertions(+), 30 deletions(-) diff --git a/src/components/textures/ProjectionTexture.ts b/src/components/textures/ProjectionTexture.ts index d7571fca..07b8c1c5 100644 --- a/src/components/textures/ProjectionTexture.ts +++ b/src/components/textures/ProjectionTexture.ts @@ -130,31 +130,40 @@ export function reproject(resolution: number = 256){ } const aspectRatio = Math.abs(maxX - minX)/ Math.abs(maxY - minY); + function safeInverse(proj: any, xy: [number, number], tol = 1e-6) { + //This function checks if the coordinates are valid and returns 0 or 1 based on conditions + const [lon, lat] = proj.inverse(xy); + if (!isFinite(lon) || !isFinite(lat)) return [lon, lat, 0]; + const [xCheck, yCheck] = proj.forward([lon, lat]); + if (Math.abs(xCheck - xy[0]) > tol * Math.max(1, Math.abs(xy[0])) || + Math.abs(yCheck - xy[1]) > tol * Math.max(1, Math.abs(xy[1]))) { + return [lon, lat, 0]; + } + return [lon, lat, 1]; + } // ---- Construct new CRS axis' ----// const targetWidth = Math.ceil(resolution*aspectRatio); const targetHeight = resolution; const xTicks = linspace(minX, maxX, targetWidth); const yTicks = linspace(minY, maxY, targetHeight); - const data = new Uint16Array(targetWidth * targetHeight * 2); + const data = new Uint16Array(targetWidth * targetHeight * 4); for (let j = 0; j < targetHeight; j++) { for (let i = 0; i < targetWidth; i++) { - const [lon, lat] = proj.inverse([xTicks[i], yTicks[j]]); + const [lon, lat, valid] = safeInverse(proj, [xTicks[i], yTicks[j]]); const u = (lon - xMin) / (xMax - xMin); const v = (lat - yMin) / (yMax - yMin); - const idx = (j * targetWidth + i) * 2; - const valid = u >= 0 && u <= 1 && v >= 0 && v <= 1 && isFinite(lon) && isFinite(lat); - data[idx] = THREE.DataUtils.toHalfFloat(valid ? u : -1); - data[idx + 1] = THREE.DataUtils.toHalfFloat(valid ? v : -1); + const idx = (j * targetWidth + i) * 4; + data[idx] = THREE.DataUtils.toHalfFloat(u); + data[idx + 1] = THREE.DataUtils.toHalfFloat(v); + data[idx + 2] = THREE.DataUtils.toHalfFloat(valid); } } - const sanity = proj4(projection, defaultProjection, [-17912187.334735576, -8989370.410319714]) - console.log(sanity) const texture = new THREE.DataTexture( data, targetWidth, targetHeight, - THREE.RGFormat, + THREE.RGBAFormat, // Must be RGBA as HalfFloat RGB is not supported THREE.HalfFloatType ); texture.needsUpdate = true; diff --git a/src/components/textures/shaders/flatFrag.glsl b/src/components/textures/shaders/flatFrag.glsl index 87842a02..6f509541 100644 --- a/src/components/textures/shaders/flatFrag.glsl +++ b/src/components/textures/shaders/flatFrag.glsl @@ -80,13 +80,9 @@ void main() { #ifdef IS_FLAT vec2 texCoord = vUv; #ifdef REPROJECT - texCoord.xy = texture(remapTexture,texCoord.xy).rg; - if ( - texCoord.x > 1. || - texCoord.x < 0. || - texCoord.y > 1. || - texCoord.y < 0. - ) discard; + vec3 remap = texture(remapTexture,texCoord.xy).rgb; + texCoord.xy = remap.rg; + if (remap.b < 0.5) discard; #endif texCoord.xy = clamp(texCoord.xy, vec2(0.0), 1. - vec2(epsilon)); // This prevent the very edges from looping around and causing line artifacts ivec2 idx = clamp(ivec2(texCoord * textureDepths.xy), ivec2(0), ivec2(textureDepths.xy) - 1); @@ -95,13 +91,9 @@ void main() { #else vec3 texCoord = vec3(vUv, animateProg); #ifdef REPROJECT - texCoord.xy = texture(remapTexture,texCoord.xy).rg; - if ( - texCoord.x >= 1. || - texCoord.x <= 0. || - texCoord.y >= 1. || - texCoord.y <= 0. - ) discard; + vec3 remap = texture(remapTexture,texCoord.xy).rgb; + texCoord.xy = remap.rg; + if (remap.b < 0.5) discard; #endif texCoord.xy = clamp(texCoord.xy, vec2(0.0), 1. - vec2(epsilon)); // This prevent the very edges from looping around and causing line artifacts ivec3 idx = clamp(ivec3(texCoord * textureDepths), ivec3(0), ivec3(textureDepths) - 1); diff --git a/src/components/textures/shaders/volFragment.glsl b/src/components/textures/shaders/volFragment.glsl index 8091be0f..d4ab8174 100644 --- a/src/components/textures/shaders/volFragment.glsl +++ b/src/components/textures/shaders/volFragment.glsl @@ -113,13 +113,9 @@ void main() { } vec3 texCoord = p / scale + 0.5; #ifdef REPROJECT - texCoord.xy = texture2D(remapTexture, texCoord.xy).rg; - if ( - texCoord.x > 1. || - texCoord.x < 0. || - texCoord.y > 1. || - texCoord.y < 0. - ) {continue;} + vec3 remap = texture2D(remapTexture, texCoord.xy).rgb; + texCoord.xy = remap.rg; + if (remap.b < 0.5) {continue;} #endif if (maskValue != 0){ From de886188a6b44d1d02bdefe2aec22e22c3186639 Mon Sep 17 00:00:00 2001 From: Jeran Date: Thu, 16 Jul 2026 11:12:50 +0200 Subject: [PATCH 06/14] UI options and errors added --- src/components/textures/ProjectionTexture.ts | 21 ++++-- src/components/ui/Elements/ErrorList.ts | 4 ++ src/components/ui/Elements/Reprojection.tsx | 70 ++++++++++++++------ 3 files changed, 72 insertions(+), 23 deletions(-) diff --git a/src/components/textures/ProjectionTexture.ts b/src/components/textures/ProjectionTexture.ts index 07b8c1c5..a768754b 100644 --- a/src/components/textures/ProjectionTexture.ts +++ b/src/components/textures/ProjectionTexture.ts @@ -1,10 +1,24 @@ import { useGlobalStore } from '@/GlobalStates/GlobalStore'; import { usePlotStore } from '@/GlobalStates/PlotStore'; import { ArrayMinMax, linspace } from '@/utils/HelperFuncs'; +import { useErrorStore } from '@/GlobalStates/ErrorStore'; import * as THREE from 'three'; import proj4 from 'proj4'; +export function checkProjString(projString: string){ + const {setError} = useErrorStore.getState() + try{ + const proj = proj4(projString) + console.log(proj) + return true + } catch{ + setError('badProj') + return false + } + +} + function normalizeArray(array: Array){ const len = array.length; let min = Infinity; @@ -86,6 +100,8 @@ export function SetReprojectionTexture(dimArrays: Array[]){ export function reproject(resolution: number = 256){ const {defaultProjection, projection} = usePlotStore.getState() if (!defaultProjection || !projection) return; // This shouldn't trigger as the button will be disabled for this same condition + if (!checkProjString(projection)) return; // defaultProjection will already be checked when the user sets it, so we don't need to check it here + const {dimArrays, remapTexture } = useGlobalStore.getState() if (remapTexture) remapTexture.dispose(); const dimCount = dimArrays.length; @@ -101,19 +117,16 @@ export function reproject(resolution: number = 256){ // We need the border points as the min/max of the old CRS won't always be the min/max of the new CRS const boundaryPoints: [number, number][] = []; - // top edge: j = 0, all i + for (let i = 0; i < width; i++) { boundaryPoints.push([xArray[i], yArray[0]]); } - // bottom edge: j = height - 1, all i for (let i = 0; i < width; i++) { boundaryPoints.push([xArray[i], yArray[height - 1]]); } - // left edge: i = 0, all j for (let j = 0; j < height; j++) { boundaryPoints.push([xArray[0], yArray[j]]); } - // right edge: i = width - 1, all j for (let j = 0; j < height; j++) { boundaryPoints.push([xArray[width - 1], yArray[j]]); } diff --git a/src/components/ui/Elements/ErrorList.ts b/src/components/ui/Elements/ErrorList.ts index 40e33424..ec79b35a 100644 --- a/src/components/ui/Elements/ErrorList.ts +++ b/src/components/ui/Elements/ErrorList.ts @@ -26,5 +26,9 @@ export const ErrorList = { largeArray :{ title: "Requested too Much Memory", description: "Browzarr cannot request that much data in one call. This is a fixable issue, open an issue on GitHub so I am motivated to implement it." + }, + badProj :{ + title: "Unrecognized Projection String", + description: "The projection string provided is not recognized. Please check the string and try again." } } \ No newline at end of file diff --git a/src/components/ui/Elements/Reprojection.tsx b/src/components/ui/Elements/Reprojection.tsx index 8e80306f..54a852ee 100644 --- a/src/components/ui/Elements/Reprojection.tsx +++ b/src/components/ui/Elements/Reprojection.tsx @@ -5,8 +5,10 @@ import { Hider } from '../Widgets/Hider' import { ChevronDown } from 'lucide-react' import { Input } from '../input' import { Button } from '../button' -import { reproject } from '@/components/textures/ProjectionTexture' +import { checkProjString, reproject } from '@/components/textures/ProjectionTexture' import { TbReplace } from "react-icons/tb"; +import { RxReset } from "react-icons/rx"; +import { useGlobalStore } from '@/GlobalStates/GlobalStore' export const Reprojection = () => { const {projection, defaultProjection} = usePlotStore(useShallow(state => ({ @@ -15,7 +17,17 @@ export const Reprojection = () => { }))) const [showRepro, setShowRepro] = useState(false) const [changeNativeCRS, setChangeNativeCRS] = useState(false) + const nativeCRS = useRef('') + const repRes = useRef(256) + + function handleNativeCRS(){ + const valid = checkProjString(nativeCRS.current) + if (valid){ + usePlotStore.setState({ defaultProjection: nativeCRS.current }) + setChangeNativeCRS(false) + } + } return (
@@ -41,12 +53,21 @@ export const Reprojection = () => { ? `Native CRS: ${defaultProjection}` : "No CRS detected"}
- +
+ + +
+
@@ -54,26 +75,37 @@ export const Reprojection = () => { type="string" defaultValue={nativeCRS.current} onChange={e => (nativeCRS.current = e.target.value)} - placeholder="Enter native CRS" + placeholder={`${defaultProjection ? "Update" : "Enter" } native CRS`} />
-
- usePlotStore.setState({ projection: e.target.value })} - placeholder="Target projection" - /> - diff --git a/src/hooks/useDataFetcher.tsx b/src/hooks/useDataFetcher.tsx index 1d2990ac..3c89a9f6 100644 --- a/src/hooks/useDataFetcher.tsx +++ b/src/hooks/useDataFetcher.tsx @@ -127,6 +127,7 @@ export const useDataFetcher = () => { setDimArrays(dimArrays); setDimNames(dimNames); setDimUnits(dimUnits); + useGlobalStore.setState({axisDimArrays: dimArrays, axisDimNames: dimNames, axisDimUnits: dimUnits}); const targetDim = dimArrays.length > 2 ? dimArrays[1] : dimArrays[0]; const shouldFlip = targetDim[1] < targetDim[0]; From 4b941a9014fff43866bc4879f5141467b04408ce Mon Sep 17 00:00:00 2001 From: Jeran Date: Thu, 16 Jul 2026 16:09:47 +0200 Subject: [PATCH 10/14] change name to CRS --- src/GlobalStates/PlotStore.ts | 8 ++--- src/components/textures/ProjectionTexture.ts | 11 ++++--- src/components/ui/Elements/Reprojection.tsx | 31 ++++++++++---------- src/components/ui/MainPanel/AdjustPlot.tsx | 4 +-- 4 files changed, 26 insertions(+), 28 deletions(-) diff --git a/src/GlobalStates/PlotStore.ts b/src/GlobalStates/PlotStore.ts index 66cabea7..def20607 100644 --- a/src/GlobalStates/PlotStore.ts +++ b/src/GlobalStates/PlotStore.ts @@ -65,8 +65,8 @@ type PlotState ={ cameraPosition: THREE.Vector3; disablePointScale: boolean; camera: THREE.Camera | undefined; - defaultProjection: string | undefined; - projection: string | undefined; + nativeCRS: string | undefined; + destCRS: string | undefined; setQuality: (quality: number) => void; setTimeScale: (timeScale : number) =>void; @@ -191,8 +191,8 @@ export const usePlotStore = create((set, get) => ({ cameraPosition: new THREE.Vector3(0, 0, 5), disablePointScale: false, camera: undefined, - defaultProjection: undefined, - projection: undefined, + nativeCRS: undefined, + destCRS: undefined, setVTransferRange: (vTransferRange) => set({ vTransferRange }), setVTransferScale: (vTransferScale) => set({ vTransferScale }), diff --git a/src/components/textures/ProjectionTexture.ts b/src/components/textures/ProjectionTexture.ts index 129eb153..b32cd2d8 100644 --- a/src/components/textures/ProjectionTexture.ts +++ b/src/components/textures/ProjectionTexture.ts @@ -125,9 +125,9 @@ export function SetReprojectionTexture(dimArrays: Array[]){ } export function reproject(resolution: number = 256){ - const {defaultProjection, projection} = usePlotStore.getState() - if (!defaultProjection || !projection) return; // This shouldn't trigger as the button will be disabled for this same condition - if (!checkProjString(projection)) return; // defaultProjection will already be checked when the user sets it, so we don't need to check it here + const {nativeCRS, destCRS} = usePlotStore.getState() + if (!nativeCRS || !destCRS) return; // This shouldn't trigger as the button will be disabled for this same condition + if (!checkProjString(destCRS)) return; // nativeCRS will already be checked when the user sets it, so we don't need to check it here console.log("Heh?") const {dimArrays, remapTexture } = useGlobalStore.getState() if (remapTexture) remapTexture.dispose(); @@ -158,7 +158,7 @@ export function reproject(resolution: number = 256){ boundaryPoints.push([xArray[width - 1], yArray[j]]); } - const proj = proj4(defaultProjection, projection); + const proj = proj4(nativeCRS, destCRS); let [minX, minY] = [Infinity, Infinity]; let [maxX, maxY] = [-Infinity, -Infinity]; @@ -217,7 +217,7 @@ export function reproject(resolution: number = 256){ useGlobalStore.setState({remapTexture: texture}) // ---- Update Axis and Shape information ----// - const crsCheck = proj4(projection); + const crsCheck = proj4(destCRS); const {axisDimArrays, axisDimUnits, axisDimNames, shape} = useGlobalStore.getState() const newAxisDimArrays = [...axisDimArrays]; newAxisDimArrays[xIdx] = xTicks; @@ -244,6 +244,5 @@ export function reproject(resolution: number = 256){ xSlice: [0, null], ySlice: [0, null] }) - console.log(shape,newShape) } \ No newline at end of file diff --git a/src/components/ui/Elements/Reprojection.tsx b/src/components/ui/Elements/Reprojection.tsx index e9eec8fc..813b8f44 100644 --- a/src/components/ui/Elements/Reprojection.tsx +++ b/src/components/ui/Elements/Reprojection.tsx @@ -8,23 +8,22 @@ import { Button } from '../button' import { checkProjString, reproject, resetProjection } from '@/components/textures/ProjectionTexture' import { TbReplace } from "react-icons/tb"; import { RxReset } from "react-icons/rx"; -import { useGlobalStore } from '@/GlobalStates/GlobalStore' export const Reprojection = () => { - const {projection, defaultProjection} = usePlotStore(useShallow(state => ({ - projection: state.projection, - defaultProjection: state.defaultProjection + const {destCRS, nativeCRS} = usePlotStore(useShallow(state => ({ + destCRS: state.destCRS, + nativeCRS: state.nativeCRS }))) const [showRepro, setShowRepro] = useState(false) const [changeNativeCRS, setChangeNativeCRS] = useState(false) - const nativeCRS = useRef('') + const tempCRS = useRef('') const repRes = useRef(256) function handleNativeCRS(){ - const valid = checkProjString(nativeCRS.current) + const valid = checkProjString(tempCRS.current) if (valid){ - usePlotStore.setState({ defaultProjection: nativeCRS.current }) + usePlotStore.setState({ nativeCRS: tempCRS.current }) setChangeNativeCRS(false) } } @@ -49,8 +48,8 @@ export const Reprojection = () => {
- {defaultProjection - ? `Native CRS: ${defaultProjection}` + {nativeCRS + ? `Native CRS: ${nativeCRS}` : "No CRS detected"}
@@ -69,13 +68,13 @@ export const Reprojection = () => {
- +
(nativeCRS.current = e.target.value)} - placeholder={`${defaultProjection ? "Update" : "Enter" } native CRS`} + defaultValue={tempCRS.current} + onChange={e => (tempCRS.current = e.target.value)} + placeholder={`${nativeCRS ? "Update" : "Enter" } native CRS`} /> diff --git a/src/components/ui/MainPanel/AdjustPlot.tsx b/src/components/ui/MainPanel/AdjustPlot.tsx index e0d35fa4..1e3a3b86 100644 --- a/src/components/ui/MainPanel/AdjustPlot.tsx +++ b/src/components/ui/MainPanel/AdjustPlot.tsx @@ -503,12 +503,12 @@ const SpatialExtent = () =>{ } const GlobalOptions = () =>{ - const {valueRange, showBorders, borderColor, nanColor, nanTransparency, plotType, interpPixels, fillValue, useBorderTexture, defaultProjection, + const {valueRange, showBorders, borderColor, nanColor, nanTransparency, plotType, interpPixels, fillValue, useBorderTexture, nativeCRS, setValueRange, setShowBorders, setBorderColor, setNanColor, setNanTransparency, setInterpPixels, setFillValue} = usePlotStore(useShallow(state => ({ showBorders: state.showBorders, borderColor: state.borderColor, nanColor: state.nanColor, nanTransparency: state.nanTransparency, plotType: state.plotType, interpPixels: state.interpPixels, - fillValue: state.fillValue, useBorderTexture:state.useBorderTexture, defaultProjection: state.defaultProjection, + fillValue: state.fillValue, useBorderTexture:state.useBorderTexture, nativeCRS: state.nativeCRS, valueRange: state.valueRange, setValueRange: state.setValueRange, setShowBorders: state.setShowBorders, setBorderColor: state.setBorderColor, setNanColor: state.setNanColor, setNanTransparency: state.setNanTransparency, From 5b0bcc64c2c02fd8662ce7b2855882991e8323bd Mon Sep 17 00:00:00 2001 From: Jeran Date: Thu, 16 Jul 2026 16:46:29 +0200 Subject: [PATCH 11/14] Working with FlatBlocks --- src/components/plots/AxisLines.tsx | 39 +++++++++---------- src/components/plots/FlatBlocks.tsx | 27 +++++++------ src/components/plots/FlatMap.tsx | 13 ++++--- src/components/textures/ProjectionTexture.ts | 3 +- .../textures/shaders/flatBlocksVert.glsl | 10 ++++- 5 files changed, 52 insertions(+), 40 deletions(-) diff --git a/src/components/plots/AxisLines.tsx b/src/components/plots/AxisLines.tsx index 3db95aa2..37894681 100644 --- a/src/components/plots/AxisLines.tsx +++ b/src/components/plots/AxisLines.tsx @@ -77,9 +77,9 @@ const CubeAxis = ({flipX, flipY, flipDown}: {flipX: boolean, flipY: boolean, fli const isPC = useMemo(()=>plotType == 'point-cloud',[plotType]) const globalScale = isPC ? dataShape[2]/AXIS_CONSTANTS.PC_GLOBAL_SCALE_DIVISOR : 1 - const depthRatio = useMemo(()=>dataShape[0]/dataShape[2]*timeScale,[dataShape, timeScale]); - const shapeRatio = useMemo(()=>dataShape[1]/dataShape[2], [dataShape]) - const timeRatio = Math.max(dataShape[0]/dataShape[2], 2); + const depthRatio = useMemo(()=>shape.z/shape.x*timeScale,[shape, timeScale]); + const shapeRatio = useMemo(()=>shape.y/shape.x, [shape]) + const timeRatio = Math.max(shape.z/shape.x, 2); const secondaryColor = useCSSVariable('--text-plot') //replace with needed variable const colorHex = useMemo(()=>{ @@ -342,10 +342,10 @@ const FLAT_AXIS_CONSTANTS = { }; const FlatAxis = () =>{ - const {dimArrays, dimNames, dimUnits, flipY} = useGlobalStore(useShallow(state => ({ - dimArrays: state.dimArrays, - dimNames: state.dimNames, - dimUnits: state.dimUnits, + const {axisDimArrays, axisDimNames, axisDimUnits, flipY} = useGlobalStore(useShallow(state => ({ + axisDimArrays: state.axisDimArrays, + axisDimNames: state.axisDimNames, + axisDimUnits: state.axisDimUnits, flipY: state.flipY, }))) @@ -365,23 +365,23 @@ const FlatAxis = () =>{ axis: state.axis }))) - const originallyFlat = dimArrays.length == 2; + const originallyFlat = axisDimArrays.length == 2; const slices = originallyFlat ? [ySlice, xSlice] : [zSlice, ySlice, xSlice] const {xIdx, yIdx, zIdx} = useAxisIndices() const dimSlices = useMemo(()=> { return originallyFlat ? [ - flipY ? dimArrays[yIdx]?.slice().reverse() ?? [] : dimArrays[yIdx] ?? [], - dimArrays[xIdx] ?? [] + flipY ? axisDimArrays[yIdx]?.slice().reverse() ?? [] : axisDimArrays[yIdx] ?? [], + axisDimArrays[xIdx] ?? [] ] : [ - dimArrays[zIdx]?.slice(zSlice[0], zSlice[1] ? zSlice[1] : undefined) ?? [], - flipY ? dimArrays[yIdx]?.slice(ySlice[0], ySlice[1] ? ySlice[1] : undefined).reverse() ?? [] : dimArrays[yIdx]?.slice(ySlice[0], ySlice[1] ? ySlice[1] : undefined) ?? [], - dimArrays[xIdx]?.slice(xSlice[0], xSlice[1] ? xSlice[1] : undefined) ?? [], + axisDimArrays[zIdx]?.slice(zSlice[0], zSlice[1] ? zSlice[1] : undefined) ?? [], + flipY ? axisDimArrays[yIdx]?.slice(ySlice[0], ySlice[1] ? ySlice[1] : undefined).reverse() ?? [] : axisDimArrays[yIdx]?.slice(ySlice[0], ySlice[1] ? ySlice[1] : undefined) ?? [], + axisDimArrays[xIdx]?.slice(xSlice[0], xSlice[1] ? xSlice[1] : undefined) ?? [], ] - },[dimArrays, flipY, originallyFlat, xSlice, ySlice, zSlice, xIdx, yIdx, zIdx]) + },[axisDimArrays, flipY, originallyFlat, xSlice, ySlice, zSlice, xIdx, yIdx, zIdx]) const dimLengths = useMemo(()=>{ if (analysisMode && !originallyFlat){ @@ -404,13 +404,13 @@ const FlatAxis = () =>{ if (originallyFlat) { return { axisArrays: dimSlices, - axisUnits: [dimUnits[yIdx], dimUnits[xIdx]], - axisNames: [dimNames[yIdx], dimNames[xIdx]], + axisUnits: [axisDimUnits[yIdx], axisDimUnits[xIdx]], + axisNames: [axisDimNames[yIdx], axisDimNames[xIdx]], }; } - const baseUnits = [dimUnits[zIdx], dimUnits[yIdx], dimUnits[xIdx]]; - const baseNames = [dimNames[zIdx], dimNames[yIdx], dimNames[xIdx]]; + const baseUnits = [axisDimUnits[zIdx], axisDimUnits[yIdx], axisDimUnits[xIdx]]; + const baseNames = [axisDimNames[zIdx], axisDimNames[yIdx], axisDimNames[xIdx]]; if (analysisMode) { return { @@ -425,9 +425,8 @@ const FlatAxis = () =>{ axisNames: baseNames, }; } - }, [analysisMode, dimArrays, dimUnits, dimNames, dimSlices, originallyFlat, axis, xIdx, yIdx, zIdx]); + }, [analysisMode, axisDimArrays, axisDimUnits, axisDimNames, dimSlices, originallyFlat, axis, xIdx, yIdx, zIdx]); - const shapeRatio = useMemo(()=>{ if(analysisMode && axis == 2){ return dimLengths[heightIdx]/dimLengths[widthIdx] diff --git a/src/components/plots/FlatBlocks.tsx b/src/components/plots/FlatBlocks.tsx index 03d1006e..b972eeae 100644 --- a/src/components/plots/FlatBlocks.tsx +++ b/src/components/plots/FlatBlocks.tsx @@ -5,22 +5,25 @@ import { usePlotStore } from '@/GlobalStates/PlotStore'; import { useErrorStore } from '@/GlobalStates/ErrorStore'; import { useShallow } from 'zustand/shallow' import * as THREE from 'three' -import { sphereBlocksFrag, sphereBlocksVert } from '../textures/shaders' +import { flatBlocksVert, sphereBlocksFrag } from '../textures/shaders' import { invalidate } from '@react-three/fiber' import { deg2rad } from '@/utils/HelperFuncs' import { useCoordBounds } from '@/hooks/useCoordBounds' import { usePaddedTextures } from '@/hooks/usePaddedTextures'; +import { useAxisIndices } from '@/hooks'; const FlatBlocks = ({textures: propTextures} : {textures: THREE.Data3DTexture[] | THREE.DataTexture[] | null}) => { const textures = usePaddedTextures(propTextures); const {colormap, isFlat, valueScales, flipY, - dataShape, textureArrayDepths} = useGlobalStore(useShallow(state=>({ + dataShape, textureArrayDepths, axisDimArrays, remapTexture} = useGlobalStore(useShallow(state=>({ colormap: state.colormap, isFlat: state.isFlat, valueScales: state.valueScales, flipY: state.flipY, dataShape: state.dataShape, - textureArrayDepths: state.textureArrayDepths + textureArrayDepths: state.textureArrayDepths, + axisDimArrays: state.axisDimArrays, + remapTexture: state.remapTexture }))) const { animProg, cOffset, cScale, nanColor, nanTransparency, displacement, fillValue, valueRange, offsetNegatives, rotateFlat, maskTexture, maskValue, } = usePlotStore(useShallow(state=> ({ @@ -33,16 +36,16 @@ const FlatBlocks = ({textures: propTextures} : {textures: THREE.Data3DTexture[] const {analysisMode, axis} = useAnalysisStore(useShallow(state => ({ analysisMode: state.analysisMode, axis:state.axis }))) + const {xIdx, yIdx} = useAxisIndices() const {width, height} = useMemo(()=>{ - if (dataShape.length == 2){ - return {width: dataShape[1], height: dataShape[0]} - } else if (analysisMode){ + if (analysisMode){ const thisShape = dataShape.filter((_val, idx) => idx != axis) return {width: thisShape[1], height: thisShape[0]} } else { - return {width: dataShape[2], height: dataShape[1]} + return {width: axisDimArrays[xIdx].length, height: axisDimArrays[yIdx].length} } - },[analysisMode, axis, dataShape]) + },[analysisMode, axis, dataShape, axisDimArrays]) + const rotateMap = analysisMode && axis == 2; const count = useMemo(()=>{ const count = width * height; @@ -83,6 +86,7 @@ const FlatBlocks = ({textures: propTextures} : {textures: THREE.Data3DTexture[] glslVersion: THREE.GLSL3, uniforms: { map: { value: textures }, + remapTexture: { value: remapTexture }, maskTexture: {value: maskTexture}, maskValue: {value: maskValue}, threshold: {value: new THREE.Vector2(valueRange[0],valueRange[1])}, @@ -101,16 +105,17 @@ const FlatBlocks = ({textures: propTextures} : {textures: THREE.Data3DTexture[] fillValue: {value: fillValue?? NaN}, }, defines:{ - IS_FLAT: isFlat + IS_FLAT: isFlat, + REPROJECT: remapTexture ? true: false }, - vertexShader: sphereBlocksVert, + vertexShader: flatBlocksVert, fragmentShader: sphereBlocksFrag, blending: THREE.NoBlending, depthWrite:true, depthTest:true, }) return shader - },[width, height, isFlat]) + },[width, height, isFlat, remapTexture]) useEffect(()=>{ if (shaderMaterial){ diff --git a/src/components/plots/FlatMap.tsx b/src/components/plots/FlatMap.tsx index a556dcd6..16b549d2 100644 --- a/src/components/plots/FlatMap.tsx +++ b/src/components/plots/FlatMap.tsx @@ -27,13 +27,14 @@ const FlatMap = ({textures: propTextures, infoSetters} : {textures : THREE.DataT const textures = usePaddedTextures(propTextures); const {setLoc, setShowInfo, val, coords} = infoSetters; const {flipY, colormap, dimArrays, dimNames, dimUnits, - isFlat, dataShape, textureArrayDepths, strides, remapTexture, + isFlat, dataShape, textureArrayDepths, strides, remapTexture, shape, setPlotDim,updateDimCoords, updateTimeSeries} = useGlobalStore(useShallow(state => ({ flipY: state.flipY, colormap: state.colormap, dimArrays: state.dimArrays, strides: state.strides, dimNames:state.dimNames, dimUnits: state.dimUnits, isFlat: state.isFlat, dataShape: state.dataShape, - textureArrayDepths: state.textureArrayDepths,remapTexture:state.remapTexture, + textureArrayDepths: state.textureArrayDepths, + remapTexture:state.remapTexture, shape: state.shape, setPlotDim:state.setPlotDim, updateDimCoords:state.updateDimCoords, updateTimeSeries: state.updateTimeSeries @@ -78,17 +79,17 @@ const FlatMap = ({textures: propTextures, infoSetters} : {textures : THREE.DataT if (coarsen) slices = slices.map((val, idx) => coarsenFlatArray(val, (idx === 0 && slices.length > 2 ? kernelDepth : kernelSize))) return slices } ,[dimArrays, zSlice, ySlice, xSlice, coarsen, kernelDepth, kernelSize, xIdx, yIdx, zIdx]) - const shapeRatio = useMemo(()=> { if (dataShape.length == 2){ - return dataShape[0]/dataShape[1] + return shape.y/shape.x } else if (analysisMode){ const thisShape = dataShape.filter((_val, idx) => idx != axis) return thisShape[0]/thisShape[1] } else { - return dataShape[1]/dataShape[2] + return shape.y/shape.x } - }, [axis, analysisMode] ) + }, [axis, shape, dataShape, analysisMode] ) + const geometry = useMemo(()=>new THREE.PlaneGeometry(2,2*shapeRatio),[shapeRatio]) const infoRef = useRef(false) const lastUV = useRef(new THREE.Vector2(0,0)) diff --git a/src/components/textures/ProjectionTexture.ts b/src/components/textures/ProjectionTexture.ts index b32cd2d8..90412090 100644 --- a/src/components/textures/ProjectionTexture.ts +++ b/src/components/textures/ProjectionTexture.ts @@ -12,7 +12,6 @@ export function checkProjString(projString: string){ const {setError} = useErrorStore.getState() try{ const proj = proj4(projString) - console.log(proj) return true } catch{ setError('badProj') @@ -128,7 +127,7 @@ export function reproject(resolution: number = 256){ const {nativeCRS, destCRS} = usePlotStore.getState() if (!nativeCRS || !destCRS) return; // This shouldn't trigger as the button will be disabled for this same condition if (!checkProjString(destCRS)) return; // nativeCRS will already be checked when the user sets it, so we don't need to check it here - console.log("Heh?") + const {dimArrays, remapTexture } = useGlobalStore.getState() if (remapTexture) remapTexture.dispose(); diff --git a/src/components/textures/shaders/flatBlocksVert.glsl b/src/components/textures/shaders/flatBlocksVert.glsl index c0fbe437..895fa1e8 100644 --- a/src/components/textures/shaders/flatBlocksVert.glsl +++ b/src/components/textures/shaders/flatBlocksVert.glsl @@ -7,7 +7,7 @@ attribute vec2 instanceUV; #else uniform sampler3D map[14]; #endif - +uniform sampler2D remapTexture; uniform sampler2D maskTexture; uniform vec3 textureDepths; @@ -81,6 +81,14 @@ void main() { int zStepSize = int(textureDepths.y) * int(textureDepths.x); int yStepSize = int(textureDepths.x); vec3 texCoord = vec3(instanceUV, animateProg); + #ifdef REPROJECT + vec3 remap = texture(remapTexture,instanceUV).rgb; + texCoord.xy = remap.rg; + if (remap.b < 0.5){ + gl_Position = vec4(2.0, 2.0, 2.0, 1.0); + return; + } + #endif #ifdef IS_FLAT ivec2 idx = clamp(ivec2(instanceUV * textureDepths.xy), ivec2(0), ivec2(textureDepths.xy) - 1); int textureIdx = idx.y * yStepSize + idx.x; From 9e0121a4745bac29a5a34906d4c24d977c39ffc8 Mon Sep 17 00:00:00 2001 From: Jeran Date: Thu, 16 Jul 2026 17:13:26 +0200 Subject: [PATCH 12/14] cleanup on new store. --- src/components/LandingHome.tsx | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/components/LandingHome.tsx b/src/components/LandingHome.tsx index f5ba7108..cd1f4e87 100644 --- a/src/components/LandingHome.tsx +++ b/src/components/LandingHome.tsx @@ -12,6 +12,7 @@ import { useGlobalStore } from '@/GlobalStates/GlobalStore'; import { useZarrStore } from '@/GlobalStates/ZarrStore'; import { useShallow } from 'zustand/shallow'; import { loadNetCDF, NETCDF_EXT_REGEX } from '@/utils/loadNetCDF'; +import { usePlotStore } from '@/GlobalStates/PlotStore'; async function sendPing() { const url = "https://www.bgc-jena.mpg.de/~jpoehls/browzarr/visitor_logger.php"; @@ -86,8 +87,10 @@ export function LandingHome() { ); setCurrentStore(newStore); // Clear after use - useZarrStore.getState().setIcechunkOptions(null); - useZarrStore.getState().setFetchOptions(null); + + useZarrStore.setState({icechunkOptions: null, fetchOptions:null}); + useGlobalStore.setState({remapTexture: undefined }) + usePlotStore.setState({nativeCRS:undefined, destCRS:undefined}) }, [initStore, fetchKey, setCurrentStore, setUseNC, setZSlice, setYSlice, setXSlice, storeFromURL, setOpenVariables, setStoreFromURL]); useEffect(() => { From e9da18bcdefd86a62eb81c6ba7563a533ced2750 Mon Sep 17 00:00:00 2001 From: Jeran Date: Thu, 16 Jul 2026 17:31:15 +0200 Subject: [PATCH 13/14] Gemini suggestions --- src/components/LandingHome.tsx | 7 ++++--- src/components/textures/ProjectionTexture.ts | 8 +++++--- src/components/ui/Elements/Reprojection.tsx | 8 ++++---- 3 files changed, 13 insertions(+), 10 deletions(-) diff --git a/src/components/LandingHome.tsx b/src/components/LandingHome.tsx index cd1f4e87..384cf981 100644 --- a/src/components/LandingHome.tsx +++ b/src/components/LandingHome.tsx @@ -87,10 +87,11 @@ export function LandingHome() { ); setCurrentStore(newStore); // Clear after use - + const {remapTexture} = useGlobalStore.getState(); + if (remapTexture) remapTexture.dispose(); useZarrStore.setState({icechunkOptions: null, fetchOptions:null}); - useGlobalStore.setState({remapTexture: undefined }) - usePlotStore.setState({nativeCRS:undefined, destCRS:undefined}) + useGlobalStore.setState({remapTexture: undefined }); + usePlotStore.setState({nativeCRS:undefined, destCRS:undefined}); }, [initStore, fetchKey, setCurrentStore, setUseNC, setZSlice, setYSlice, setXSlice, storeFromURL, setOpenVariables, setStoreFromURL]); useEffect(() => { diff --git a/src/components/textures/ProjectionTexture.ts b/src/components/textures/ProjectionTexture.ts index 90412090..ff372a14 100644 --- a/src/components/textures/ProjectionTexture.ts +++ b/src/components/textures/ProjectionTexture.ts @@ -20,7 +20,8 @@ export function checkProjString(projString: string){ } export function resetProjection(){ - const {dimArrays, dimNames, dimUnits, shape} = useGlobalStore.getState() + const {dimArrays, dimNames, dimUnits, shape, remapTexture} = useGlobalStore.getState() + if (remapTexture) remapTexture.dispose() const {xSlice, ySlice} = useZarrStore.getState() const {xIdx, yIdx} = getAxisIndices() @@ -29,6 +30,7 @@ export function resetProjection(){ const aspectRatio = xLength/yLength; const newShape = new THREE.Vector3().copy(shape) newShape.y = 2/aspectRatio; + useGlobalStore.setState({ axisDimArrays: dimArrays, @@ -54,7 +56,7 @@ function normalizeArray(array: Array){ if (v > max) max = v; } const range = max - min; - const scaler = 1/range; + const scaler = range === 0 ? 0 : 1 / range; const out = new Float32Array(len); for (let i = 0; i < array.length; i++){ out[i] = (array[i]-min)* scaler; @@ -118,7 +120,7 @@ export function SetReprojectionTexture(dimArrays: Array[]){ const yArray = dimArrays[yIdx]; const isRegular = isUniformStep(xArray) && isUniformStep(yArray) if (isRegular) return; - + //Dispose of remaptexture if you use this function const remapTexture = createRemapTexture(xArray, yArray); useGlobalStore.setState({remapTexture}); } diff --git a/src/components/ui/Elements/Reprojection.tsx b/src/components/ui/Elements/Reprojection.tsx index 813b8f44..3253b81f 100644 --- a/src/components/ui/Elements/Reprojection.tsx +++ b/src/components/ui/Elements/Reprojection.tsx @@ -27,7 +27,6 @@ export const Reprojection = () => { setChangeNativeCRS(false) } } - return (
From d930c1d1a7ea0200fbe79cc0784c2d771d0c84d0 Mon Sep 17 00:00:00 2001 From: Jeran Date: Thu, 16 Jul 2026 17:38:06 +0200 Subject: [PATCH 14/14] shader array sizes --- src/components/textures/shaders/flatBlocksVert.glsl | 6 ++---- src/components/textures/shaders/fragmentOpt.glsl | 4 +--- src/components/textures/shaders/sphereBlocksVert.glsl | 6 ++---- src/components/textures/shaders/sphereFrag.glsl | 6 ++---- src/components/textures/shaders/sphereVertex.glsl | 6 ++---- src/components/textures/shaders/volFragment.glsl | 5 ++--- 6 files changed, 11 insertions(+), 22 deletions(-) diff --git a/src/components/textures/shaders/flatBlocksVert.glsl b/src/components/textures/shaders/flatBlocksVert.glsl index 895fa1e8..75341dc8 100644 --- a/src/components/textures/shaders/flatBlocksVert.glsl +++ b/src/components/textures/shaders/flatBlocksVert.glsl @@ -3,9 +3,9 @@ attribute vec2 instanceUV; #ifdef IS_FLAT - uniform sampler2D map[14]; + uniform sampler2D map[12]; #else - uniform sampler3D map[14]; + uniform sampler3D map[12]; #endif uniform sampler2D remapTexture; uniform sampler2D maskTexture; @@ -61,8 +61,6 @@ float sample1( else if (index == 9) return texture(map[9], p).r; else if (index == 10) return texture(map[10], p).r; else if (index == 11) return texture(map[11], p).r; - else if (index == 12) return texture(map[12], p).r; - else if (index == 13) return texture(map[13], p).r; else return 0.0; } diff --git a/src/components/textures/shaders/fragmentOpt.glsl b/src/components/textures/shaders/fragmentOpt.glsl index d9ba71c0..da95a2a0 100644 --- a/src/components/textures/shaders/fragmentOpt.glsl +++ b/src/components/textures/shaders/fragmentOpt.glsl @@ -8,7 +8,7 @@ in vec3 vDirection; out vec4 color; -uniform sampler3D map[14]; // We are limited to 16 textures. Cmap counts as one. 15 is weird so we use 14. +uniform sampler3D map[12]; // We are limited to 16 textures. Cmap counts as one. 15 is weird so we use 14. uniform sampler2D maskTexture; uniform sampler2D cmap; uniform vec3 textureDepths; @@ -74,8 +74,6 @@ float sample1(vec3 p, int index) { // Shader doesn't support dynamic indexing so else if (index == 9) return texture(map[9], p).r; else if (index == 10) return texture(map[10], p).r; else if (index == 11) return texture(map[11], p).r; - else if (index == 12) return texture(map[12], p).r; - else if (index == 13) return texture(map[13], p).r; else return 0.0; } diff --git a/src/components/textures/shaders/sphereBlocksVert.glsl b/src/components/textures/shaders/sphereBlocksVert.glsl index 6ab91c5b..963adc7e 100644 --- a/src/components/textures/shaders/sphereBlocksVert.glsl +++ b/src/components/textures/shaders/sphereBlocksVert.glsl @@ -3,9 +3,9 @@ attribute vec2 instanceUV; #ifdef IS_FLAT - uniform sampler2D map[14]; + uniform sampler2D map[12]; #else - uniform sampler3D map[14]; + uniform sampler3D map[12]; #endif uniform sampler2D maskTexture; uniform vec3 textureDepths; @@ -75,8 +75,6 @@ float sample1( else if (index == 9) return texture(map[9], p).r; else if (index == 10) return texture(map[10], p).r; else if (index == 11) return texture(map[11], p).r; - else if (index == 12) return texture(map[12], p).r; - else if (index == 13) return texture(map[13], p).r; else return 0.0; } diff --git a/src/components/textures/shaders/sphereFrag.glsl b/src/components/textures/shaders/sphereFrag.glsl index 55d78d84..4face350 100644 --- a/src/components/textures/shaders/sphereFrag.glsl +++ b/src/components/textures/shaders/sphereFrag.glsl @@ -4,9 +4,9 @@ out vec4 color; in vec3 aPosition; #ifdef IS_FLAT - uniform sampler2D map[14]; + uniform sampler2D map[12]; #else - uniform sampler3D map[14]; + uniform sampler3D map[12]; #endif uniform sampler2D maskTexture; @@ -69,8 +69,6 @@ float sample1( else if (index == 9) return texture(map[9], p).r; else if (index == 10) return texture(map[10], p).r; else if (index == 11) return texture(map[11], p).r; - else if (index == 12) return texture(map[12], p).r; - else if (index == 13) return texture(map[13], p).r; else return 0.0; } diff --git a/src/components/textures/shaders/sphereVertex.glsl b/src/components/textures/shaders/sphereVertex.glsl index 94a72759..abafb755 100644 --- a/src/components/textures/shaders/sphereVertex.glsl +++ b/src/components/textures/shaders/sphereVertex.glsl @@ -1,9 +1,9 @@ // by Jeran Poehls #ifdef IS_FLAT - uniform sampler2D map[14]; + uniform sampler2D map[12]; #else - uniform sampler3D map[14]; + uniform sampler3D map[12]; #endif uniform vec3 textureDepths; @@ -44,8 +44,6 @@ float sample1( else if (index == 9) return texture(map[9], p).r; else if (index == 10) return texture(map[10], p).r; else if (index == 11) return texture(map[11], p).r; - else if (index == 12) return texture(map[12], p).r; - else if (index == 13) return texture(map[13], p).r; else return 0.0; } diff --git a/src/components/textures/shaders/volFragment.glsl b/src/components/textures/shaders/volFragment.glsl index d4ab8174..112489d6 100644 --- a/src/components/textures/shaders/volFragment.glsl +++ b/src/components/textures/shaders/volFragment.glsl @@ -7,7 +7,7 @@ in vec3 vDirection; out vec4 color; -uniform sampler3D map[11]; // We are limited to 16 textures. Cmap counts as one. 15 is weird so we use 14. +uniform sampler3D map[12]; // We are limited to 16 textures. Cmap counts as one. 15 is weird so we use 14. uniform sampler2D maskTexture; uniform sampler2D cmap; uniform sampler2D remapTexture; @@ -74,8 +74,7 @@ float sample1(vec3 p, int index) { // Shader doesn't support dynamic indexing so else if (index == 8) return texture(map[8], p).r; else if (index == 9) return texture(map[9], p).r; else if (index == 10) return texture(map[10], p).r; - // else if (index == 11) return texture(map[11], p).r; - // else if (index == 12) return texture(map[12], p).r; + else if (index == 11) return texture(map[11], p).r; else return 0.0; }