Skip to content

Commit

Permalink
fixes and rework of visible box and pixel size
Browse files Browse the repository at this point in the history
  • Loading branch information
t20100 committed May 10, 2022
1 parent 89cc188 commit 3d3eb9a
Show file tree
Hide file tree
Showing 4 changed files with 106 additions and 100 deletions.
10 changes: 5 additions & 5 deletions apps/storybook/src/TiledHeatmapMesh.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ interface TiledHeatmapStoryProps extends TiledHeatmapMeshProps {
}

const Template: Story<TiledHeatmapStoryProps> = (args) => {
const { abscissaConfig, api, ordinateConfig, ...tiledHeatmapProps } = args;
const { abscissaConfig, ordinateConfig, ...tiledHeatmapProps } = args;

return (
<VisCanvas
Expand All @@ -150,7 +150,7 @@ const Template: Story<TiledHeatmapStoryProps> = (args) => {
<group
scale={[abscissaConfig.flip ? -1 : 1, ordinateConfig.flip ? -1 : 1, 1]}
>
<TiledHeatmapMesh api={api} {...tiledHeatmapProps} />
<TiledHeatmapMesh {...tiledHeatmapProps} />
</group>
</VisCanvas>
);
Expand Down Expand Up @@ -224,7 +224,7 @@ FlippedAxes.args = {

function LinearAxesGroup(props: { children: ReactNode }) {
const { children } = props;
const { visSize, abscissaConfig, ordinateConfig } = useAxisSystemContext();
const { abscissaConfig, ordinateConfig, visSize } = useAxisSystemContext();
const { width, height } = visSize;
const sx =
((abscissaConfig.flip ? -1 : 1) * width) /
Expand All @@ -236,7 +236,7 @@ function LinearAxesGroup(props: { children: ReactNode }) {
const y = 0.5 * (ordinateConfig.visDomain[0] + ordinateConfig.visDomain[1]);

return (
<group scale={[sx, sy, 1]} position={[-x * sx, -y * sy, 0]}>
<group position={[-x * sx, -y * sy, 0]} scale={[sx, sy, 1]}>
{children}
</group>
);
Expand Down Expand Up @@ -283,7 +283,7 @@ WithTransforms.args = {
visDomain: [0, 2],
isIndexAxis: true,
showGrid: false,
flip: true,
flip: false,
},
};

Expand Down
35 changes: 22 additions & 13 deletions packages/lib/src/vis/tiles/TiledHeatmapMesh.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { useThree } from '@react-three/fiber';
import { clamp, range } from 'lodash';
import { useRef } from 'react';
import type { Group } from 'three';
import type { Object3D } from 'three';

import { getInterpolator } from '../heatmap/utils';
import { useCameraState } from '../hooks';
Expand All @@ -10,7 +10,12 @@ import { useAxisSystemContext } from '../shared/AxisSystemProvider';
import TiledLayer from './TiledLayer';
import type { TilesApi } from './api';
import type { ColorMapProps } from './models';
import { getScaledVisibleBox } from './utils';
import {
getObject3DVisibleBox,
getObject3DPixelSize,
getNdcToObject3DMatrix,
scaleToLayer,
} from './utils';

interface Props extends ColorMapProps {
api: TilesApi;
Expand All @@ -33,25 +38,28 @@ function TiledHeatmapMesh(props: Props) {
const { visSize } = useAxisSystemContext();
const meshSize = size ?? visSize;

const groupRef = useRef<Group>(null);
const groupRef = useRef<Object3D>(null);

const box = useCameraState(
(...args) =>
getScaledVisibleBox(...args, meshSize, baseLayerSize, groupRef),
[meshSize, baseLayerSize, groupRef]
const ndcToLocalMatrix = useCameraState(
(camera) => getNdcToObject3DMatrix(camera, groupRef),
[]
);
const visibleBox = getObject3DVisibleBox(ndcToLocalMatrix);

const bounds = scaleToLayer(visibleBox, baseLayerSize, meshSize);

let layers: number[] = [];
if (box) {
const itemsPerPixel = Math.max(
if (!bounds.isEmpty()) {
const pixelSize = getObject3DPixelSize(ndcToLocalMatrix, canvasSize);
const dataPointsPerPixel = Math.max(
1,
Math.abs(box.max.x - box.min.x) / canvasSize.width,
Math.abs(box.max.y - box.min.y) / canvasSize.height
(pixelSize.x / meshSize.width) * baseLayerSize.width,
(pixelSize.y / meshSize.height) * baseLayerSize.height
);

const roundingOffset = 1 - clamp(qualityFactor, 0, 1);
const subsamplingLevel = Math.min(
Math.floor(Math.log2(itemsPerPixel) + roundingOffset),
Math.floor(Math.log2(dataPointsPerPixel) + roundingOffset),
baseLayerIndex
);
const currentLayerIndex = baseLayerIndex - subsamplingLevel;
Expand Down Expand Up @@ -79,7 +87,8 @@ function TiledHeatmapMesh(props: Props) {
key={layer}
api={api}
layer={layer}
size={meshSize}
meshSize={meshSize}
visibleBox={visibleBox}
{...colorMapProps}
/>
))}
Expand Down
84 changes: 33 additions & 51 deletions packages/lib/src/vis/tiles/TiledLayer.tsx
Original file line number Diff line number Diff line change
@@ -1,75 +1,57 @@
import { Suspense, useRef } from 'react';
import { Suspense } from 'react';
import { LinearFilter, NearestFilter, Vector2 } from 'three';
import type { Group } from 'three';
import type { Box3 } from 'three';

import { useCameraState } from '../hooks';
import type { Size } from '../models';
import { useAxisSystemContext } from '../shared/AxisSystemProvider';
import Tile from './Tile';
import type { TilesApi } from './api';
import type { ColorMapProps } from './models';
import {
getTileOffsets,
getScaledVisibleBox,
sortTilesByDistanceTo,
} from './utils';
import { getTileOffsets, scaleToLayer, sortTilesByDistanceTo } from './utils';

interface Props extends ColorMapProps {
api: TilesApi;
layer: number;
size: Size;
meshSize: Size;
visibleBox: Box3;
}

function TiledLayer(props: Props) {
const { api, layer, size: meshSize, ...colorMapProps } = props;
const { api, layer, meshSize, visibleBox, ...colorMapProps } = props;

const { baseLayerIndex, numLayers, tileSize } = api;
const layerSize = api.layerSizes[layer];

const groupRef = useRef<Group>(null);
const box = useCameraState(
(...args) => getScaledVisibleBox(...args, meshSize, layerSize, groupRef),
[meshSize, layerSize, groupRef]
);

let tileOffsets: Vector2[] = [];
if (box) {
tileOffsets = getTileOffsets(box, tileSize);

// Sort tiles from closest to vis center to farthest away
sortTilesByDistanceTo(tileOffsets, tileSize, box.getCenter(new Vector2()));
if (visibleBox.isEmpty()) {
return null;
}

const bounds = scaleToLayer(visibleBox, layerSize, meshSize);
const tileOffsets = getTileOffsets(bounds, tileSize);
// Sort tiles from closest to vis center to farthest away
sortTilesByDistanceTo(tileOffsets, tileSize, bounds.getCenter(new Vector2()));

return (
// Transforms to use level of details layer array coordinates
<group ref={groupRef}>
<group
position={[
-meshSize.width / 2,
-meshSize.height / 2,
layer / numLayers,
]}
scale={[
meshSize.width / layerSize.width,
meshSize.height / layerSize.height,
1,
]}
>
{tileOffsets.map((offset) => (
<Suspense key={`${offset.x},${offset.y}`} fallback={null}>
<Tile
api={api}
layer={layer}
x={offset.x}
y={offset.y}
{...colorMapProps}
magFilter={
layer === baseLayerIndex ? NearestFilter : LinearFilter
}
/>
</Suspense>
))}
</group>
<group
position={[-meshSize.width / 2, -meshSize.height / 2, layer / numLayers]}
scale={[
meshSize.width / layerSize.width,
meshSize.height / layerSize.height,
1,
]}
>
{tileOffsets.map((offset) => (
<Suspense key={`${offset.x},${offset.y}`} fallback={null}>
<Tile
api={api}
layer={layer}
x={offset.x}
y={offset.y}
{...colorMapProps}
magFilter={layer === baseLayerIndex ? NearestFilter : LinearFilter}
/>
</Suspense>
))}
</group>
);
}
Expand Down
77 changes: 46 additions & 31 deletions packages/lib/src/vis/tiles/utils.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import { ScaleType } from '@h5web/shared';
import type { Camera } from '@react-three/fiber';
import type { RefObject } from 'react';
import { Box2, Box3, Matrix4, Vector2, Vector3 } from 'three';
import type { Object3D } from 'three';
import { Box2, Box3, Vector2, Vector3 } from 'three';
import type { Object3D, Matrix4 } from 'three';

import type { Size } from '../models';
import type { AxisSystemContextValue } from '../shared/AxisSystemProvider';
import { createAxisScale } from '../utils';

export function getTileOffsets(box: Box2, tileSize: Size): Vector2[] {
Expand Down Expand Up @@ -47,51 +46,67 @@ export function sortTilesByDistanceTo(
});
}

const NDC_BOX = new Box3(new Vector3(-1, -1, -1), new Vector3(1, 1, 1));

function getObject3DVisibleBox(
export function getNdcToObject3DMatrix(
camera: Camera,
context: AxisSystemContextValue,
Object3DRef: RefObject<Object3D>
): Box3 | undefined {
const object3D = Object3DRef.current;
object3DRef: RefObject<Object3D>
): Matrix4 | undefined {
const object3D = object3DRef.current;
if (!object3D) {
return undefined;
}

// Convert view box: Normalized Device Coordinates -> camera -> world -> local
const matrix = new Matrix4()
.multiplyMatrices(object3D.matrixWorld.invert(), camera.matrixWorld)
// Convert Normalized Device Coordinates -> camera -> world -> local
return object3D.matrixWorld
.clone()
.invert()
.multiply(camera.matrixWorld)
.multiply(camera.projectionMatrixInverse);
return NDC_BOX.clone().applyMatrix4(matrix);
}

export function getScaledVisibleBox(
camera: Camera,
context: AxisSystemContextValue,
meshSize: Size,
arraySize: Size,
ref: RefObject<Object3D>
): Box2 | undefined {
const box3d = getObject3DVisibleBox(camera, context, ref);
if (!box3d) {
return undefined;
export function getObject3DPixelSize(
ndcToObject3DMatrix: Matrix4 | undefined,
canvasSize: Size
): Vector3 {
if (!ndcToObject3DMatrix) {
return new Vector3();
}
const ndcPixelBox = new Box3(
new Vector3(0, 0, 0),
new Vector3(2 / canvasSize.width, 2 / canvasSize.height, 0)
);
const box = ndcPixelBox.applyMatrix4(ndcToObject3DMatrix);
return box.getSize(new Vector3());
}

const NDC_BOX = new Box3(new Vector3(-1, -1, -1), new Vector3(1, 1, 1));

export function getObject3DVisibleBox(
ndcToObject3DMatrix: Matrix4 | undefined
): Box3 {
if (!ndcToObject3DMatrix) {
return new Box3();
}
return NDC_BOX.clone().applyMatrix4(ndcToObject3DMatrix);
}

export function scaleToLayer(box: Box3, layerSize: Size, meshSize: Size): Box2 {
if (box.isEmpty()) {
return new Box2();
}

const xScale = createAxisScale(ScaleType.Linear, {
domain: [-meshSize.width / 2, meshSize.width / 2],
range: [0, arraySize.width],
range: [0, layerSize.width],
clamp: true,
});

const yScale = createAxisScale(ScaleType.Linear, {
domain: [-meshSize.height / 2, meshSize.height / 2],
range: [0, arraySize.height],
range: [0, layerSize.height],
clamp: true,
});

return new Box2().setFromPoints([
new Vector2(xScale(box3d.min.x), yScale(box3d.min.y)),
new Vector2(xScale(box3d.max.x), yScale(box3d.max.y)),
]);
return new Box2(
new Vector2(xScale(box.min.x), yScale(box.min.y)),
new Vector2(xScale(box.max.x), yScale(box.max.y))
);
}

0 comments on commit 3d3eb9a

Please sign in to comment.