Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Screen space line width for camera frustums #330

Merged
merged 5 commits into from
Nov 12, 2024
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
4 changes: 2 additions & 2 deletions src/viser/_messages.py
Original file line number Diff line number Diff line change
Expand Up @@ -186,8 +186,8 @@ class CameraFrustumProps:
"""Aspect ratio of the camera (width over height). Synchronized automatically when assigned."""
scale: float
"""Scale factor for the size of the frustum. Synchronized automatically when assigned."""
line_thickness: float
"""Thickness of the frustum lines. Synchronized automatically when assigned."""
line_width: float
"""Width of the frustum lines. Synchronized automatically when assigned."""
color: Tuple[int, int, int]
"""Color of the frustum as RGB integers. Synchronized automatically when assigned."""
image_media_type: Optional[Literal["image/jpeg", "image/png"]]
Expand Down
18 changes: 11 additions & 7 deletions src/viser/_scene_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -739,14 +739,15 @@ def add_camera_frustum(
fov: float,
aspect: float,
scale: float = 0.3,
line_thickness: float | None = None,
line_width: float = 2.0,
color: RgbTupleOrArray = (20, 20, 20),
image: np.ndarray | None = None,
format: Literal["png", "jpeg"] = "jpeg",
jpeg_quality: int | None = None,
wxyz: tuple[float, float, float, float] | np.ndarray = (1.0, 0.0, 0.0, 0.0),
position: tuple[float, float, float] | np.ndarray = (0.0, 0.0, 0.0),
visible: bool = True,
*_removed_kwargs,
) -> CameraFrustumHandle:
"""Add a camera frustum to the scene for visualization.

Expand All @@ -755,16 +756,15 @@ def add_camera_frustum(
and coverage of a camera in the 3D space.

Like all cameras in the viser Python API, frustums follow the OpenCV [+Z forward,
+X right, +Y down] convention. fov is vertical in radians; aspect is width over height
+X right, +Y down] convention. fov is vertical in radians; aspect is width over height.

Args:
name: A scene tree name. Names in the format of /parent/child can be used to
define a kinematic tree.
fov: Field of view of the camera (in radians).
aspect: Aspect ratio of the camera (width over height).
scale: Scale factor for the size of the frustum.
line_thickness: Thickness of the frustum lines. If not set,
defaults to `0.03 * scale`.
line_width: Width of the frustum lines, in screen space. Defaults to `2.0`.
color: Color of the frustum as an RGB tuple.
image: Optional image to be displayed on the frustum.
format: Format of the provided image ('png' or 'jpeg').
Expand All @@ -777,6 +777,12 @@ def add_camera_frustum(
Handle for manipulating scene node.
"""

if "line_thickness" in _removed_kwargs:
warnings.warn(
"The 'line_thickness' argument has been removed. Please use 'line_width' instead. Note that the units have been changed from world space to screen space.",
DeprecationWarning,
)

if image is not None:
media_type, binary = _encode_image_binary(
image, format, jpeg_quality=jpeg_quality
Expand All @@ -791,9 +797,7 @@ def add_camera_frustum(
fov=fov,
aspect=aspect,
scale=scale,
line_thickness=line_thickness
if line_thickness is not None
else 0.03 * scale,
line_width=line_width,
color=_encode_rgb(color),
image_media_type=media_type,
image_binary=binary,
Expand Down
2 changes: 1 addition & 1 deletion src/viser/client/src/SceneTree.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -282,7 +282,7 @@ function useObjectFactory(message: SceneNodeMessage | undefined): {
fov={message.props.fov}
aspect={message.props.aspect}
scale={message.props.scale}
lineThickness={message.props.line_thickness}
lineWidth={message.props.line_width}
color={rgbToInt(message.props.color)}
imageBinary={message.props.image_binary}
imageMediaType={message.props.image_media_type}
Expand Down
128 changes: 46 additions & 82 deletions src/viser/client/src/ThreeAssets.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Instance, Instances, shaderMaterial } from "@react-three/drei";
import { Instance, Instances, Line, shaderMaterial } from "@react-three/drei";
import { createPortal, useFrame, useThree } from "@react-three/fiber";
import { Outlines } from "./Outlines";
import React from "react";
Expand Down Expand Up @@ -699,7 +699,7 @@ export const CameraFrustum = React.forwardRef<
fov: number;
aspect: number;
scale: number;
lineThickness: number;
lineWidth: number;
color: number;
imageBinary: Uint8Array | null;
imageMediaType: string | null;
Expand All @@ -722,59 +722,53 @@ export const CameraFrustum = React.forwardRef<
let z = 1.0;

const volumeScale = Math.cbrt((x * y * z) / 3.0);
x /= volumeScale;
y /= volumeScale;
z /= volumeScale;

function scaledLineSegments(points: [number, number, number][]) {
points = points.map((xyz) => [xyz[0] * x, xyz[1] * y, xyz[2] * z]);
return [...Array(points.length - 1).keys()].map((i) => (
<LineSegmentInstance
key={i}
start={new THREE.Vector3()
.fromArray(points[i])
.multiplyScalar(props.scale)}
end={new THREE.Vector3()
.fromArray(points[i + 1])
.multiplyScalar(props.scale)}
color={props.color}
/>
));
}
x /= volumeScale * props.scale;
y /= volumeScale * props.scale;
z /= volumeScale * props.scale;

const hoveredRef = React.useContext(HoverableContext)!;
const [isHovered, setIsHovered] = React.useState(false);

useFrame(() => {
if (hoveredRef.current !== isHovered) {
setIsHovered(hoveredRef.current);
}
});

const frustumPoints: [number, number, number][] = [
// Rectangle.
[-1, -1, 1],
[1, -1, 1],
[1, -1, 1],
[1, 1, 1],
[1, 1, 1],
[-1, 1, 1],
[-1, 1, 1],
[-1, -1, 1],
// Lines to origin.
[-1, -1, 1],
[0, 0, 0],
[0, 0, 0],
[1, -1, 1],
// Lines to origin.
[-1, 1, 1],
[0, 0, 0],
[0, 0, 0],
[1, 1, 1],
// Up direction indicator.
// Don't overlap with the image if the image is present.
[0.0, -1.2, 1.0],
imageTexture === undefined ? [0.0, -0.9, 1.0] : [0.0, -1.0, 1.0],
].map((xyz) => [xyz[0] * x, xyz[1] * y, xyz[2] * z]);

return (
<group ref={ref}>
<Instances limit={9}>
<meshBasicMaterial color={props.color} side={THREE.DoubleSide} />
<cylinderGeometry
args={[props.lineThickness, props.lineThickness, 1.0, 3]}
/>
{scaledLineSegments([
// Rectangle.
[-1, -1, 1],
[1, -1, 1],
[1, 1, 1],
[-1, 1, 1],
[-1, -1, 1],
])}
{scaledLineSegments([
// Lines to origin.
[-1, -1, 1],
[0, 0, 0],
[1, -1, 1],
])}
{scaledLineSegments([
// Lines to origin.
[-1, 1, 1],
[0, 0, 0],
[1, 1, 1],
])}
{scaledLineSegments([
// Up direction.
[0.0, -1.2, 1.0],
[0.0, -0.9, 1.0],
])}
</Instances>
<Line
points={frustumPoints}
color={isHovered ? 0xfbff00 : props.color}
lineWidth={isHovered ? 1.5 * props.lineWidth : props.lineWidth}
segments
/>
{imageTexture && (
<mesh
position={[0.0, 0.0, props.scale * z]}
Expand All @@ -797,36 +791,6 @@ export const CameraFrustum = React.forwardRef<
);
});

function LineSegmentInstance(props: {
start: THREE.Vector3;
end: THREE.Vector3;
color: number;
}) {
const desiredDirection = new THREE.Vector3()
.subVectors(props.end, props.start)
.normalize();
const canonicalDirection = new THREE.Vector3(0.0, 1.0, 0.0);
const orientation = new THREE.Quaternion().setFromUnitVectors(
canonicalDirection,
desiredDirection,
);

const length = props.start.distanceTo(props.end);
const midpoint = new THREE.Vector3()
.addVectors(props.start, props.end)
.divideScalar(2.0);

return (
<Instance
position={midpoint}
quaternion={orientation}
scale={[1.0, length, 1.0]}
>
<OutlinesIfHovered creaseAngle={0.0} />
</Instance>
);
}

export const HoverableContext =
React.createContext<React.MutableRefObject<boolean> | null>(null);

Expand Down
2 changes: 1 addition & 1 deletion src/viser/client/src/WebsocketMessages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ export interface CameraFrustumMessage {
fov: number;
aspect: number;
scale: number;
line_thickness: number;
line_width: number;
color: [number, number, number];
image_media_type: "image/jpeg" | "image/png" | null;
image_binary: Uint8Array | null;
Expand Down
Loading