Skip to content
Snippets Groups Projects
Commit ebc8ca71 authored by Sebastian Pape's avatar Sebastian Pape
Browse files

Adding Settings Menu and use it to switch on and off stuff in the scene/visualization

parent 4c9e0e85
Branches
No related tags found
No related merge requests found
Pipeline #292833 passed
...@@ -23,7 +23,7 @@ const lightTheme = createTheme({ ...@@ -23,7 +23,7 @@ const lightTheme = createTheme({
function Desktop() { function Desktop() {
const updateMessageRates = useStore(state => state.updateMessageRates); const updateMessageRates = useStore(state => state.updateMessageRates);
const [darkMode, setDarkMode] = useState(true); const darkMode = useStore(state => state.appSettings.darkTheme);
useEffect( useEffect(
() => { () => {
...@@ -44,10 +44,7 @@ function Desktop() { ...@@ -44,10 +44,7 @@ function Desktop() {
flexDirection: 'column', flexDirection: 'column',
}} }}
> >
<AppBar <AppBar />
darkMode={darkMode}
setDarkMode={setDarkMode}
/>
<Split <Split
initialPrimarySize='20%' initialPrimarySize='20%'
> >
......
...@@ -4,10 +4,10 @@ import Grid2 from '@mui/material/Unstable_Grid2'; ...@@ -4,10 +4,10 @@ import Grid2 from '@mui/material/Unstable_Grid2';
import CameraMenu from '../features/camera/CameraMenu'; import CameraMenu from '../features/camera/CameraMenu';
import ConnectionMenu from '../features/connections/ConnectionsMenu'; import ConnectionMenu from '../features/connections/ConnectionsMenu';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import { useStore } from '../common/store';
import AppSettingsPopover from '../features/app/AppSettingsPopover';
export interface AppBarProps { export interface AppBarProps {
darkMode: boolean,
setDarkMode: (darkMode: boolean) => void;
} }
function AppBar(props: AppBarProps) { function AppBar(props: AppBarProps) {
...@@ -16,7 +16,7 @@ function AppBar(props: AppBarProps) { ...@@ -16,7 +16,7 @@ function AppBar(props: AppBarProps) {
<Grid2 container> <Grid2 container>
<Grid2> <Grid2>
<ConnectionMenu /> <ConnectionMenu />
<CameraMenu /> {/* <CameraMenu /> */}
<IconButton <IconButton
component={Link} component={Link}
to="/?vr" to="/?vr"
...@@ -29,20 +29,8 @@ function AppBar(props: AppBarProps) { ...@@ -29,20 +29,8 @@ function AppBar(props: AppBarProps) {
<Grid2 xs> <Grid2 xs>
</Grid2> </Grid2>
<Grid2> <Grid2>
<Grid2 container alignItems="center"> <AppSettingsPopover />
<Grid2>
<LightMode />
</Grid2>
<Grid2>
<Switch
checked={props.darkMode}
onChange={() => props.setDarkMode(!props.darkMode)}
/>
</Grid2>
<Grid2>
<DarkMode />
</Grid2>
</Grid2>
</Grid2> </Grid2>
</Grid2> </Grid2>
</MUIAppBar> </MUIAppBar>
......
...@@ -37,12 +37,24 @@ export interface AreaOfInterest { ...@@ -37,12 +37,24 @@ export interface AreaOfInterest {
audioRecordings: Blob[], audioRecordings: Blob[],
} }
export interface AppSettings {
showTFDebugVis: boolean,
showTFDebugLabels: boolean,
darkTheme: boolean,
showSea: boolean,
oceanLevelOffset: number,
showSkybox: boolean,
}
export interface ConfigState { export interface ConfigState {
connections: {[uri: string]: RosbridgeConnectionState}; connections: {[uri: string]: RosbridgeConnectionState};
ship: Config['ship'], ship: Config['ship'],
transformTree: Config['transformTree'] | undefined, transformTree: Config['transformTree'] | undefined,
robots: {[name: string]: Robot}; robots: {[name: string]: Robot};
aois: {[id: string]: AreaOfInterest}, aois: {[id: string]: AreaOfInterest},
appSettings: AppSettings,
updateAppSettings: (newSettings: Partial<AppSettings>) => void;
connect: (uri: string, bsonMode: boolean) => void; connect: (uri: string, bsonMode: boolean) => void;
updateTopics: (uri: string) => void; updateTopics: (uri: string) => void;
...@@ -96,6 +108,14 @@ export const useStore = create<ConfigState>((set, get) => { ...@@ -96,6 +108,14 @@ export const useStore = create<ConfigState>((set, get) => {
connections: {}, connections: {},
ship: undefined, ship: undefined,
transformTree: undefined, transformTree: undefined,
appSettings: {
darkTheme: true,
showSea: true,
oceanLevelOffset: 0,
showSkybox: true,
showTFDebugLabels: true,
showTFDebugVis: true
},
aois: { aois: {
"0": { "0": {
id: 0, id: 0,
...@@ -117,6 +137,13 @@ export const useStore = create<ConfigState>((set, get) => { ...@@ -117,6 +137,13 @@ export const useStore = create<ConfigState>((set, get) => {
}, },
}, },
updateAppSettings: (newSettings: Partial<AppSettings>) => {
set(state => ({ appSettings: {
...state.appSettings,
...newSettings
}}));
},
connect: (uri, bsonMode) => { connect: (uri, bsonMode) => {
if (!(uri in get().connections)) { if (!(uri in get().connections)) {
console.log(`Connecting to ${uri}`); console.log(`Connecting to ${uri}`);
......
import PopoverListButton from "../../common/PopoverListButton";
import { DarkMode, Label, LightMode, Settings } from '@mui/icons-material';
import Grid2 from "@mui/material/Unstable_Grid2/Grid2";
import { useStore } from "../../common/store";
import { FormControlLabel, ListItem, Slider, Switch, Typography } from "@mui/material";
export interface AppSettingsPopoverProps {
}
const AppSettingsPopover = (props: AppSettingsPopoverProps) => {
const darkMode = useStore(state => state.appSettings.darkTheme);
const showSea = useStore(state => state.appSettings.showSea);
const showSkybox = useStore(state => state.appSettings.showSkybox);
const showTFTreeVis = useStore(state => state.appSettings.showTFDebugVis);
const showTFTreeLabels = useStore(state => state.appSettings.showTFDebugLabels);
const oceanLevelOffset = useStore(state => state.appSettings.oceanLevelOffset);
const updateSettings = useStore(state => state.updateAppSettings);
return (
<PopoverListButton title={"Settings"} icon={Settings}>
<ListItem button={false}>
<FormControlLabel control={
<Switch
checked={darkMode}
onChange={() => updateSettings({ darkTheme: !darkMode })}
/>
} label={darkMode ? "Dark Mode" : "Light Mode"} />
</ListItem>
<ListItem button={false}>
<FormControlLabel control={
<Switch
checked={showSea}
onChange={() => updateSettings({ showSea: !showSea })}
/>
} label="Show Ocean" />
</ListItem>
<ListItem button={false}>
<Slider
value={oceanLevelOffset}
onChange={(event, value) => updateSettings({ oceanLevelOffset: (value as number) })}
aria-labelledby="Ocean Level Offset"
max={5}
min={-5}
step={0.01}
valueLabelDisplay="auto"
/>
<Typography gutterBottom noWrap>
Ocean Height Offset
</Typography>
</ListItem>
<ListItem button={false}>
<FormControlLabel control={
<Switch
checked={showSkybox}
onChange={() => updateSettings({ showSkybox: !showSkybox })}
/>
} label="Show Skybox" />
</ListItem>
<ListItem button={false}>
<FormControlLabel control={
<Switch
checked={showTFTreeVis}
onChange={() => updateSettings({ showTFDebugVis: !showTFTreeVis })}
/>
} label="Show TF-Tree Visualization" />
</ListItem>
<ListItem button={false}>
<FormControlLabel control={
<Switch
checked={showTFTreeLabels}
onChange={() => updateSettings({ showTFDebugLabels: !showTFTreeLabels })}
/>
} label="Show TF-Tree Labels" />
</ListItem>
</PopoverListButton>
);
};
export default AppSettingsPopover;
import { useCubeTexture } from '@react-three/drei';
import { useThree } from '@react-three/fiber' import { useThree } from '@react-three/fiber'
import { useEffect } from 'react'; import { useEffect } from 'react';
import { CubeTextureLoader } from 'three'
export interface SkyBoxProps { export interface SkyBoxProps {
baseURL: string; baseURL: string;
visible?: boolean;
} }
function SkyBox(props: SkyBoxProps) { function SkyBox(props: SkyBoxProps) {
const { scene } = useThree(); const { scene } = useThree();
let texture = useCubeTexture(["east.jpeg", "west.jpeg", "up.jpeg", "down.jpeg", "north.jpeg", "south.jpeg"], { path: props.baseURL });
useEffect( useEffect(
() => { () => {
const loader = new CubeTextureLoader(); if (!scene || !texture) return;
// The CubeTextureLoader load method takes an array of urls representing all 6 sides of the cube. scene.background = props.visible ? texture : null;
const texture = loader.load([
`${props.baseURL}/east.jpeg`,
`${props.baseURL}/west.jpeg`,
`${props.baseURL}/up.jpeg`,
`${props.baseURL}/down.jpeg`,
`${props.baseURL}/north.jpeg`,
`${props.baseURL}/south.jpeg`,
]);
// Set the scene background property to the resulting texture.
scene.background = texture;
}, },
[scene, props.baseURL] [scene, texture, props.visible]
); );
return null; return null;
......
import { Props, useThree } from '@react-three/fiber' import { Props, useThree } from '@react-three/fiber'
import { PropsWithChildren, useEffect, useRef } from 'react'; import { PropsWithChildren, useEffect, useRef } from 'react';
import { Group, Matrix4, Mesh, MeshBasicMaterial, Object3D, Quaternion, SphereGeometry, Vector3 } from 'three' import { Group, Matrix4, Mesh, MeshBasicMaterial, Object3D, Quaternion, SphereGeometry, Vector3 } from 'three'
import { useConnection } from '../../common/store'; import { useConnection, useStore } from '../../common/store';
import { Config as GeneralConfig } from '../../schemas/Config.schema'; import { Config as GeneralConfig } from '../../schemas/Config.schema';
import tf2_msgs from '../../common/ROS/tf2_msgs'; import tf2_msgs from '../../common/ROS/tf2_msgs';
import geometry_msgs from '../../common/ROS/geometry_msgs'; import geometry_msgs from '../../common/ROS/geometry_msgs';
...@@ -46,8 +46,6 @@ export type yaml_anchor = { ...@@ -46,8 +46,6 @@ export type yaml_anchor = {
}; };
export interface TF2Props { export interface TF2Props {
config: GeneralConfig["transformTree"] | undefined;
debugVisualization?: boolean;
yaml?: string[]; yaml?: string[];
attachPrintGraphFunction?: boolean; attachPrintGraphFunction?: boolean;
} }
...@@ -68,9 +66,12 @@ function TF2(props: TF2Props) { ...@@ -68,9 +66,12 @@ function TF2(props: TF2Props) {
const { scene } = useThree(); const { scene } = useThree();
let root = useRef<Object3D>(new Object3D()); let root = useRef<Object3D>(new Object3D());
let knownObjects = useRef(new Map<string, Object3D>()); let knownObjects = useRef(new Map<string, Object3D>());
let debugGeometry = useRef<Object3D[]>([]);
const debug_geometry = useRef<SphereGeometry>(new SphereGeometry(0.1, 16, 16)); const debug_geometry = useRef<SphereGeometry>(new SphereGeometry(0.1, 16, 16));
const debug_material = useRef<MeshBasicMaterial>(new MeshBasicMaterial({ color: 0xff0000 })); const debug_material = useRef<MeshBasicMaterial>(new MeshBasicMaterial({ color: 0xff0000 }));
const connection = useConnection(props.config?.rosbridge.uri); const config = useStore(state => state.transformTree);
const connection = useConnection(config?.rosbridge.uri);
let showDebugSpheres = useStore(state => state.appSettings.showTFDebugVis);
useEffect(() => { useEffect(() => {
if (!props.yaml) return; if (!props.yaml) return;
...@@ -109,8 +110,8 @@ function TF2(props: TF2Props) { ...@@ -109,8 +110,8 @@ function TF2(props: TF2Props) {
// Debug // Debug
const sphere = new Mesh(debug_geometry.current, debug_material.current); const sphere = new Mesh(debug_geometry.current, debug_material.current);
sphere.name = prefix + "debugging_sphere"; sphere.visible = showDebugSpheres;
sphere.visible = (props.debugVisualization) ? true : false; debugGeometry.current.push(sphere);
Result.add(sphere); Result.add(sphere);
knownObjects.current.set(name, Result); knownObjects.current.set(name, Result);
...@@ -163,7 +164,7 @@ function TF2(props: TF2Props) { ...@@ -163,7 +164,7 @@ function TF2(props: TF2Props) {
let res = "\"" + node_name + "\" : {"; let res = "\"" + node_name + "\" : {";
let first = true; let first = true;
for (let i = 0; i < node.children.length; i++) { for (let i = 0; i < node.children.length; i++) {
if (node.children[i].name === "" || node.children[i].name == prefix + "debugging_sphere") continue; if (node.children[i].name === "" || debugGeometry.current.indexOf(node) != -1) continue;
if (first) { first = false; } else { res += ","; } if (first) { first = false; } else { res += ","; }
res += recurse(node.children[i]); res += recurse(node.children[i]);
} }
...@@ -178,16 +179,14 @@ function TF2(props: TF2Props) { ...@@ -178,16 +179,14 @@ function TF2(props: TF2Props) {
// visualizations visible/invisible // visualizations visible/invisible
useEffect(() => { useEffect(() => {
root.current.traverse((node) => { for (let obj of debugGeometry.current) {
if (node.name == prefix + "debugging_sphere") { obj.visible = showDebugSpheres;
node.visible = (props.debugVisualization || false);
} }
}); }, [showDebugSpheres]);
}, [props.debugVisualization]);
// Subscribe/Unsubscribe to TF Messages // Subscribe/Unsubscribe to TF Messages
useEffect(() => { useEffect(() => {
if (!connection?.rosbridge || !props.config) return; if (!connection?.rosbridge || !config) return;
let tf_topic = new Topic({ "ros": connection?.rosbridge, "name": "/tf", "messageType": 'tf2_msgs/TFMessage' }); let tf_topic = new Topic({ "ros": connection?.rosbridge, "name": "/tf", "messageType": 'tf2_msgs/TFMessage' });
let tf_static_topic = new Topic({ "ros": connection?.rosbridge, "name": "/tf_static", "messageType": 'tf2_msgs/TFMessage' }); let tf_static_topic = new Topic({ "ros": connection?.rosbridge, "name": "/tf_static", "messageType": 'tf2_msgs/TFMessage' });
...@@ -199,7 +198,7 @@ function TF2(props: TF2Props) { ...@@ -199,7 +198,7 @@ function TF2(props: TF2Props) {
tf_topic.unsubscribe(newTFMessage); tf_topic.unsubscribe(newTFMessage);
tf_static_topic.unsubscribe(newTFMessage); tf_static_topic.unsubscribe(newTFMessage);
}; };
}, [connection?.rosbridge, props.config]); }, [connection?.rosbridge, config]);
return null; return null;
} }
......
...@@ -3,7 +3,8 @@ import { Canvas } from '@react-three/fiber'; ...@@ -3,7 +3,8 @@ import { Canvas } from '@react-three/fiber';
import { MeshBasicMaterial, MeshLambertMaterial } from 'three'; import { MeshBasicMaterial, MeshLambertMaterial } from 'three';
import For from '../../common/For'; import For from '../../common/For';
import { useStore } from '../../common/store'; import { useStore } from '../../common/store';
import Mesh from './Mesh'; import Skybox from './SkyBox';
import Water from './Water';
import MeshLoader from './MeshLoader'; import MeshLoader from './MeshLoader';
import Robot from './Robot'; import Robot from './Robot';
import TF2, { Frame } from './TF2'; import TF2, { Frame } from './TF2';
...@@ -13,7 +14,9 @@ import FiducialVisualizer from './FiducialVisualizer'; ...@@ -13,7 +14,9 @@ import FiducialVisualizer from './FiducialVisualizer';
const Viewport = () => { const Viewport = () => {
const robots = useStore(state => state.robots); const robots = useStore(state => state.robots);
const ship = useStore(state => state.ship); const ship = useStore(state => state.ship);
const transformtree = useStore(state => state.transformTree); const showSkybox = useStore(state => state.appSettings.showSkybox);
const showOcean = useStore(state => state.appSettings.showSea);
const oceanLevelOffest = useStore(state => state.appSettings.oceanLevelOffset);
return ( return (
<Canvas <Canvas
...@@ -30,6 +33,9 @@ const Viewport = () => { ...@@ -30,6 +33,9 @@ const Viewport = () => {
<ambientLight /> <ambientLight />
<pointLight /> <pointLight />
<directionalLight /> <directionalLight />
<Skybox baseURL="/skyboxes/clouds/" visible={showSkybox} />
<Water waterNormalsTexture='waternormals.jpg' size={1000} heightOffset={oceanLevelOffest} visible={showOcean} />
{/* { {/* {
ship && ship.topics.mesh ? ship && ship.topics.mesh ?
<Mesh <Mesh
...@@ -68,7 +74,7 @@ const Viewport = () => { ...@@ -68,7 +74,7 @@ const Viewport = () => {
</For> </For>
<FiducialVisualizer knownMarkers={[103, 104, 105, 109]} image_uri_prefix="/markers/aruco_DICT_5X5_250_Marker_" image_uri_postfix=".png" marker_size={[0.52, 0.52]} frame_prefix="fiducial_static_" /> <FiducialVisualizer knownMarkers={[103, 104, 105, 109]} image_uri_prefix="/markers/aruco_DICT_5X5_250_Marker_" image_uri_postfix=".png" marker_size={[0.52, 0.52]} frame_prefix="fiducial_static_" />
{/* <FiducialVisualizer knownMarkers={[103, 104, 105, 109]} image_uri_prefix="/markers/aruco_DICT_5X5_250_Marker_" image_uri_postfix=".png" marker_size={[0.52, 0.52]} frame_prefix="/mussol/fiducial_" color_tint='orange' /> */} {/* <FiducialVisualizer knownMarkers={[103, 104, 105, 109]} image_uri_prefix="/markers/aruco_DICT_5X5_250_Marker_" image_uri_postfix=".png" marker_size={[0.52, 0.52]} frame_prefix="/mussol/fiducial_" color_tint='orange' /> */}
<TF2 config={transformtree} debugVisualization={false} yaml={["meshes/fiducial_tags.yaml"]} attachPrintGraphFunction /> <TF2 yaml={["meshes/fiducial_tags.yaml"]} attachPrintGraphFunction />
</Canvas> </Canvas>
); );
} }
......
...@@ -25,7 +25,6 @@ const Viewport2D = () => { ...@@ -25,7 +25,6 @@ const Viewport2D = () => {
const [cameraPosition, setCameraPosition] = useState<[number, number]>([0, 0]); const [cameraPosition, setCameraPosition] = useState<[number, number]>([0, 0]);
const sideFactor = side === 'portside' ? -1 : 1; const sideFactor = side === 'portside' ? -1 : 1;
const canvas = useRef<HTMLCanvasElement>(null); const canvas = useRef<HTMLCanvasElement>(null);
const transformtree = useStore(state => state.transformTree);
// React uses passive events by default. So, we need to register the event // React uses passive events by default. So, we need to register the event
// listener this way. // listener this way.
...@@ -126,7 +125,7 @@ const Viewport2D = () => { ...@@ -126,7 +125,7 @@ const Viewport2D = () => {
/> />
} }
</For> </For>
<TF2 config={transformtree} debugVisualization={false} /> <TF2 />
</Canvas> </Canvas>
<Box <Box
component="div" component="div"
......
import { useThree } from '@react-three/fiber' import { useThree } from '@react-three/fiber'
import { useEffect } from 'react'; import { useEffect, useRef } from 'react';
import { TextureLoader, PlaneGeometry, RepeatWrapping, Vector3 } from 'three' import { TextureLoader, PlaneGeometry, RepeatWrapping, Vector3, Group } from 'three'
import { Water as ThreeJSWater } from "three/examples/jsm/objects/Water"; import { Water as ThreeJSWater } from "three/examples/jsm/objects/Water";
export interface WaterProps { export interface WaterProps {
waterNormalsTexture?: string; waterNormalsTexture?: string;
width?: number; size?: number;
height?: number; heightOffset?: number;
visible?: boolean;
} }
function Water(props: WaterProps) { function Water(props: WaterProps) {
const { scene } = useThree(); const { scene } = useThree();
let water = useRef<ThreeJSWater | null>(null);
let water: ThreeJSWater;
useEffect(() => { useEffect(() => {
const updateInterval = setInterval(() => { const updateInterval = setInterval(() => {
water.material.uniforms[ 'time' ].value += 1.0 / 60.0 / 10; if (water.current) water.current.material.uniforms['time'].value += 1.0 / 60.0 / 10;
}, 1 / 60 * 1000); }, 1 / 60 * 1000);
const waterGeometry = new PlaneGeometry(props.width || 100, props.height || 100, 1, 1); const waterGeometry = new PlaneGeometry(props.size || 100, props.size || 100, 1, 1);
waterGeometry.computeVertexNormals(); waterGeometry.computeVertexNormals();
water = new ThreeJSWater( water.current = new ThreeJSWater(
waterGeometry, waterGeometry,
{ {
textureWidth: 512, textureWidth: 512,
...@@ -38,17 +38,17 @@ function Water(props: WaterProps) { ...@@ -38,17 +38,17 @@ function Water(props: WaterProps) {
alpha: 0.1, alpha: 0.1,
} }
); );
scene.add(water); water.current.rotation.x = -Math.PI * 0.5;
water.rotation.x = -Math.PI * 0.5; water.current.position.setY(-5);
water.position.setY(-5);
return () => { return () => {
clearInterval(updateInterval); clearInterval(updateInterval);
scene.remove(water);
} }
}, [props.width, props.height, props.waterNormalsTexture]); }, [props.size, props.waterNormalsTexture]);
return null; return <group position={[0, props.heightOffset || 0, 0]} visible={!!props.visible}>
{water.current ? <primitive object={water.current} /> : null}
</group>;
} }
export default Water; export default Water;
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment