Skip to content

Commit

Permalink
More robust visibility toggles for scene tree table (fixes #147)
Browse files Browse the repository at this point in the history
  • Loading branch information
brentyi committed Jan 15, 2024
1 parent e7d7560 commit 30db9c2
Show file tree
Hide file tree
Showing 6 changed files with 83 additions and 83 deletions.
3 changes: 2 additions & 1 deletion src/viser/client/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,8 @@ export type ViewerContextContents = {
| {
wxyz?: [number, number, number, number];
position?: [number, number, number];
visibility?: boolean;
visibility?: boolean; // Visibility state from the server.
overrideVisibility?: boolean; // Override from the GUI.
};
}>;
messageQueueRef: React.MutableRefObject<Message[]>;
Expand Down
8 changes: 4 additions & 4 deletions src/viser/client/src/ControlPanel/ControlPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,10 @@ export default function ControlPanel(props: {
controlWidthString == "small"
? "16em"
: controlWidthString == "medium"
? "20em"
: controlWidthString == "large"
? "24em"
: null
? "20em"
: controlWidthString == "large"
? "24em"
: null
)!;

const generatedServerToggleButton = (
Expand Down
61 changes: 29 additions & 32 deletions src/viser/client/src/ControlPanel/SceneTreeTable.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { ViewerContext } from "../App";
import { ActionIcon, Modal } from "@mantine/core";
import { ActionIcon, Modal, Tooltip } from "@mantine/core";
import { useDisclosure } from "@mantine/hooks";
import {
IconCaretDown,
IconCaretRight,
IconEye,
IconEyeCancel,
IconEyeOff,
IconMaximize,
} from "@tabler/icons-react";
Expand All @@ -26,10 +27,10 @@ export default function SceneTreeTable(props: { compact: boolean }) {
const setLabelVisibility = viewer.useSceneTree(
(state) => state.setLabelVisibility,
);
function setVisible(name: string, visible: boolean) {
function setOverrideVisibility(name: string, visible: boolean) {
const attr = viewer.nodeAttributesFromName.current;
if (attr[name] === undefined) attr[name] = {};
attr[name]!.visibility = visible;
attr[name]!.overrideVisibility = visible;
rerenderTable();
}

Expand All @@ -47,14 +48,6 @@ export default function SceneTreeTable(props: { compact: boolean }) {
};
}, []);

// Debouncing to suppress onMouseEnter and onMouseDown events from
// re-renders.
const debouncedReady = React.useRef(false);
debouncedReady.current = false;
setTimeout(() => {
debouncedReady.current = true;
}, 50);

function getSceneTreeSubRows(
parentName: string,
parentCount: number,
Expand All @@ -64,39 +57,25 @@ export default function SceneTreeTable(props: { compact: boolean }) {
if (node === undefined) return [];

return node.children.map((childName) => {
const attrs = viewer.nodeAttributesFromName.current[childName];
const isVisible =
viewer.nodeAttributesFromName.current[childName]?.visibility ?? true;
(attrs?.overrideVisibility === undefined
? attrs?.visibility
: attrs.overrideVisibility) ?? true;
const isVisibleEffective = isVisible && isParentVisible;

const VisibleIcon = isVisible ? IconEye : IconEyeOff;
return {
name: childName,
visible: (
<ActionIcon
onMouseDown={() => {
const isVisible =
viewer.nodeAttributesFromName.current[childName]?.visibility ??
true;
if (debouncedReady.current) {
setVisible(childName, !isVisible);
}
}}
onClick={(evt) => {
// Don't propagate click events to the row containing the icon.
//
// If we don't stop propagation, clicking the visibility icon
// will also expand/collapse nodes in the scene tree.
evt.stopPropagation();
}}
onMouseEnter={(event) => {
if (event.buttons !== 0) {
const isVisible =
viewer.nodeAttributesFromName.current[childName]
?.visibility ?? true;
if (debouncedReady.current) {
setVisible(childName, !isVisible);
}
}
setOverrideVisibility(childName, !isVisible);
}}
sx={{ opacity: isVisibleEffective ? "1.0" : "0.5" }}
>
Expand Down Expand Up @@ -252,7 +231,7 @@ export default function SceneTreeTable(props: { compact: boolean }) {
variant="filled"
onClick={() => {
table.getSelectedRowModel().flatRows.map((row) => {
setVisible(row.getValue("name"), true);
setOverrideVisibility(row.getValue("name"), true);
});
}}
>
Expand All @@ -264,12 +243,30 @@ export default function SceneTreeTable(props: { compact: boolean }) {
variant="filled"
onClick={() => {
table.getSelectedRowModel().flatRows.map((row) => {
setVisible(row.getValue("name"), false);
setOverrideVisibility(row.getValue("name"), false);
});
}}
>
<IconEyeOff />
</ActionIcon>
<Tooltip
zIndex={100}
label="Clear visibility overrides"
withinPortal
>
<ActionIcon
variant="filled"
onClick={() => {
Object.values(
viewer.nodeAttributesFromName.current,
).forEach((attrs) => {
delete attrs!.overrideVisibility;
});
}}
>
<IconEyeCancel />
</ActionIcon>
</Tooltip>
</div>
);
}
Expand Down
34 changes: 20 additions & 14 deletions src/viser/client/src/SceneTree.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import { ErrorBoundary } from "react-error-boundary";
import { rayToViserCoords } from "./WorldTransformUtils";
import { HoverableContext } from "./ThreeAssets";


export type MakeObject<T extends THREE.Object3D = THREE.Object3D> = (
ref: React.Ref<T>,
) => React.ReactNode;
Expand Down Expand Up @@ -173,8 +172,12 @@ export function SceneNodeThreeObject(props: {
function isDisplayed() {
// We avoid checking obj.visible because obj may be unmounted when
// unmountWhenInvisible=true.
if (viewer.nodeAttributesFromName.current[props.name]?.visibility === false)
return false;
const attrs = viewer.nodeAttributesFromName.current[props.name];
const visibility =
(attrs?.overrideVisibility === undefined
? attrs?.visibility
: attrs.overrideVisibility) ?? true;
if (visibility === false) return false;
if (props.parent === null) return true;

// Check visibility of parents + ancestors.
Expand Down Expand Up @@ -202,23 +205,22 @@ export function SceneNodeThreeObject(props: {

if (obj === null) return;

const nodeAttributes = viewer.nodeAttributesFromName.current[props.name];
if (nodeAttributes === undefined) return;
const attrs = viewer.nodeAttributesFromName.current[props.name];
if (attrs === undefined) return;

const visibility = nodeAttributes.visibility;
if (visibility !== undefined) {
obj.visible = visibility;
} else {
obj.visible = true;
}
const visibility =
(attrs?.overrideVisibility === undefined
? attrs?.visibility
: attrs.overrideVisibility) ?? true;
obj.visible = visibility;

let changed = false;
const wxyz = nodeAttributes.wxyz;
const wxyz = attrs.wxyz;
if (wxyz !== undefined) {
changed = true;
obj.quaternion.set(wxyz[1], wxyz[2], wxyz[3], wxyz[0]);
}
const position = nodeAttributes.position;
const position = attrs.position;
if (position !== undefined) {
changed = true;
obj.position.set(position[0], position[1], position[2]);
Expand Down Expand Up @@ -299,7 +301,11 @@ export function SceneNodeThreeObject(props: {
name: props.name,
// Note that the threejs up is +Y, but we expose a +Z up.
ray_origin: [ray.origin.x, ray.origin.y, ray.origin.z],
ray_direction: [ray.direction.x, ray.direction.y, ray.direction.z],
ray_direction: [
ray.direction.x,
ray.direction.y,
ray.direction.z,
],
});
}}
onPointerOver={(e) => {
Expand Down
50 changes: 25 additions & 25 deletions src/viser/client/src/WebsocketInterface.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -85,9 +85,13 @@ function useMessageHandler() {
}
addSceneNode(node);

// Retain visibility, but reset orientation and position.
// If we hide a scene node, it should stay hidden even if it's re-added/updated!
const attrs = viewer.nodeAttributesFromName.current;
if (node.name in attrs) {
delete attrs[node.name];
attrs[node.name] = {
overrideVisibility: attrs[node.name]?.overrideVisibility,
};
}
}

Expand Down Expand Up @@ -178,20 +182,16 @@ function useMessageHandler() {
message.plane == "xz"
? new THREE.Euler(0.0, 0.0, 0.0)
: message.plane == "xy"
? new THREE.Euler(Math.PI / 2.0, 0.0, 0.0)
: message.plane == "yx"
? new THREE.Euler(0.0, Math.PI / 2.0, Math.PI / 2.0)
: message.plane == "yz"
? new THREE.Euler(0.0, 0.0, Math.PI / 2.0)
: message.plane == "zx"
? new THREE.Euler(0.0, Math.PI / 2.0, 0.0)
: message.plane == "zy"
? new THREE.Euler(
-Math.PI / 2.0,
0.0,
-Math.PI / 2.0,
)
: undefined
? new THREE.Euler(Math.PI / 2.0, 0.0, 0.0)
: message.plane == "yx"
? new THREE.Euler(0.0, Math.PI / 2.0, Math.PI / 2.0)
: message.plane == "yz"
? new THREE.Euler(0.0, 0.0, Math.PI / 2.0)
: message.plane == "zx"
? new THREE.Euler(0.0, Math.PI / 2.0, 0.0)
: message.plane == "zy"
? new THREE.Euler(-Math.PI / 2.0, 0.0, -Math.PI / 2.0)
: undefined
}
/>
</group>
Expand Down Expand Up @@ -278,16 +278,16 @@ function useMessageHandler() {
message.material == "standard" || message.wireframe
? new THREE.MeshStandardMaterial(standardArgs)
: message.material == "toon3"
? new THREE.MeshToonMaterial({
gradientMap: generateGradientMap(3),
...standardArgs,
})
: message.material == "toon5"
? new THREE.MeshToonMaterial({
gradientMap: generateGradientMap(5),
...standardArgs,
})
: assertUnreachable(message.material);
? new THREE.MeshToonMaterial({
gradientMap: generateGradientMap(3),
...standardArgs,
})
: message.material == "toon5"
? new THREE.MeshToonMaterial({
gradientMap: generateGradientMap(5),
...standardArgs,
})
: assertUnreachable(message.material);
geometry.setAttribute(
"position",
new THREE.Float32BufferAttribute(
Expand Down
10 changes: 3 additions & 7 deletions src/viser/client/src/WorldTransformUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,16 +25,12 @@ export function rayToViserCoords(
): THREE.Ray {
const T_world_threeworld = computeT_threeworld_world(viewer).invert();

const origin = ray.origin
.clone()
.applyMatrix4(T_world_threeworld);
const origin = ray.origin.clone().applyMatrix4(T_world_threeworld);

// Compute just the rotation term without new memory allocation; this
// will mutate T_world_threeworld!
const R_world_threeworld = T_world_threeworld.setPosition(0.0, 0.0, 0);
const direction = ray.direction
.clone()
.applyMatrix4(R_world_threeworld);
const direction = ray.direction.clone().applyMatrix4(R_world_threeworld);

return new THREE.Ray(origin, direction);
}
}

0 comments on commit 30db9c2

Please sign in to comment.