Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
64 changes: 37 additions & 27 deletions src/components/plots/CountryBorders.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<boolean>(false)

Expand All @@ -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) => {
Expand Down Expand Up @@ -201,9 +209,11 @@ const CountryBorders = () => {
const [borders, setBorders] = useState<any>(null)
const [swapSides, setSwapSides] = useState<boolean>(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,
Expand Down Expand Up @@ -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(
<group
rotation={[rotateFlat ? -Math.PI/2 : 0, 0, 0]}
scale={[globalScale, globalScale * (spherize ? 1 : 2 / aspectRatio), globalScale]}
scale={[globalScale, globalScale * (spherize ? 1 : (2 / aspectRatio) * (flipY ? -1 : 1)), globalScale]}
>
<group
visible={showBorders && !(analysisMode && axis != 0)}
position={(spherize || isFlatMap) ? [0,0,(isFlatMap ? 0.001 : 0)] : [0, 0, swapSides ? zRange[0]*(isPC ? depthScale + pointSize/10000 : timeRatio/2) : zRange[1]*(isPC ? depthScale + pointSize/10000 : timeRatio/2)]} // I don't know what value to use here. THis seems okay but not perfect
position={(spherize || isFlatMap) ? [0,0,(isFlatMap ? 0.001 : 0)] : [0, 0, swapSides ? zRange[0]*(depthScale + (isPC ? pointSize/10000 + 0.01 : 0)) : zRange[1]*(depthScale + (isPC ? pointSize/10000 + 0.01 : 0))]} // I don't know what value to use here. THis seems okay but not perfect
rotation={[0, (is360Deg && spherize) ? Math.PI : 0, 0]}
>
{coastLines && <Borders features={coastLines} />}
Expand Down
2 changes: 1 addition & 1 deletion src/components/plots/DataCube.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
4 changes: 2 additions & 2 deletions src/components/plots/FlatBlocks.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
4 changes: 2 additions & 2 deletions src/components/plots/FlatMap.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
5 changes: 5 additions & 0 deletions src/components/plots/Plot.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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})))
Expand Down Expand Up @@ -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(()=>{
Expand Down
112 changes: 92 additions & 20 deletions src/components/plots/PointCloud.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,18 @@ 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
}

const MappingCube = () =>{

const {dataShape} = useGlobalStore(useShallow(state => ({
const {dataShape, shape} = useGlobalStore(useShallow(state => ({
dataShape: state.dataShape,
shape: state.shape
})))

const {timeScale} = usePlotStore(useShallow(state=> ({
Expand All @@ -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(
Expand All @@ -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,
Expand Down Expand Up @@ -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]))},
Expand All @@ -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 } : {})
},
Comment thread
lazarusA marked this conversation as resolved.
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;
Expand All @@ -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],
Expand All @@ -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 (
<group scale={[1,flipY ? -1:1, 1]}>
<group scale={[1, 1, 1]}>
<group scale={[tsScale,tsScale,tsScale]}>
<ColumnMeshes />
</group>
<points geometry={geometry} material={shaderMaterial} frustumCulled={false}/>
{geometries.map((geom, idx) => (
<points key={idx} geometry={geom} material={shaderMaterial} frustumCulled={false}/>
))}
<MappingCube/>
</group>

Expand Down
Loading
Loading