Skip to content

Commit

Permalink
add support for multi-select and hide/unhide
Browse files Browse the repository at this point in the history
  • Loading branch information
Krande committed Nov 6, 2024
1 parent 785870e commit b1075b1
Show file tree
Hide file tree
Showing 14 changed files with 758 additions and 365 deletions.
Binary file modified src/ada/visit/rendering/resources/index.zip
Binary file not shown.
1 change: 0 additions & 1 deletion src/frontend/src/components/Menu.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import React from 'react';
import {toggle_info_panel} from "../utils/info_panel_utils";
import ObjectInfoBox from "./object_info_box/ObjectInfoBoxComponent";
import {useObjectInfoStore} from "../state/objectInfoStore";
import AnimationControls from "./viewer/AnimationControls";
Expand Down
62 changes: 30 additions & 32 deletions src/frontend/src/components/viewer/CameraControls.tsx
Original file line number Diff line number Diff line change
@@ -1,53 +1,51 @@
// CameraControls.tsx
import * as THREE from 'three';
import React, {useEffect} from 'react';
import {useThree} from '@react-three/fiber';
import {OrbitControls as OrbitControlsImpl} from 'three-stdlib';
import {useSelectedObjectStore} from "../../state/selectedObjectStore";
import {centerViewOnObject} from "../../utils/scene/centerViewOnObject";
import React, { useEffect } from 'react';
import { useThree } from '@react-three/fiber';
import { OrbitControls as OrbitControlsImpl } from 'three-stdlib';
import { useSelectedObjectStore } from '../../state/useSelectedObjectStore';
import { centerViewOnSelection } from '../../utils/scene/centerViewOnSelection';
import { CustomBatchedMesh } from '../../utils/mesh_select/CustomBatchedMesh';

type CameraControlsProps = {
orbitControlsRef: React.RefObject<OrbitControlsImpl>;
};

const CameraControls: React.FC<CameraControlsProps> = ({orbitControlsRef}) => {
const {camera, gl, scene} = useThree();
const CameraControls: React.FC<CameraControlsProps> = ({ orbitControlsRef }) => {
const { camera, scene } = useThree();

useEffect(() => {
const handlePointerDown = (event: PointerEvent) => {
if (event.ctrlKey && event.button === 0) {
console.log('CTRL+Left Click');
}
};

gl.domElement.addEventListener('pointerdown', handlePointerDown);

const handleKeyDown = (event: KeyboardEvent) => {

if (event.key.toLowerCase() === 'f' && event.shiftKey) {
centerViewOnObject(orbitControlsRef, camera);
} else if (event.key.toLowerCase() === 'h' && event.shiftKey) {
// Perform an action when "ctrl+h" is pressed
console.log('SHIFT+H pressed');
// currently_selected?.layers.set(1);
// Example action: Reset the camera position to the default
} else if (event.key.toLowerCase() === 'g' && event.shiftKey) {
// Perform an action when "ctrl+h" is pressed
console.log('SHIFT+g pressed');
// loop over objects in layers 2 and set them to layer 0
// Example action: Reset the camera position to the default
if (event.shiftKey && event.key.toLowerCase() === 'h') {
// SHIFT+H pressed - Hide selected draw ranges
const selectedObjects = useSelectedObjectStore.getState().selectedObjects;
selectedObjects.forEach((drawRangeIds, mesh) => {
drawRangeIds.forEach((drawRangeId) => {
mesh.hideDrawRange(drawRangeId);
});
mesh.deselect();
});
useSelectedObjectStore.getState().clearSelectedObjects();
} else if (event.shiftKey && event.key.toLowerCase() === 'u') {
// SHIFT+U pressed - Unhide all
scene.traverse((object) => {
if (object instanceof CustomBatchedMesh) {
object.unhideAllDrawRanges();
}
});
} else if (event.shiftKey && event.key.toLowerCase() === 'f') {
// SHIFT+F pressed - Center view on selection
centerViewOnSelection(orbitControlsRef, camera);
}
};

window.addEventListener('keydown', handleKeyDown);

return () => {
gl.domElement.removeEventListener('pointerdown', handlePointerDown);
window.removeEventListener('keydown', handleKeyDown);
};
}, [camera, gl, scene]);
}, [camera, orbitControlsRef, scene]);

return null; // This component doesn't render anything
return null;
};

export default CameraControls;
122 changes: 78 additions & 44 deletions src/frontend/src/components/viewer/ThreeModel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {useTreeViewStore} from '../../state/treeViewStore';
import {useOptionsStore} from "../../state/optionsStore";
import {buildTreeFromUserData} from '../../utils/tree_view/generateTree';
import {handleClickMesh} from "../../utils/mesh_select/handleClickMesh";

import {CustomBatchedMesh} from '../../utils/mesh_select/CustomBatchedMesh';

const ThreeModel: React.FC<ModelProps> = ({url}) => {
const {raycaster, camera} = useThree();
Expand All @@ -23,9 +23,10 @@ const ThreeModel: React.FC<ModelProps> = ({url}) => {
const {showEdges} = useOptionsStore();

useAnimationEffects(animations, scene);

useEffect(() => {
THREE.Object3D.DEFAULT_UP.set(0, 0, 1);
if (scene){
if (scene) {
useModelStore.getState().setScene(scene);
}

Expand All @@ -39,58 +40,92 @@ const ThreeModel: React.FC<ModelProps> = ({url}) => {
camera.layers.enable(0);
camera.layers.enable(1);

const meshesToReplace: { original: THREE.Mesh; parent: THREE.Object3D }[] = [];

scene.traverse((object) => {
if (object instanceof THREE.Mesh) {
// Ensure geometry has normals
if (!object.geometry.hasAttribute('normal')) {
object.geometry.computeVertexNormals();
meshesToReplace.push({original: object, parent: object.parent!});
} else if (object instanceof THREE.LineSegments) {
object.layers.set(1);
} else if (object instanceof THREE.Points) {
object.layers.set(1);
}
});

// Replace meshes with CustomBatchedMesh instances
for (const {original, parent} of meshesToReplace) {
// Extract draw ranges from userData for the given mesh name
const meshName = original.name;
const drawRangesData = scene.userData[`draw_ranges_${meshName}`] as Record<string, [number, number]>;

// Convert drawRangesData to a Map
const drawRanges = new Map<string, [number, number]>();
if (drawRangesData) {
for (const [rangeId, [start, count]] of Object.entries(drawRangesData)) {
drawRanges.set(rangeId, [start, count]);
}
}

// Set materials to double-sided and enable flat shading
if (Array.isArray(object.material)) {
object.material.forEach((mat) => {
const customMesh = new CustomBatchedMesh(
original.geometry,
original.material,
drawRanges
);

// Copy over properties from original mesh to customMesh
customMesh.position.copy(original.position);
customMesh.rotation.copy(original.rotation);
customMesh.scale.copy(original.scale);
customMesh.name = original.name;
customMesh.userData = original.userData;
customMesh.castShadow = original.castShadow;
customMesh.receiveShadow = original.receiveShadow;
customMesh.visible = original.visible;
customMesh.frustumCulled = original.frustumCulled;
customMesh.renderOrder = original.renderOrder;
customMesh.layers.mask = original.layers.mask;

// Set materials to double-sided and enable flat shading
if (Array.isArray(customMesh.material)) {
customMesh.material.forEach((mat) => {
if (mat instanceof THREE.MeshStandardMaterial) {
mat.side = THREE.DoubleSide;
mat.flatShading = true;
mat.needsUpdate = true;
});
} else {
console.warn('Material is not an instance of MeshStandardMaterial');
}
});
} else {
if (customMesh.material instanceof THREE.MeshStandardMaterial) {
customMesh.material.side = THREE.DoubleSide;
customMesh.material.flatShading = true;
customMesh.material.needsUpdate = true;
} else {
object.material.side = THREE.DoubleSide;
object.material.flatShading = true;
object.material.needsUpdate = true;
console.warn('Material is not an instance of MeshStandardMaterial');
}
}

if (showEdges) {
// Create edges geometry and add it as a line segment
const edges = new THREE.EdgesGeometry(object.geometry);
const lineMaterial = new THREE.LineBasicMaterial({color: 0x000000});
const edgeLine = new THREE.LineSegments(edges, lineMaterial);

// Make sure the edge line inherits position and rotation of the object
edgeLine.position.copy(object.position);
edgeLine.rotation.copy(object.rotation);
edgeLine.scale.copy(object.scale);
edgeLine.layers.set(1);
// Add edge lines to the scene
scene.add(edgeLine);
}
if (showEdges) {
// Create edges geometry and add it as a line segment
const edges = new THREE.EdgesGeometry(customMesh.geometry);
const lineMaterial = new THREE.LineBasicMaterial({color: 0x000000});
const edgeLine = new THREE.LineSegments(edges, lineMaterial);

// Enable shadow casting and receiving
// object.castShadow = true;
// object.receiveShadow = true;
} else if (object instanceof THREE.LineSegments) {
// Line segments should by default not be clickable
//object.userData.clickable = false;
object.layers.set(1)
} else if (object instanceof THREE.Points) {
// Set points material to double-sided
console.log(object.material);
object.layers.set(1)
} else {
console.log(`Unknown object type: ${object.type}`);
// Ensure the edge line inherits transformations
edgeLine.position.copy(customMesh.position);
edgeLine.rotation.copy(customMesh.rotation);
edgeLine.scale.copy(customMesh.scale);
edgeLine.layers.set(1);

// Add edge lines to the scene
scene.add(edgeLine);
}

});
// Replace the original mesh with the custom mesh
parent.add(customMesh);
parent.remove(original);
}

// Replace black materials with default gray material
replaceBlackMaterials(scene);
Expand All @@ -106,6 +141,7 @@ const ThreeModel: React.FC<ModelProps> = ({url}) => {
const minY = boundingBox.min.y;
const bheight = boundingBox.max.y - minY;
translation.y = -minY + bheight * 0.05;

// Apply the translation to the model
scene.position.add(translation);

Expand All @@ -116,9 +152,7 @@ const ThreeModel: React.FC<ModelProps> = ({url}) => {

// Generate the tree data and update the store
const treeData = buildTreeFromUserData(scene);
// const treeData = buildTreeFromScene(scene);
if (treeData)
setTreeData(treeData); // Update the tree view store with the scene graph data
if (treeData) setTreeData(treeData);

// Cleanup when the component is unmounted
return () => {
Expand All @@ -136,7 +170,7 @@ const ThreeModel: React.FC<ModelProps> = ({url}) => {
return (
<primitive
object={scene}
onClick={handleClickMesh}
onPointerDown={handleClickMesh}
dispose={null}
/>
);
Expand Down
16 changes: 0 additions & 16 deletions src/frontend/src/state/selectedObjectStore.ts

This file was deleted.

36 changes: 36 additions & 0 deletions src/frontend/src/state/useSelectedObjectStore.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { create } from 'zustand';
import { CustomBatchedMesh } from '../utils/mesh_select/CustomBatchedMesh';

type SelectedObjectState = {
selectedObjects: Map<CustomBatchedMesh, Set<string>>;
addSelectedObject: (mesh: CustomBatchedMesh, drawRangeId: string) => void;
removeSelectedObject: (mesh: CustomBatchedMesh, drawRangeId: string) => void;
clearSelectedObjects: () => void;
};

export const useSelectedObjectStore = create<SelectedObjectState>((set) => ({
selectedObjects: new Map(),
addSelectedObject: (mesh, drawRangeId) =>
set((state) => {
const newMap = new Map(state.selectedObjects);
const existingSet = newMap.get(mesh) || new Set<string>();
existingSet.add(drawRangeId);
newMap.set(mesh, existingSet);
return { selectedObjects: newMap };
}),
removeSelectedObject: (mesh, drawRangeId) =>
set((state) => {
const newMap = new Map(state.selectedObjects);
const existingSet = newMap.get(mesh);
if (existingSet) {
existingSet.delete(drawRangeId);
if (existingSet.size === 0) {
newMap.delete(mesh);
} else {
newMap.set(mesh, existingSet);
}
}
return { selectedObjects: newMap };
}),
clearSelectedObjects: () => set({ selectedObjects: new Map() }),
}));
Loading

0 comments on commit b1075b1

Please sign in to comment.