diff --git a/package.json b/package.json index b36cff06..46bcf1ab 100644 --- a/package.json +++ b/package.json @@ -85,6 +85,7 @@ "lucide-react": "^0.562.0", "next": "^16.2.7", "next-themes": "^0.4.6", + "pnpm": "^10.34.5", "proj4": "^2.20.9", "radix-ui": "1.4.3", "react": "^19.0.0", diff --git a/src/components/plots/CountryBorders.tsx b/src/components/plots/CountryBorders.tsx index 42bd8cd2..aa95143e 100644 --- a/src/components/plots/CountryBorders.tsx +++ b/src/components/plots/CountryBorders.tsx @@ -45,11 +45,15 @@ function Borders({features}:{features: any}){ const [lonBounds, latBounds] = useMemo(()=>{ //The bounds for the shader. It takes the middle point of the furthest coordinate and adds the distance to edge of pixel const newLatStep = latResolution/2; const newLonStep = lonResolution/2; - const newLonBounds = [Math.max(lonExtent[0]-newLonStep, -180), Math.min(lonExtent[1]+newLonStep, 180)] - let newLatBounds = [Math.max(latExtent[0]-newLatStep, -90), Math.min(latExtent[1]+newLatStep, 90)] + const minLat = Math.min(latExtent[0], latExtent[1]); + const maxLat = Math.max(latExtent[0], latExtent[1]); + const minLon = Math.min(lonExtent[0], lonExtent[1]); + const maxLon = Math.max(lonExtent[0], lonExtent[1]); + const newLonBounds = [Math.max(minLon-newLonStep, -180), Math.min(maxLon+newLonStep, 180)] + let newLatBounds = [Math.max(minLat-newLatStep, -90), Math.min(maxLat+newLatStep, 90)] newLatBounds = flipY ? [newLatBounds[1], newLatBounds[0]] : newLatBounds return [newLonBounds as [number, number], newLatBounds as [number, number]] - },[latExtent, lonExtent, lonResolution, latResolution]) + },[latExtent, lonExtent, lonResolution, latResolution, flipY]) const [spherize, setSpherize] = useState(false) @@ -63,33 +67,37 @@ function Borders({features}:{features: any}){ },[plotType]) - const lineShaderMat = useMemo(()=>new THREE.ShaderMaterial( - { - glslVersion: THREE.GLSL3, - vertexShader, - fragmentShader: bordersFrag, - uniforms:{ - xBounds: {value: new THREE.Vector2(-xRange[1],-xRange[0])}, - yBounds: {value: new THREE.Vector2(yRange[0]/shape.x, yRange[1]/shape.x)}, - borderColor: {value: new THREE.Color(borderColor)}, - trim: {value: !spherize}, - }, - defines: { - USE_APOSITION: 1 + const lineShaderMat = useMemo(() => { + const shapeX = (shape && shape.x > 0) ? shape.x : 1; + return new THREE.ShaderMaterial( + { + glslVersion: THREE.GLSL3, + vertexShader, + fragmentShader: bordersFrag, + uniforms:{ + xBounds: {value: new THREE.Vector2(-xRange[1],-xRange[0])}, + yBounds: {value: new THREE.Vector2(yRange[0]/shapeX, yRange[1]/shapeX)}, + borderColor: {value: new THREE.Color(borderColor)}, + trim: {value: !spherize}, + }, + defines: { + USE_APOSITION: 1 + } } - } - ),[]) + ); + }, []) useEffect(()=>{ if (lineShaderMat){ const uniforms = lineShaderMat.uniforms uniforms.xBounds.value = new THREE.Vector2(xRange[0], xRange[1]) - uniforms.yBounds.value = new THREE.Vector2(yRange[0]/shape.x, yRange[1]/shape.x) + const shapeX = (shape && shape.x > 0) ? shape.x : 1; + uniforms.yBounds.value = new THREE.Vector2(yRange[0]/shapeX, yRange[1]/shapeX) uniforms.borderColor.value = new THREE.Color(borderColor) uniforms.trim.value = !spherize invalidate() } - },[xRange, yRange, borderColor, spherize]) + },[xRange, yRange, borderColor, spherize, shape]) const lineGeometries = useMemo(() => { return features.flatMap((feature: any, i: number) => { @@ -201,9 +209,11 @@ const CountryBorders = () => { const [borders, setBorders] = useState(null) const [swapSides, setSwapSides] = useState(false) - const {dataShape, is4D} = useGlobalStore(useShallow(state => ({ + const {dataShape, is4D, flipY, shape} = useGlobalStore(useShallow(state => ({ dataShape: state.dataShape, - is4D: state.is4D + is4D: state.is4D, + flipY: state.flipY, + shape: state.shape }))) const {zRange, plotType, showBorders, timeScale, rotateFlat, pointSize, is360Deg} = usePlotStore(useShallow(state => ({ zRange: state.zRange, plotType: state.plotType, @@ -251,19 +261,19 @@ const CountryBorders = () => { const isPC = plotType == 'point-cloud' const isFlatMap = plotType == "flat" const timeRatio = isPC ? dataShape[0]/dataShape[2] : Math.max(dataShape[0]/dataShape[2],2) - const depthScale = timeRatio*timeScale - const aspectRatio = dataShape[2]/dataShape[1] - + const depthRatio = (shape && shape.x > 0) ? (shape.z / shape.x) * timeScale : 1; const globalScale = isPC ? dataShape[2]/500 : 1 + const depthScale = isPC ? depthRatio : timeRatio/2 + const aspectRatio = (shape && shape.y > 0) ? (shape.x / shape.y) : 1; return( {coastLines && } diff --git a/src/components/plots/DataCube.tsx b/src/components/plots/DataCube.tsx index efb10aec..bae8dbd0 100644 --- a/src/components/plots/DataCube.tsx +++ b/src/components/plots/DataCube.tsx @@ -76,7 +76,7 @@ export const DataCube = ({ volTexture: propVolTexture }: DataCubeProps ) => { defines: { USE_VORIGIN: 1, USE_VDIRECTION: 1, - REPROJECT: remapTexture ? true : false + ...(remapTexture ? { REPROJECT: true } : {}) }, vertexShader: useOrtho ? orthoVertex : vertexShader, fragmentShader: useFragOpt ? fragOpt : fragmentShader, diff --git a/src/components/plots/FlatBlocks.tsx b/src/components/plots/FlatBlocks.tsx index b972eeae..f527a86d 100644 --- a/src/components/plots/FlatBlocks.tsx +++ b/src/components/plots/FlatBlocks.tsx @@ -105,8 +105,8 @@ const FlatBlocks = ({textures: propTextures} : {textures: THREE.Data3DTexture[] fillValue: {value: fillValue?? NaN}, }, defines:{ - IS_FLAT: isFlat, - REPROJECT: remapTexture ? true: false + ...(isFlat ? { IS_FLAT: true } : {}), + ...(remapTexture ? { REPROJECT: true } : {}) }, vertexShader: flatBlocksVert, fragmentShader: sphereBlocksFrag, diff --git a/src/components/plots/FlatMap.tsx b/src/components/plots/FlatMap.tsx index 16b549d2..105f3c73 100644 --- a/src/components/plots/FlatMap.tsx +++ b/src/components/plots/FlatMap.tsx @@ -199,8 +199,8 @@ const FlatMap = ({textures: propTextures, infoSetters} : {textures : THREE.DataT fillValue: {value: fillValue?? NaN}, }, defines:{ - IS_FLAT: isFlat, - REPROJECT: remapTexture ? true: false + ...(isFlat ? { IS_FLAT: true } : {}), + ...(remapTexture ? { REPROJECT: true } : {}) }, vertexShader: vertShader, fragmentShader: flatFrag, diff --git a/src/components/plots/Plot.tsx b/src/components/plots/Plot.tsx index 9abfbe56..fc4a112c 100644 --- a/src/components/plots/Plot.tsx +++ b/src/components/plots/Plot.tsx @@ -15,6 +15,7 @@ import { OrbitControls as OrbitControlsImpl } from 'three-stdlib'; import AnalysisWG from './AnalysisWG'; import ExportCanvas from '@/utils/ExportCanvas'; import { useDataFetcher } from '@/hooks/useDataFetcher'; +import { reproject } from '@/components/textures/ProjectionTexture'; const TransectNotice = () =>{ const {selectTS} = usePlotStore(useShallow(state => ({selectTS: state.selectTS}))) @@ -219,6 +220,10 @@ const Plot = () => { useEffect(()=>{ // Rotates flat back when changing away usePlotStore.setState({rotateFlat: false}) + const { remapTexture } = useGlobalStore.getState(); + if (remapTexture) { + reproject(); + } },[plotType]) useEffect(()=>{ diff --git a/src/components/plots/PointCloud.tsx b/src/components/plots/PointCloud.tsx index dafeffa4..00103978 100644 --- a/src/components/plots/PointCloud.tsx +++ b/src/components/plots/PointCloud.tsx @@ -9,6 +9,8 @@ import { useCoordBounds } from '@/hooks/useCoordBounds'; import { UVCube } from './UVCube'; import { ColumnMeshes } from './TransectMeshes'; +import { usePaddedTextures } from '@/hooks/usePaddedTextures'; + interface PCProps { texture: THREE.Data3DTexture[] | null, colormap: THREE.DataTexture @@ -16,8 +18,9 @@ interface PCProps { const MappingCube = () =>{ - const {dataShape} = useGlobalStore(useShallow(state => ({ + const {dataShape, shape} = useGlobalStore(useShallow(state => ({ dataShape: state.dataShape, + shape: state.shape }))) const {timeScale} = usePlotStore(useShallow(state=> ({ @@ -27,7 +30,7 @@ const MappingCube = () =>{ const globalScale = dataShape[2]/500 const offset = 1/500; //I don't really understand that. But the cube is off by one pixel in each dimension - const depthRatio = useMemo(()=>dataShape[0]/dataShape[2]*timeScale,[dataShape, timeScale]); + const depthRatio = useMemo(()=> (shape && shape.x > 0 ? (shape.z / shape.x) * timeScale : 1),[shape, timeScale]); const shapeRatio = useMemo(()=>dataShape[1]/dataShape[2], [dataShape]) return( @@ -39,10 +42,14 @@ const MappingCube = () =>{ export const PointCloud = ({textures} : {textures:PCProps} )=>{ const { colormap } = textures; - const { flipY, dataShape, textureData} = useGlobalStore(useShallow(state=>({ + const volTexture = usePaddedTextures(textures.texture); + const { flipY, dataShape, textureData, remapTexture, textureArrayDepths, shape } = useGlobalStore(useShallow(state=>({ flipY: state.flipY, dataShape: state.dataShape, - textureData: state.textureData + textureData: state.textureData, + remapTexture: state.remapTexture, + textureArrayDepths: state.textureArrayDepths, + shape: state.shape }))) const {scalePoints, scaleIntensity, pointSize, cScale, cOffset, valueRange, animProg, timeScale, xRange, yRange, zRange, fillValue, @@ -74,19 +81,55 @@ export const PointCloud = ({textures} : {textures:PCProps} )=>{ depth: depth, }; }, [textureData, dataShape]); - // Create buffer geometry - const geometry = useMemo(() => { - const geom = new THREE.BufferGeometry(); - geom.setAttribute('value', new THREE.Uint8BufferAttribute(data as Uint8Array, 1)); - const arrayLength = depth * height * width ; - geom.setDrawRange(0, arrayLength); // This is used to tell it how many data points are needed since we aren't giving it positions. - return geom; - }, [data]); + + const targetWidth = (remapTexture && remapTexture.image) ? remapTexture.image.width : width; + const targetHeight = (remapTexture && remapTexture.image) ? remapTexture.image.height : height; + const is2D = dataShape.length === 2; + const depthRatio = useMemo(()=> (shape && shape.x > 0 ? (shape.z / shape.x) * timeScale : 1),[shape, timeScale]); + + // Create buffer geometries (divided into chunks of max 25M vertices to fit WebGL draw limit) + const geometries = useMemo(() => { + const numPoints = depth * targetHeight * targetWidth; + const maxPoints = 100000000; + const stride = numPoints > maxPoints ? Math.ceil(numPoints / maxPoints) : 1; + + const subNumPoints = Math.floor(numPoints / stride); + const attrData = new Uint8Array(subNumPoints); + const indexData = new Float32Array(subNumPoints); + const originalData = data as Uint8Array; + + let writePtr = 0; + for (let i = 0; i < numPoints; i += stride) { + if (writePtr < subNumPoints) { + attrData[writePtr] = originalData[i] ?? 0; + indexData[writePtr] = i; + writePtr++; + } + } + + const valueAttr = new THREE.Uint8BufferAttribute(attrData, 1); + const indexAttr = new THREE.Float32BufferAttribute(indexData, 1); + + const maxPointsPerDraw = 25000000; + const list = []; + for (let offset = 0; offset < subNumPoints; offset += maxPointsPerDraw) { + const count = Math.min(maxPointsPerDraw, subNumPoints - offset); + const geom = new THREE.BufferGeometry(); + geom.setAttribute('value', valueAttr); + geom.setAttribute('vertexIdx', indexAttr); + geom.setDrawRange(offset, count); + list.push(geom); + } + return list; + }, [data, depth, targetWidth, targetHeight]); const {lonBounds, latBounds} = useCoordBounds() const shaderMaterial = useMemo(()=> (new THREE.ShaderMaterial({ glslVersion: THREE.GLSL3, uniforms: { + map: { value: volTexture }, + remapTexture: { value: remapTexture }, + textureDepths: { value: new THREE.Vector3(textureArrayDepths[2], textureArrayDepths[1], textureArrayDepths[0]) }, maskTexture: {value: maskTexture}, maskValue: {value: maskValue}, latBounds: {value: new THREE.Vector2(deg2rad(latBounds[0]), deg2rad(latBounds[1]))}, @@ -98,24 +141,51 @@ export const PointCloud = ({textures} : {textures:PCProps} )=>{ valueRange: {value: new THREE.Vector2(valueRange[0], valueRange[1])}, scalePoints:{value: scalePoints}, scaleIntensity: {value: scaleIntensity}, - timeScale: {value: timeScale}, + timeScale: {value: depthRatio}, animateProg: {value: animProg}, - shape: {value: new THREE.Vector3(depth, height, width)}, + shape: {value: new THREE.Vector3(depth, targetHeight, targetWidth)}, + nativeShape: {value: new THREE.Vector3(dataShape[0], dataShape[1], dataShape[2])}, flatBounds:{value: new THREE.Vector4(xRange[0], xRange[1], zRange[0], zRange[1])}, vertBounds:{value: new THREE.Vector2(yRange[0], yRange[1])}, fillValue: {value: fillValue?? NaN} }, - vertexShader:disablePointScale ? "#define NO_SCALE\n"+pointVert : pointVert, + defines: { + ...(remapTexture ? { REPROJECT: true } : {}), + ...(flipY ? { FLIP_Y: true } : {}), + ...(is2D ? { IS_2D: true } : {}), + ...(disablePointScale ? { NO_SCALE: true } : {}) + }, + vertexShader: pointVert, fragmentShader:pointFrag, depthWrite: true, depthTest: true, blending:THREE.NoBlending, }) - ),[disablePointScale]); + ),[disablePointScale, is2D, remapTexture, flipY]); useEffect(() => { if (shaderMaterial) { const uniforms = shaderMaterial.uniforms; + uniforms.map.value = volTexture; + uniforms.remapTexture.value = remapTexture; + if (remapTexture) { + shaderMaterial.defines.REPROJECT = true; + } else { + delete shaderMaterial.defines.REPROJECT; + } + if (flipY) { + shaderMaterial.defines.FLIP_Y = true; + } else { + delete shaderMaterial.defines.FLIP_Y; + } + if (is2D) { + shaderMaterial.defines.IS_2D = true; + } else { + delete shaderMaterial.defines.IS_2D; + } + shaderMaterial.needsUpdate = true; + uniforms.shape.value.set(depth, targetHeight, targetWidth); + uniforms.nativeShape.value.set(dataShape[0], dataShape[1], dataShape[2]); uniforms.pointSize.value = pointSize; uniforms.cmap.value = colormap; uniforms.cOffset.value = cOffset; @@ -125,7 +195,7 @@ export const PointCloud = ({textures} : {textures:PCProps} )=>{ uniforms.scaleIntensity.value = scaleIntensity; uniforms.latBounds.value = new THREE.Vector2(deg2rad(latBounds[0]), deg2rad(latBounds[1])) uniforms.lonBounds.value = new THREE.Vector2(deg2rad(lonBounds[0]), deg2rad(lonBounds[1])) - uniforms.timeScale.value = timeScale; + uniforms.timeScale.value = depthRatio; uniforms.animateProg.value = animProg; uniforms.flatBounds.value.set( xRange[0], xRange[1], @@ -137,14 +207,16 @@ export const PointCloud = ({textures} : {textures:PCProps} )=>{ uniforms.fillValue.value = fillValue?? NaN uniforms.maskValue.value = maskValue } - }, [pointSize, colormap, cOffset, cScale, valueRange, scalePoints, scaleIntensity, animProg, timeScale, xRange, yRange, fillValue, zRange, maskValue, lonBounds, latBounds]); + }, [volTexture, remapTexture, flipY, is2D, depthRatio, depth, targetHeight, targetWidth, pointSize, colormap, cOffset, cScale, valueRange, scalePoints, scaleIntensity, animProg, xRange, yRange, fillValue, zRange, maskValue, lonBounds, latBounds]); const tsScale = dataShape[2]/500 return ( - + - + {geometries.map((geom, idx) => ( + + ))} diff --git a/src/components/plots/Sphere.tsx b/src/components/plots/Sphere.tsx index 26a7ae06..cab3b9f1 100644 --- a/src/components/plots/Sphere.tsx +++ b/src/components/plots/Sphere.tsx @@ -31,7 +31,7 @@ export const Sphere = ({textures: propTextures} : {textures: THREE.Data3DTexture analysisArray: state.analysisArray }))) const {colormap, isFlat, dimArrays, dimNames, dimUnits, valueScales, - dataShape, strides, flipY, textureArrayDepths} = useGlobalStore(useShallow(state=>({ + dataShape, strides, flipY, textureArrayDepths, remapTexture} = useGlobalStore(useShallow(state=>({ colormap: state.colormap, isFlat: state.isFlat, dimArrays:state.dimArrays, @@ -41,7 +41,8 @@ export const Sphere = ({textures: propTextures} : {textures: THREE.Data3DTexture dataShape: state.dataShape, strides: state.strides, flipY: state.flipY, - textureArrayDepths: state.textureArrayDepths + textureArrayDepths: state.textureArrayDepths, + remapTexture: state.remapTexture }))) const {animate, animProg, cOffset, cScale, valueRange, selectTS, nanColor, nanTransparency, sphereDisplacement, sphereResolution, @@ -85,6 +86,7 @@ export const Sphere = ({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])}, @@ -102,7 +104,8 @@ export const Sphere = ({textures: propTextures} : {textures: THREE.Data3DTexture fillValue: {value: NaN}, }, defines:{ - IS_FLAT: isFlat + ...(isFlat ? { IS_FLAT: true } : {}), + ...(remapTexture ? { REPROJECT: true } : {}) }, vertexShader: sphereVertex, fragmentShader: sphereFrag, @@ -120,8 +123,16 @@ export const Sphere = ({textures: propTextures} : {textures: THREE.Data3DTexture return mat; },[shaderMaterial]) - const updateMaterial = (material: THREE.ShaderMaterial) =>{ + const updateMaterial = (material: THREE.ShaderMaterial) => { const uniforms = material.uniforms; + uniforms.map.value = textures; + uniforms.remapTexture.value = remapTexture; + if (remapTexture) { + material.defines.REPROJECT = true; + } else { + delete material.defines.REPROJECT; + } + material.needsUpdate = true; uniforms.cmap.value = colormap uniforms.maskValue.value = maskValue uniforms.cOffset.value = cOffset @@ -144,7 +155,7 @@ export const Sphere = ({textures: propTextures} : {textures: THREE.Data3DTexture if (backMaterial){ updateMaterial(backMaterial) } - },[animProg, colormap, cOffset, cScale, animate, lonBounds, latBounds, nanColor, nanTransparency, sphereDisplacement,valueRange, fillValue, maskValue, valueScales]) + },[textures, remapTexture, animProg, colormap, cOffset, cScale, animate, lonBounds, latBounds, nanColor, nanTransparency, sphereDisplacement,valueRange, fillValue, maskValue, valueScales]) function HandleTimeSeries(event: THREE.Intersection){ @@ -190,12 +201,10 @@ export const Sphere = ({textures: propTextures} : {textures: THREE.Data3DTexture } return ( - <> - + + selectTS && HandleTimeSeries(e)}/> + - selectTS && HandleTimeSeries(e)}/> - - ) } diff --git a/src/components/plots/SphereBlocks.tsx b/src/components/plots/SphereBlocks.tsx index 1305169f..23ed63da 100644 --- a/src/components/plots/SphereBlocks.tsx +++ b/src/components/plots/SphereBlocks.tsx @@ -12,12 +12,14 @@ import { usePaddedTextures } from '@/hooks/usePaddedTextures'; const SphereBlocks = ({textures: propTextures} : {textures: THREE.Data3DTexture[] | THREE.DataTexture[] | null}) => { const textures = usePaddedTextures(propTextures); const {colormap, isFlat, valueScales, - dataShape, textureArrayDepths} = useGlobalStore(useShallow(state=>({ + dataShape, textureArrayDepths, flipY, remapTexture} = useGlobalStore(useShallow(state=>({ colormap: state.colormap, isFlat: state.isFlat, valueScales: state.valueScales, dataShape: state.dataShape, textureArrayDepths: state.textureArrayDepths, + flipY: state.flipY, + remapTexture: state.remapTexture }))) const { animProg, cOffset, cScale, nanColor, nanTransparency, sphereDisplacement, offsetNegatives, fillValue, valueRange, maskTexture, maskValue} = usePlotStore(useShallow(state=> ({ animate: state.animate, @@ -81,6 +83,7 @@ const SphereBlocks = ({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])}, @@ -98,7 +101,8 @@ const SphereBlocks = ({textures: propTextures} : {textures: THREE.Data3DTexture[ fillValue: {value: fillValue?? NaN}, }, defines:{ - IS_FLAT: isFlat + ...(isFlat ? { IS_FLAT: true } : {}), + ...(remapTexture ? { REPROJECT: true } : {}) }, vertexShader: sphereBlocksVert, fragmentShader: sphereBlocksFrag, @@ -113,6 +117,13 @@ const SphereBlocks = ({textures: propTextures} : {textures: THREE.Data3DTexture[ if (shaderMaterial){ const uniforms = shaderMaterial.uniforms; uniforms.map.value = textures; + uniforms.remapTexture.value = remapTexture; + if (remapTexture) { + shaderMaterial.defines.REPROJECT = true; + } else { + delete shaderMaterial.defines.REPROJECT; + } + shaderMaterial.needsUpdate = true; uniforms.animateProg.value = animProg uniforms.displaceZero.value = -valueScales.minVal/(valueScales.maxVal-valueScales.minVal) uniforms.displacement.value = sphereDisplacement @@ -127,7 +138,7 @@ const SphereBlocks = ({textures: propTextures} : {textures: THREE.Data3DTexture[ uniforms.maskValue.value = maskValue } invalidate(); - },[animProg, valueScales, sphereDisplacement, colormap, cScale, cOffset, latBounds, lonBounds, valueRange, offsetNegatives, textures, maskValue, fillValue]) + },[animProg, valueScales, sphereDisplacement, colormap, cScale, cOffset, latBounds, lonBounds, valueRange, offsetNegatives, textures, remapTexture, maskValue, fillValue]) const nanMaterial = useMemo(()=>new THREE.MeshBasicMaterial({color:nanColor}),[]) nanMaterial.transparent = true; diff --git a/src/components/textures/ProjectionTexture.ts b/src/components/textures/ProjectionTexture.ts index ff372a14..b2a96f64 100644 --- a/src/components/textures/ProjectionTexture.ts +++ b/src/components/textures/ProjectionTexture.ts @@ -1,6 +1,6 @@ import { useGlobalStore } from '@/GlobalStates/GlobalStore'; import { usePlotStore } from '@/GlobalStates/PlotStore'; -import { ArrayMinMax, linspace } from '@/utils/HelperFuncs'; +import { ArrayMinMax, linspace, ParseExtent } from '@/utils/HelperFuncs'; import { useErrorStore } from '@/GlobalStates/ErrorStore'; import * as THREE from 'three'; @@ -126,11 +126,11 @@ export function SetReprojectionTexture(dimArrays: Array[]){ } export function reproject(resolution: number = 256){ - const {nativeCRS, destCRS} = usePlotStore.getState() + const {nativeCRS, destCRS, plotType} = 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 - const {dimArrays, remapTexture } = useGlobalStore.getState() + const {dimArrays, remapTexture, flipY, dataShape } = useGlobalStore.getState() if (remapTexture) remapTexture.dispose(); const {xIdx, yIdx} = getAxisIndices() @@ -170,7 +170,9 @@ export function reproject(resolution: number = 256){ minY = Math.min(minY, py); maxY = Math.max(maxY, py); } - const aspectRatio = Math.abs(maxX - minX)/ Math.abs(maxY - minY); + const xDiff = Math.abs(maxX - minX); + const yDiff = Math.abs(maxY - minY); + const aspectRatio = yDiff > 0 ? xDiff / yDiff : 1; 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); @@ -184,23 +186,69 @@ export function reproject(resolution: number = 256){ } // ---- 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); - - // ---- Create Rpojection Texture ----// - const data = new Uint16Array(targetWidth * targetHeight * 4); - for (let j = 0; j < targetHeight; j++) { - for (let i = 0; i < targetWidth; i++) { - 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) * 4; - data[idx] = THREE.DataUtils.toHalfFloat(u); - data[idx + 1] = THREE.DataUtils.toHalfFloat(v); - data[idx + 2] = THREE.DataUtils.toHalfFloat(valid); - } + let adjustedResolution = resolution; + + let targetWidth: number; + let targetHeight: number; + let data: Uint16Array; + let xTicks: Array; + let yTicks: Array; + + if (plotType === 'point-cloud') { + targetWidth = width; + targetHeight = height; + xTicks = linspace(minX, maxX, targetWidth); + yTicks = flipY ? linspace(maxY, minY, targetHeight) : linspace(minY, maxY, targetHeight); + data = new Uint16Array(targetWidth * targetHeight * 4); + + const xDiff = Math.abs(maxX - minX); + const yDiff = Math.abs(maxY - minY); + + for (let j = 0; j < targetHeight; j++) { + const lat = yArray[j]; + for (let i = 0; i < targetWidth; i++) { + const lon = xArray[i]; + const [px, py] = proj.forward([lon, lat]); + const valid = (isFinite(px) && isFinite(py)) ? 1 : 0; + + const u = xDiff > 0 ? (px - minX) / xDiff : 0; + const v = yDiff > 0 ? (py - minY) / yDiff : 0; + + 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); + } + } + } else { + targetWidth = Math.ceil(adjustedResolution * aspectRatio); + targetHeight = adjustedResolution; + xTicks = linspace(minX, maxX, targetWidth); + yTicks = flipY ? linspace(maxY, minY, targetHeight) : linspace(minY, maxY, targetHeight); + + // Detect if coordinate axes are descending + const isXDescending = xArray.length > 1 ? xArray[0] > xArray[xArray.length - 1] : false; + const isYDescending = yArray.length > 1 ? yArray[0] > yArray[yArray.length - 1] : false; + + data = new Uint16Array(targetWidth * targetHeight * 4); + const xRangeDiff = xMax - xMin; + const yRangeDiff = yMax - yMin; + for (let j = 0; j < targetHeight; j++) { + for (let i = 0; i < targetWidth; i++) { + const [lon, lat, valid] = safeInverse(proj, [xTicks[i], yTicks[j]]); + const u = xRangeDiff > 0 ? (isXDescending ? (xMax - lon) / xRangeDiff : (lon - xMin) / xRangeDiff) : 0; + const v = yRangeDiff > 0 ? (isYDescending ? (yMax - lat) / yRangeDiff : (lat - yMin) / yRangeDiff) : 0; + + // Check boundary bounds to avoid displaying clamped blocks outside the dataset area + const inBounds = lon >= xMin && lon <= xMax && lat >= yMin && lat <= yMax; + const validVal = (valid === 1 && inBounds) ? 1 : 0; + + 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(validVal); + } + } } const texture = new THREE.DataTexture( data, @@ -225,10 +273,11 @@ export function reproject(resolution: number = 256){ newAxisDimArrays[yIdx] = yTicks; const newAxisDimUnits = [...axisDimUnits]; + const targetUnits = (crsCheck.oProj as any)?.units; //@ts-ignore At this point these are all valid - newAxisDimUnits[xIdx] = crsCheck.oProj.units; + newAxisDimUnits[xIdx] = targetUnits; //@ts-ignore At this point these are all valid - newAxisDimUnits[yIdx] = crsCheck.oProj.units; + newAxisDimUnits[yIdx] = targetUnits; const newAxisDimNames = [...axisDimNames]; newAxisDimNames[xIdx] = 'X'; @@ -245,5 +294,6 @@ export function reproject(resolution: number = 256){ xSlice: [0, null], ySlice: [0, null] }) + ParseExtent(newAxisDimUnits, newAxisDimArrays); } \ No newline at end of file diff --git a/src/components/textures/shaders/pointVertex.glsl b/src/components/textures/shaders/pointVertex.glsl index b5108cdf..1b688d9d 100644 --- a/src/components/textures/shaders/pointVertex.glsl +++ b/src/components/textures/shaders/pointVertex.glsl @@ -1,118 +1,175 @@ -attribute float value; - -out float vValue; - -uniform sampler2D maskTexture; - -uniform float pointSize; -uniform bool scalePoints; -uniform float scaleIntensity; -uniform vec2 valueRange; -uniform float timeScale; -uniform float animateProg; -uniform vec4 flatBounds; -uniform vec2 vertBounds; -uniform vec3 shape; -uniform float fillValue; -uniform vec2 latBounds; -uniform vec2 lonBounds; -uniform int maskValue; - -#define PI 3.1415925 - -vec3 computePosition(int vertexID) { - int depth = int(shape.x); - int height = int(shape.y); - int width = int(shape.z); - - int sliceSize = width * height; - - int z = vertexID / sliceSize; - int y = (vertexID % sliceSize) / width; - int x = vertexID % width; - - float px = (float(x) - (float(width)/2.)) / 500.; - float py = (float(y) - (float(height)/2.)) / 500.; - float pz = (float(z) - (float(depth)/2.)) /500.; - - return vec3(px * 2.0, py * 2.0, pz * 2.0); -} -vec2 giveUV(int vertexID){ - int height = int(shape.y); - int width = int(shape.z); - - int sliceSize = width * height; - int y = (vertexID % sliceSize) / width; - int x = vertexID % width; - float u = float(x)/float(width); - float v = float(y)/float(height); - - return vec2(u, v); -} -vec2 realCoords(vec2 uv){ - vec2 normalizedLon = lonBounds/2./PI+0.5; - vec2 normalizedLat = latBounds/PI+0.5; - float lonScale = normalizedLon.y-normalizedLon.x; - float latScale = normalizedLat.y-normalizedLat.x; - - float u = uv.x * lonScale + normalizedLon.x; - float v = uv.y * latScale + normalizedLat.x; - - return vec2(u, v); -} - -void main() { - if (maskValue != 0 ){ // If using a mask, quick check if vertex is masked out before doing additional rendering - vec2 newV = realCoords(giveUV(gl_VertexID)); - float mask = texture(maskTexture, newV).r; - bool cond = maskValue == 1 ? mask< 0.5 : mask>=0.5; - if (cond){ // Masked out. Move off screen - gl_Position = vec4(2.0, 2.0, 2.0, 1.0); - return; - } - } - - vValue = float(value)/255.; - vec3 scaledPos = computePosition(gl_VertexID); - float depthSize = float(shape.x)/500.; - - scaledPos.z += depthSize; - scaledPos.z = mod(scaledPos.z + animateProg*depthSize*2., depthSize*2.); - scaledPos.z -= depthSize; - - scaledPos.z *= timeScale; - gl_Position = projectionMatrix * modelViewMatrix * vec4(scaledPos, 1.0); - - #ifndef NO_SCALE - float pointScale = pointSize/gl_Position.w; - pointScale = scalePoints ? pointScale*pow(vValue,scaleIntensity) : pointScale; - - if (value == 255. || (pointScale*gl_Position.w < 0.75 && scalePoints)){ //Hide points that are invisible or get too small when scalled - gl_Position = vec4(2.0, 2.0, 2.0, 1.0); - } - gl_PointSize = pointScale; - #else - gl_PointSize = 1.; - #endif - if (vValue < valueRange.x || vValue > valueRange.y){ //Hide points that are outside of value range - gl_Position = vec4(2.0, 2.0, 2.0, 1.0); - } - - float scaleX = float(shape.z) / 500.0; //width scaling - float scaleY = float(shape.y) / 500.0; //height scaling - float scaleZ = float(shape.x) / 500.0; //depth scaling - - vec2 scaledXBounds = vec2(flatBounds.x, flatBounds.y) * scaleX; - vec2 scaledZBounds = vec2(flatBounds.z, flatBounds.w) * scaleZ * timeScale; - vec2 scaledYBounds = vec2(vertBounds.x, vertBounds.y) * scaleY; - - bool xCheck = scaledPos.x < scaledXBounds.x || scaledPos.x > scaledXBounds.y; - bool zCheck = scaledPos.z < scaledZBounds.x || scaledPos.z > scaledZBounds.y; - bool yCheck = scaledPos.y < scaledYBounds.x || scaledPos.y > scaledYBounds.y; - bool fillCheck = abs(vValue - fillValue) < 0.005; - - if (xCheck || zCheck || yCheck || fillCheck){ //Hide points that are clipped - gl_Position = vec4(2.0, 2.0, 2.0, 1.0); - } - -} +attribute float value; +in float vertexIdx; + +out float vValue; + +uniform sampler2D maskTexture; + +uniform float pointSize; +uniform bool scalePoints; +uniform float scaleIntensity; +uniform vec2 valueRange; +uniform float timeScale; +uniform float animateProg; +uniform vec4 flatBounds; +uniform vec2 vertBounds; +uniform vec3 shape; +uniform vec3 nativeShape; +uniform float fillValue; +uniform vec2 latBounds; +uniform vec2 lonBounds; +uniform int maskValue; + +#define PI 3.1415925 + +#ifdef REPROJECT + uniform sampler2D remapTexture; +#endif + +vec3 computePosition(int vertexID) { + int depth = int(shape.x); + int height = int(shape.y); + int width = int(shape.z); + + int sliceSize = width * height; + + int z = vertexID / sliceSize; + int y = (vertexID % sliceSize) / width; + int x = vertexID % width; + + #ifdef REPROJECT + // Get the reprojected normalized coordinates (u, v) from remapTexture + vec2 uv = vec2((float(x) + 0.5) / float(width), (float(y) + 0.5) / float(height)); + vec3 remap = texture(remapTexture, uv).rgb; + + float nativeWidth = nativeShape.z; + float px = (remap.r - 0.5) * (nativeWidth / 500.0); + + float lonRange = abs(lonBounds.y - lonBounds.x); + float latRange = abs(latBounds.y - latBounds.x); + float aspectRatio = (latRange > 0.0 && lonRange > 0.0) ? (lonRange / latRange) : 1.0; + + float py = (remap.g - 0.5) * (nativeWidth / 500.0) / aspectRatio; + #else + float px = (float(x) - (float(width)/2.)) / 500.; + float py = (float(y) - (float(height)/2.)) / 500.; + #endif + #ifndef REPROJECT + #ifdef FLIP_Y + py = -py; + #endif + #endif + float pz = (float(depth) > 1.0) ? (float(z) / (float(depth) - 1.0) - 0.5) * (nativeShape.z / 500.0) : 0.0; + + return vec3(px * 2.0, py * 2.0, pz * 2.0); +} + +vec2 giveUV(int vertexID){ + int height = int(shape.y); + int width = int(shape.z); + + int sliceSize = width * height; + int y = (vertexID % sliceSize) / width; + int x = vertexID % width; + float u = float(x)/float(width); + float v = float(y)/float(height); + + return vec2(u, v); +} + +vec2 realCoords(vec2 uv){ + vec2 normalizedLon = lonBounds/2./PI+0.5; + vec2 normalizedLat = latBounds/PI+0.5; + float lonScale = normalizedLon.y-normalizedLon.x; + float latScale = normalizedLat.y-normalizedLat.x; + + float u = uv.x * lonScale + normalizedLon.x; + float v = uv.y * latScale + normalizedLat.x; + + return vec2(u, v); +} + +void main() { + int originalID = int(vertexIdx); + + if (maskValue != 0 ){ // If using a mask, quick check if vertex is masked out before doing additional rendering + vec2 newV = realCoords(giveUV(originalID)); + float mask = texture(maskTexture, newV).r; + bool cond = maskValue == 1 ? mask< 0.5 : mask>=0.5; + if (cond){ // Masked out. Move off screen + gl_Position = vec4(2.0, 2.0, 2.0, 1.0); + return; + } + } + + // Set the point value directly from attribute (no 3D texture sampling needed!) + vValue = float(value)/255.; + + #ifdef REPROJECT + // For reprojected points, we must check if they are in the valid projection area. + int height = int(shape.y); + int width = int(shape.z); + int sliceSize = width * height; + int y = (originalID % sliceSize) / width; + int x = originalID % width; + + vec2 uv = vec2((float(x) + 0.5) / float(width), (float(y) + 0.5) / float(height)); + vec3 remap = texture(remapTexture, uv).rgb; + if (remap.b < 0.5) { + gl_Position = vec4(2.0, 2.0, 2.0, 1.0); + return; + } + #endif + + vec3 scaledPos = computePosition(originalID); + float depthSize = (float(shape.x) > 1.0) ? (nativeShape.z / 500.0) : 0.001; + + scaledPos.z += depthSize; + scaledPos.z = mod(scaledPos.z + animateProg*depthSize*2., depthSize*2.); + scaledPos.z -= depthSize; + + scaledPos.z *= timeScale; + gl_Position = projectionMatrix * modelViewMatrix * vec4(scaledPos, 1.0); + + #ifndef NO_SCALE + float pointScale = pointSize/gl_Position.w; + pointScale = scalePoints ? pointScale*pow(vValue,scaleIntensity) : pointScale; + + if (value == 255. || (pointScale*gl_Position.w < 0.75 && scalePoints)){ //Hide points that are invisible or get too small when scaled + gl_Position = vec4(2.0, 2.0, 2.0, 1.0); + } + gl_PointSize = pointScale; + #else + gl_PointSize = 1.; + #endif + if (vValue < valueRange.x || vValue > valueRange.y){ //Hide points that are outside of value range + gl_Position = vec4(2.0, 2.0, 2.0, 1.0); + } + + #ifdef REPROJECT + float nativeWidth = nativeShape.z; + float lonRange = abs(lonBounds.y - lonBounds.x); + float latRange = abs(latBounds.y - latBounds.x); + float aspectRatio = (latRange > 0.0 && lonRange > 0.0) ? (lonRange / latRange) : 1.0; + + float scaleX = nativeWidth / 500.0; + float scaleY = (nativeWidth / 500.0) / aspectRatio; + #else + float scaleX = float(shape.z) / 500.0; //width scaling + float scaleY = float(shape.y) / 500.0; //height scaling + #endif + float scaleZ = (float(shape.x) > 1.0) ? (nativeShape.z / 500.0) : 0.001; //depth scaling + + vec2 scaledXBounds = vec2(flatBounds.x, flatBounds.y) * scaleX; + vec2 scaledZBounds = vec2(flatBounds.z, flatBounds.w) * scaleZ * timeScale; + vec2 scaledYBounds = vec2(vertBounds.x, vertBounds.y) * scaleY; + + bool xCheck = scaledPos.x < scaledXBounds.x || scaledPos.x > scaledXBounds.y; + bool zCheck = scaledPos.z < scaledZBounds.x || scaledPos.z > scaledZBounds.y; + bool yCheck = scaledPos.y < scaledYBounds.x || scaledPos.y > scaledYBounds.y; + bool fillCheck = abs(vValue - fillValue) < 0.005; + + if (xCheck || zCheck || yCheck || fillCheck){ //Hide points that are clipped + gl_Position = vec4(2.0, 2.0, 2.0, 1.0); + } +} diff --git a/src/components/textures/shaders/sphereBlocksVert.glsl b/src/components/textures/shaders/sphereBlocksVert.glsl index 963adc7e..cc71279f 100644 --- a/src/components/textures/shaders/sphereBlocksVert.glsl +++ b/src/components/textures/shaders/sphereBlocksVert.glsl @@ -9,6 +9,7 @@ attribute vec2 instanceUV; #endif uniform sampler2D maskTexture; uniform vec3 textureDepths; +uniform sampler2D remapTexture; uniform float displaceZero; uniform float displacement; @@ -93,11 +94,28 @@ void main() { int zStepSize = int(textureDepths.y) * int(textureDepths.x); int yStepSize = int(textureDepths.x); #ifdef IS_FLAT - ivec2 idx = clamp(ivec2(instanceUV * textureDepths.xy), ivec2(0), ivec2(textureDepths.xy) - 1); + 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 + ivec2 idx = clamp(ivec2(texCoord.xy * textureDepths.xy), ivec2(0), ivec2(textureDepths.xy) - 1); int textureIdx = idx.y * yStepSize + idx.x; - vec2 localCoord = instanceUV * (textureDepths.xy); // Scale up + vec2 localCoord = texCoord.xy * (textureDepths.xy); // Scale up #else 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 ivec3 idx = clamp(ivec3(texCoord * textureDepths), ivec3(0), ivec3(textureDepths) - 1); // Ivec3 is like running a "floor" operation on all three at once. The clamp is because the very last idx is OOR int textureIdx = idx.z * zStepSize + idx.y * yStepSize + idx.x; vec3 localCoord = texCoord * textureDepths; // Scale up diff --git a/src/components/textures/shaders/sphereFrag.glsl b/src/components/textures/shaders/sphereFrag.glsl index 4face350..7e66f1e1 100644 --- a/src/components/textures/shaders/sphereFrag.glsl +++ b/src/components/textures/shaders/sphereFrag.glsl @@ -12,6 +12,7 @@ in vec3 aPosition; uniform sampler2D maskTexture; uniform sampler2D cmap; uniform vec3 textureDepths; +uniform sampler2D remapTexture; uniform float cOffset; uniform float cScale; @@ -77,10 +78,24 @@ void main(){ vec2 texCoord = giveUV(aPosition); bool inBounds = all(greaterThanEqual(texCoord, vec2(0.0))) && all(lessThanEqual(texCoord, vec2(1.0))); + #ifdef REPROJECT + if (inBounds) { + vec3 remap = texture(remapTexture, texCoord.xy).rgb; + texCoord.xy = remap.rg; + if (remap.b < 0.5) inBounds = false; + } + #endif #else vec2 sampleCoord = giveUV(aPosition); bool inBounds = all(greaterThanEqual(sampleCoord, vec2(0.0))) && all(lessThanEqual(sampleCoord, vec2(1.0))); + #ifdef REPROJECT + if (inBounds) { + vec3 remap = texture(remapTexture, sampleCoord.xy).rgb; + sampleCoord.xy = remap.rg; + if (remap.b < 0.5) inBounds = false; + } + #endif #endif if (inBounds) { diff --git a/src/components/ui/MainPanel/AdjustPlot.tsx b/src/components/ui/MainPanel/AdjustPlot.tsx index 1e3a3b86..e0842be3 100644 --- a/src/components/ui/MainPanel/AdjustPlot.tsx +++ b/src/components/ui/MainPanel/AdjustPlot.tsx @@ -756,7 +756,7 @@ const AdjustPlot = () => { {plotType === 'sphere' && } {(plotType === 'volume' || plotType === 'point-cloud') && } {plotType === 'flat' && } - {['flat','volume'].includes(plotType) && } + diff --git a/src/hooks/useCoordBounds.tsx b/src/hooks/useCoordBounds.tsx index 89df3f2e..1756ab72 100644 --- a/src/hooks/useCoordBounds.tsx +++ b/src/hooks/useCoordBounds.tsx @@ -3,6 +3,8 @@ import { usePlotStore } from "@/GlobalStates/PlotStore"; import { useShallow } from "zustand/shallow"; +import { useGlobalStore } from "@/GlobalStates/GlobalStore"; + export const useCoordBounds = ()=>{ const {lonExtent, latExtent, lonResolution, latResolution} = usePlotStore(useShallow(state=>({ lonExtent: state.lonExtent, @@ -10,13 +12,21 @@ export const useCoordBounds = ()=>{ lonResolution: state.lonResolution, latResolution: state.latResolution, }))) + const {flipY} = useGlobalStore(useShallow(state => ({ + flipY: state.flipY + }))) const [lonBounds, latBounds] = useMemo(()=>{ //The bounds for the shader. It takes the middle point of the furthest coordinate and adds the distance to edge of pixel const newLatStep = latResolution/2; const newLonStep = lonResolution/2; - const newLonBounds = [Math.max(lonExtent[0]-newLonStep, -180), Math.min(lonExtent[1]+newLonStep, 180)] - const newLatBounds = [Math.max(latExtent[0]-newLatStep, -90), Math.min(latExtent[1]+newLatStep, 90)] + const minLat = Math.min(latExtent[0], latExtent[1]); + const maxLat = Math.max(latExtent[0], latExtent[1]); + const minLon = Math.min(lonExtent[0], lonExtent[1]); + const maxLon = Math.max(lonExtent[0], lonExtent[1]); + const newLonBounds = [Math.max(minLon-newLonStep, -180), Math.min(maxLon+newLonStep, 180)] + let newLatBounds = [Math.max(minLat-newLatStep, -90), Math.min(maxLat+newLatStep, 90)] + newLatBounds = flipY ? [newLatBounds[1], newLatBounds[0]] : newLatBounds return [newLonBounds, newLatBounds] - },[latExtent, lonExtent, lonResolution, latResolution]) + },[latExtent, lonExtent, lonResolution, latResolution, flipY]) return { lonBounds,latBounds } diff --git a/src/hooks/useDataFetcher.tsx b/src/hooks/useDataFetcher.tsx index 224ee1ed..322b9270 100644 --- a/src/hooks/useDataFetcher.tsx +++ b/src/hooks/useDataFetcher.tsx @@ -60,10 +60,14 @@ export const useDataFetcher = () => { try { //---- Texture Cleanup ----// if (textures) { - textures.forEach((tex) => { - tex.dispose(); - if (tex.source) (tex.source as any).data = null; - }); + const oldTextures = textures; + setTimeout(() => { + oldTextures.forEach((tex) => { + tex.dispose(); + if (tex.source) (tex.source as any).data = null; + }); + }, 0); + setTextures(null); } //----- TS Cleanup ----// useGlobalStore.setState({timeSeries:{}, dimCoords:{}}) diff --git a/src/utils/HelperFuncs.ts b/src/utils/HelperFuncs.ts index 63b251af..2654fa91 100644 --- a/src/utils/HelperFuncs.ts +++ b/src/utils/HelperFuncs.ts @@ -225,21 +225,26 @@ export function ParseExtent(dimUnits: string[], dimArrays: any[][]){ const maxLonIdx = xSlice[1] !== null ? xSlice[1] - 1 : xArray.length - 1; let maxLon = Number(xArray[maxLonIdx]); - if (maxLon > 180){ - maxLon -= 180 - minLon -= 180 + let finalMinLon = Math.min(minLon, maxLon); + let finalMaxLon = Math.max(minLon, maxLon); + let finalMinLat = Math.min(minLat, maxLat); + let finalMaxLat = Math.max(minLat, maxLat); + + if (finalMaxLon > 180){ + finalMaxLon -= 180 + finalMinLon -= 180 usePlotStore.setState({is360Deg:true}) } else{ usePlotStore.setState({is360Deg:false}) } - setLonExtent([minLon, maxLon]) - setLatExtent([minLat, maxLat]) + setLonExtent([finalMinLon, finalMaxLon]) + setLatExtent([finalMinLat, finalMaxLat]) const latRes = Math.abs(Number(yArray[1] ?? 0) - Number(yArray[0] ?? 0)) || 1; const lonRes = Math.abs(Number(xArray[1] ?? 0) - Number(xArray[0] ?? 0)) || 1; setLonResolution(lonRes) setLatResolution(latRes) - setOriginalExtent(new THREE.Vector4(minLon, maxLon, minLat, maxLat)) + setOriginalExtent(new THREE.Vector4(finalMinLon, finalMaxLon, finalMinLat, finalMaxLat)) } else{ setLonExtent([-180,180]) @@ -286,7 +291,7 @@ function DecompressArray(compressed : Uint8Array){ } export function GetCurrentArray(overrideStore?:string){ - const { variable, is4D, idx4D, initStore, strides, dataShape, setStatus }= useGlobalStore.getState() + const { variable, is4D, idx4D, initStore, strides, dataShape }= useGlobalStore.getState() const { arraySize, currentChunks, ndSlices } = useZarrStore.getState() const {cache} = useCacheStore.getState(); const store = overrideStore ? overrideStore : initStore @@ -297,9 +302,7 @@ export function GetCurrentArray(overrideStore?:string){ if (cache.has(cacheBase)){ const chunk = cache.get(cacheBase) const compressed = chunk?.compressed - setStatus(compressed ? "Decompressing data..." : null) const thisData = compressed ? DecompressArray(chunk.data) : chunk.data - setStatus(null) return thisData } else{ @@ -331,7 +334,6 @@ export function GetCurrentArray(overrideStore?:string){ } } } - setStatus(null) return typedArray } }