diff --git a/src/components/plots/DataCube.tsx b/src/components/plots/DataCube.tsx index a5cf9c05..bc97a87a 100644 --- a/src/components/plots/DataCube.tsx +++ b/src/components/plots/DataCube.tsx @@ -8,6 +8,7 @@ import { invalidate, useFrame } from '@react-three/fiber'; import { deg2rad } from '@/utils/HelperFuncs'; import { useCoordBounds } from '@/hooks/useCoordBounds'; import { UVCube } from '@/components/plots' +import { ColumnMeshes } from './TransectMeshes'; interface DataCubeProps { volTexture: THREE.Data3DTexture[] | THREE.DataTexture[] | null, @@ -110,6 +111,7 @@ export const DataCube = ({ volTexture }: DataCubeProps ) => { }) return ( + diff --git a/src/components/plots/FlatMap.tsx b/src/components/plots/FlatMap.tsx index a5909fe1..9d86eca7 100644 --- a/src/components/plots/FlatMap.tsx +++ b/src/components/plots/FlatMap.tsx @@ -13,7 +13,7 @@ import { coarsenFlatArray, GetCurrentArray, GetTimeSeries, parseUVCoords, deg2ra import { evaluate_cmap } from 'js-colormaps-es'; import { useCoordBounds } from '@/hooks/useCoordBounds'; import { GetFrag } from '../textures'; - +import { SquareMeshes } from './TransectMeshes'; interface InfoSettersProps{ setLoc: React.Dispatch>; setShowInfo: React.Dispatch>; @@ -138,7 +138,9 @@ const FlatMap = ({textures, infoSetters} : {textures : THREE.DataTexture[] | THR const tsID = `${dimCoords[0]}_${dimCoords[1]}` const tsObj = { color:evaluate_cmap(getColorIdx()/10,"Paired"), - data:tempTS + data:tempTS, + normal, + uv: tsUV, } incrementColorIdx(); updateTimeSeries({ [tsID] : tsObj}) @@ -203,6 +205,7 @@ const FlatMap = ({textures, infoSetters} : {textures : THREE.DataTexture[] | THR return ( <> + { uniforms.maskValue.value = maskValue } }, [pointSize, colormap, cOffset, cScale, valueRange, scalePoints, scaleIntensity, animProg, timeScale, xRange, yRange, fillValue, zRange, maskValue, lonBounds, latBounds]); - return ( - - - + const tsScale = dataShape[2]/500 + return ( + + + - ); + + + + + ); } \ No newline at end of file diff --git a/src/components/plots/Sphere.tsx b/src/components/plots/Sphere.tsx index 52464621..a9eb589e 100644 --- a/src/components/plots/Sphere.tsx +++ b/src/components/plots/Sphere.tsx @@ -8,7 +8,7 @@ import { parseUVCoords, GetTimeSeries, GetCurrentArray, deg2rad } from '@/utils/ import { evaluate_cmap } from 'js-colormaps-es'; import { useCoordBounds } from '@/hooks/useCoordBounds' import { GetFrag, GetVert } from '../textures'; - +import { SquareMeshes } from './TransectMeshes'; function XYZtoRemap(xyz : THREE.Vector3, latBounds: number[], lonBounds : number[]){ const lon = Math.atan2(xyz.z,xyz.x) const lat = Math.asin(xyz.y); @@ -153,7 +153,9 @@ export const Sphere = ({textures} : {textures: THREE.Data3DTexture[] | THREE.Dat const tsID = `${dimCoords[0]}_${dimCoords[1]}` const tsObj = { color:evaluate_cmap(getColorIdx()/10,"Paired"), - data:tempTS + data:tempTS, + normal, + uv: tsUV, } incrementColorIdx(); updateTimeSeries({ [tsID] : tsObj}) @@ -177,6 +179,9 @@ export const Sphere = ({textures} : {textures: THREE.Data3DTexture[] | THREE.Dat return ( <> + + + selectTS && HandleTimeSeries(e)}/> diff --git a/src/components/plots/TransectMeshes.tsx b/src/components/plots/TransectMeshes.tsx new file mode 100644 index 00000000..5f35d051 --- /dev/null +++ b/src/components/plots/TransectMeshes.tsx @@ -0,0 +1,183 @@ +import React, {useEffect, useMemo} from 'react' +import * as THREE from 'three' +import { usePlotStore } from '@/GlobalStates/PlotStore' +import { useGlobalStore } from '@/GlobalStates/GlobalStore' +import { useShallow } from 'zustand/shallow' +import { deg2rad, parseUVCoords } from '@/utils/HelperFuncs' +import { useCoordBounds } from '@/hooks/useCoordBounds' + +function remapToXYZ(uv: THREE.Vector2, latBounds: number[], lonBounds: number[]): THREE.Vector3 { + const u = 1 - uv.x; + const v = uv.y; + const lon = u * (deg2rad(lonBounds[1]) - deg2rad(lonBounds[0])) + deg2rad(lonBounds[0]); + const lat = v * (deg2rad(latBounds[1]) - deg2rad(latBounds[0])) + deg2rad(latBounds[0]); + return new THREE.Vector3( + Math.cos(lat) * Math.cos(lon), + Math.sin(lat), + Math.cos(lat) * Math.sin(lon) + ); +} + +function normalToPos(uv: THREE.Vector2, normal:THREE.Vector3, ratios:{depthRatio:number, aspectRatio:number}): THREE.Vector3{ + let posZ, posY, posX: number; + const {aspectRatio, depthRatio} = ratios; + if (Math.abs(normal.z) == 1){ + const flip = normal.z < 0; + const x = flip ? (1-uv.x)-0.5: (uv.x-0.5) + posX = x*2; + posY = (uv.y-0.5)*2*aspectRatio; + posZ = 0; + } else if (Math.abs(normal.y) == 1){ + const flip = normal.y > 0; + const y = flip ? (1-uv.y)-0.5: (uv.y-0.5) + posX = (uv.x-0.5)*2; + posY = 0; + posZ = y*Math.max(depthRatio,2); + } else { + const flip = normal.x > 0; + const x = flip ? (1-uv.x)-0.5: (uv.x-0.5) + posX = 0; + posY = (uv.y-0.5)*2*aspectRatio; + posZ = x*Math.max(depthRatio,2); + } + return new THREE.Vector3(posX, posY, posZ) +} + +function normalToScale(normal:THREE.Vector3, ratios:{depthRatio:number, aspectRatio:number}, steps:{xSteps:number, ySteps:number, zSteps:number}){ + let scaleZ, scaleY, scaleX: number; + const {xSteps,ySteps,zSteps} = steps; + const {aspectRatio, depthRatio} = ratios; + if (Math.abs(normal.z) == 1){ + scaleX = 2/xSteps; + scaleY = 2*aspectRatio/ySteps; + scaleZ = Math.max(depthRatio,2); + } else if (Math.abs(normal.y) == 1){ + scaleX = 2/xSteps; + scaleY = 2*aspectRatio; + scaleZ = 2*Math.max(depthRatio,2)/zSteps; + } else{ + scaleX = 2; + scaleY = 2*aspectRatio/ySteps; + scaleZ = 2*Math.max(depthRatio,2)/zSteps; + } + return new THREE.Vector3(scaleX, scaleY, scaleZ); +} + +export const SquareMeshes = () => { + const {timeSeries, dataShape, shape, flipY} = useGlobalStore(useShallow(state=>({ + timeSeries:state.timeSeries, + dataShape: state.dataShape, + shape: state.shape, flipY:state.flipY + }))) + const {plotType} = usePlotStore(useShallow(state=>({ + plotType: state.plotType + }))) + const {lonBounds, latBounds} = useCoordBounds() + const meshes: THREE.Mesh[] = useMemo(() =>{ + const meshes = [] + const dataLen = dataShape.length; + const xSteps = dataShape[dataLen-1]; + const ySteps = dataShape[dataLen-2]; + const normedXExtent = (lonBounds[1]-lonBounds[0])/360 + const normedYExtent = (latBounds[1]-latBounds[0])/180 + const isSphere = plotType == "sphere"; + const aspect = shape.y/shape.x; + for (const [_tsID, tsObj] of Object.entries(timeSeries)){ + const {normal, uv, color} = tsObj + if (normal.z != 1) break; // It should never be, but just in case, flat versions only do time. Skip all of these. + let geometry = new THREE.PlaneGeometry(1, 1) + // Color from 0-255 to 0-1 range + const thisColor = color.map((c: number) => Math.pow((c/255), 2.2)) // Gamma correct the color + const material = new THREE.MeshBasicMaterial({color: new THREE.Color(...thisColor)}); + material.side = THREE.DoubleSide; // For flipY or to see it on otherside of sphere after clipping values + material.needsUpdate = true; + const mesh = new THREE.Mesh(geometry, material) + let position: THREE.Vector3; + const uvX = (Math.floor(uv.x * xSteps)+0.5)/xSteps; + const uvY = (Math.floor(uv.y * ySteps)+0.5)/ySteps; + if (isSphere){ + const circum = 2*Math.PI; + const xScale = circum/xSteps * normedXExtent; + const yScale = circum/2/ySteps * normedYExtent; + const xScaler = Math.cos((uvY - 0.5) * Math.PI); + position = remapToXYZ(new THREE.Vector2(uvX, uvY), latBounds, lonBounds) + // Rotate the plane where position is also normal vector + mesh.lookAt(position.x, position.y, position.z) + geometry.scale(xScale*xScaler, yScale, 1) + } + else{ + const sqScale = 2/xSteps + const posX = (uvX-0.5)*2; + const posY = (uvY-0.5)*2*aspect; + position = new THREE.Vector3(posX, posY, 0.001) + geometry.scale(sqScale,sqScale,1) + } + mesh.position.set(position.x, position.y, position.z) + meshes.push(mesh) + } + return meshes + }, [timeSeries, plotType, latBounds, lonBounds]) + useEffect(() => { + return () => { + meshes.forEach(mesh => { + mesh.geometry.dispose() + //@ts-ignore TS thiunks this is a different material type + mesh.material.dispose() + }); + };}, [meshes] + ); + return ( + <> + {meshes.map((mesh, idx) => )} + + ) +} + +export const ColumnMeshes = () => { + const {timeSeries, dataShape, shape} = useGlobalStore(useShallow(state=>({ + timeSeries:state.timeSeries, + dataShape: state.dataShape, + shape: state.shape + }))) + const {plotType} = usePlotStore(useShallow(state=>({ + plotType: state.plotType + }))) + + const meshes: THREE.Mesh[] = useMemo(()=>{ + const meshes: THREE.Mesh[] = [] + const dataLen = dataShape.length; + const xSteps = dataShape[dataLen-1]; + const ySteps = dataShape[dataLen-2]; + const zSteps = dataShape[dataLen-3]; + const aspectRatio = dataShape[dataLen-2]/dataShape[dataLen-1] + const depthRatio = dataShape[dataLen-3]/dataShape[dataLen-1] + for (const [tsID, tsObj] of Object.entries(timeSeries)){ + const {normal, uv, color} = tsObj + const position = normalToPos(uv, normal, {aspectRatio,depthRatio}) + const meshScale = normalToScale(normal, {aspectRatio, depthRatio}, {xSteps, ySteps, zSteps}) + const thisColor = color.map((c: number) => Math.pow((c/255), 2.2)) // Gamma correct the color + const material = new THREE.MeshBasicMaterial({color: new THREE.Color(...thisColor)}) + let geometry = new THREE.BoxGeometry(1,1,1) + geometry.scale(...meshScale.toArray()) + const mesh = new THREE.Mesh(geometry, material) + mesh.position.copy(position) + meshes.push(mesh) + } + return meshes + + },[timeSeries, plotType]) + useEffect(() => { + return () => { + meshes.forEach(mesh => { + mesh.geometry.dispose() + //@ts-ignore TS thiunks this is a different material type + mesh.material.dispose() + }); + };}, [meshes] + ); + return ( + <> + {meshes.map((mesh, idx) => )} + + ) +} diff --git a/src/components/plots/UVCube.tsx b/src/components/plots/UVCube.tsx index c31db838..301caa68 100644 --- a/src/components/plots/UVCube.tsx +++ b/src/components/plots/UVCube.tsx @@ -122,6 +122,8 @@ export const UVCube = ( {scale} : {scale?:THREE.Vector3} )=>{ const tsID = `${dimCoords[0]}_${dimCoords[1]}` const tsObj = { color:evaluate_cmap(getColorIdx()/10,"Paired"), + normal, + uv, data:tempTS } updateTimeSeries({ [tsID] : tsObj}) diff --git a/src/components/textures/shaders/pointVertex.glsl b/src/components/textures/shaders/pointVertex.glsl index 738c2842..b5108cdf 100644 --- a/src/components/textures/shaders/pointVertex.glsl +++ b/src/components/textures/shaders/pointVertex.glsl @@ -33,7 +33,7 @@ vec3 computePosition(int vertexID) { float px = (float(x) - (float(width)/2.)) / 500.; float py = (float(y) - (float(height)/2.)) / 500.; - float pz = (float(z) - (float(depth )/2.)) /500.; + float pz = (float(z) - (float(depth)/2.)) /500.; return vec3(px * 2.0, py * 2.0, pz * 2.0); } @@ -62,12 +62,11 @@ vec2 realCoords(vec2 uv){ } void main() { - vec2 testV = giveUV(gl_VertexID); - if (maskValue != 0 ){ + 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){ + if (cond){ // Masked out. Move off screen gl_Position = vec4(2.0, 2.0, 2.0, 1.0); return; } @@ -115,8 +114,5 @@ void main() { 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/utils/ExportCanvas.tsx b/src/utils/ExportCanvas.tsx index c9bb1603..ca963885 100644 --- a/src/utils/ExportCanvas.tsx +++ b/src/utils/ExportCanvas.tsx @@ -12,8 +12,6 @@ import { lerp } from 'three/src/math/MathUtils.js'; import { FFmpeg } from '@ffmpeg/ffmpeg'; import { deg2rad } from './HelperFuncs'; - - const DrawText = ( //Context and cbarlocs ctx: CanvasRenderingContext2D, diff --git a/src/utils/HelperFuncs.ts b/src/utils/HelperFuncs.ts index f18a7ea4..e3df047a 100644 --- a/src/utils/HelperFuncs.ts +++ b/src/utils/HelperFuncs.ts @@ -130,6 +130,8 @@ export function parseUVCoords({normal,uv}:{normal:THREE.Vector3,uv:THREE.Vector2 return [uv.x, flipY ? 1-uv.y : uv.y, null] case normal.y === 1: return [1-uv.y, null, uv.x] + case normal.y === -1: + return [uv.y, null, uv.x] default: return [0,0,0] }