I have created meshes and after that i have passed them under the parent mesh. Now in this shared file i am trying to filter the meshes and taking only parent one during highlight I am trying to apply highlight to the parent one all along so that that whole meshes should Highlight.
If i remove the parent loop condition for hover over and selection its working fine for every meshes on the screen but I want to limit the selection and make the group selection by user.
Here is my program
import { useEffect, useState, useRef } from "react";
import * as BABYLON from "@babylonjs/core";
interface MeshSelectionControlProps {
scene: BABYLON.Scene | null;
selectableMeshes: BABYLON.AbstractMesh[];
onSelectionChange?: (mesh: BABYLON.AbstractMesh | BABYLON.AbstractMesh[] | null) => void;
onUndo?: () => void;
onRedo?: () => void;
onDelete?: () => void;
}
const MeshSelectionControl: React.FC<MeshSelectionControlProps> = ({
scene,
selectableMeshes,
onSelectionChange,
onUndo,
onRedo,
onDelete
}) => {
const [history, setHistory] = useState<(BABYLON.AbstractMesh | BABYLON.AbstractMesh[])[][]>([]);
const [redoStack, setRedoStack] = useState<(BABYLON.AbstractMesh | BABYLON.AbstractMesh[])[][]>([]);
const [clickCount, setClickCount] = useState(0);
const selectedMeshRef = useRef<BABYLON.Mesh | null>(null);
const highlightLayerRef = useRef<BABYLON.HighlightLayer | null>(null);
const hoveredMeshRef = useRef<BABYLON.Mesh | null>(null);
const groupedMeshesRef = useRef<BABYLON.AbstractMesh[]>([]);
useEffect(() => {
if (!scene) return;
// ✅ Create highlight layer once
const highlightLayer = new BABYLON.HighlightLayer("highlight", scene);
highlightLayerRef.current = highlightLayer;
// Filter out water-* and grass-* meshes
const filteredMeshes = selectableMeshes.filter(
(mesh) => !/^water-|^grass-|^shoreline|^point/.test(mesh.name)
);
console.log("filtered selectable meshes:",filteredMeshes.map(mesh => mesh.name));
// Group measure elements
const grouped: BABYLON.AbstractMesh[] = [];
for (let i = 0; i < filteredMeshes.length; i++) {
const mesh = filteredMeshes[i];
if (mesh.name === "finalMeasureLine" && i + 4 < filteredMeshes.length) {
const group = filteredMeshes.slice(i, i + 5);
group.forEach(m => (m.metadata = { group }));
grouped.push(...group); // Add as a subarray
// Skip the next 4 elements (since they are already grouped)
i += 4;
} else {
// Get the parent only if it's an AbstractMesh
const topMesh = mesh.parent instanceof BABYLON.AbstractMesh ? mesh.parent : mesh;
// Avoid adding the same mesh or parent twice
if (!grouped.includes(topMesh)) {
grouped.push(topMesh);
}
}
}
groupedMeshesRef.current = grouped;
//console.log("grouped selectable meshes1:", groupedMeshesRef..;
highlightLayer.isEnabled = true;
const handlePointerMove = (event: PointerEvent) => {
const pickInfo = scene.pick(scene.pointerX, scene.pointerY, (mesh) => mesh.isPickable && mesh.visibility > 0.5);
if (pickInfo?.hit && pickInfo.pickedMesh instanceof BABYLON.Mesh) {
console.log("Mesh HOVER:", pickInfo.pickedMesh.name);
// ✅ Get the parent mesh if available
const parentMesh = pickInfo.pickedMesh.parent instanceof BABYLON.Mesh
? pickInfo.pickedMesh.parent
: pickInfo.pickedMesh;
if (groupedMeshesRef.current.includes(parentMesh)) {
const newHoveredMesh = parentMesh;
if (hoveredMeshRef.current !== newHoveredMesh) {
// Remove previous hover highlight
if (hoveredMeshRef.current && hoveredMeshRef.current !== selectedMeshRef.current) {
highlightLayer.removeMesh(hoveredMeshRef.current);
}
newHoveredMesh.isPickable = true;
// ✅ Apply material updates to ALL children of the parent mesh
const allChildren = newHoveredMesh.getChildMeshes();
allChildren.forEach(child => {
if (child.material) {
child.material.transparencyMode = BABYLON.Material.MATERIAL_ALPHABLEND;
child.material.alphaMode = BABYLON.Engine.ALPHA_COMBINE;
child.material.needDepthPrePass = true;
}
});
newHoveredMesh.material.transparencyMode = BABYLON.Material.MATERIAL_ALPHABLEND;
newHoveredMesh.material.alphaMode = BABYLON.Engine.ALPHA_COMBINE;
newHoveredMesh.material.needDepthPrePass = true;
console.log("Checking Mesh HOVER:", newHoveredMesh.name);
// Apply hover highlight only if it's not the selected mesh
if (newHoveredMesh !== selectedMeshRef.current) {
highlightLayer.addMesh(newHoveredMesh, BABYLON.Color3.Yellow());
console.log("HIGHLIGHTING Mesh HOVER:", newHoveredMesh.name);
}
hoveredMeshRef.current = newHoveredMesh;
}
}
} else {
// Remove hover highlight if cursor leaves any mesh
if (
hoveredMeshRef.current &&
hoveredMeshRef.current !== selectedMeshRef.current
) {
highlightLayer.removeMesh(hoveredMeshRef.current);
}
hoveredMeshRef.current = null;
}
};
const handlePointerDown = (event: PointerEvent) => {
if (!scene) return;
const pickInfo = scene.pick(scene.pointerX, scene.pointerY, (mesh) => mesh.isPickable);
if (pickInfo?.hit && pickInfo.pickedMesh instanceof BABYLON.Mesh) {
console.log("Mesh clicked:", pickInfo.pickedMesh.name);
// ✅ Get the parent mesh if available
const parentMesh = pickInfo.pickedMesh.parent instanceof BABYLON.Mesh
? pickInfo.pickedMesh.parent
: pickInfo.pickedMesh;
if (groupedMeshesRef.current.includes(parentMesh)) {
console.log("Mesh clicked (parent selected):", parentMesh.name);
// Remove highlight from previous selection
if (selectedMeshRef.current) {
highlightLayerRef.current?.removeMesh(selectedMeshRef.current);
}
console.log("Mesh clicked (checking):", parentMesh.name);
// Apply highlight to new selection
if (highlightLayerRef.current) {
console.log("Highlight layer exists, adding mesh...");
highlightLayerRef.current.addMesh(parentMesh, BABYLON.Color3.Green());
} else {
console.warn("Highlight layer is null or undefined!");
}
//console.log("Meshes in highlight layer:", highlightLayer.meshes.map(m => m.name));
// Ensure material updates properly
// ✅ Apply material updates to ALL children of the parent mesh
const allChildren = parentMesh.getChildMeshes();
allChildren.forEach(child => {
if (child.material) {
child.material.transparencyMode = BABYLON.Material.MATERIAL_ALPHABLEND;
child.material.alphaMode = BABYLON.Engine.ALPHA_COMBINE;
child.material.needDepthPrePass = true;
}
});
// Store new selection
selectedMeshRef.current = parentMesh;
onSelectionChange?.(parentMesh);
// Force immediate rendering update
scene.markAllMaterialsAsDirty(BABYLON.Material.AllDirtyFlag);
scene.render();
}
}
};
const handleKeyDown = (event: KeyboardEvent) => {
if (event.key === "Delete" && selectedMeshRef.current) {
console.log("Delete key pressed");
if (selectedMeshRef.current) {
setHistory((prev) => [...prev, [selectedMeshRef.current as BABYLON.AbstractMesh]]);
selectedMeshRef.current.setEnabled(false); // Disable instead of dispose
selectedMeshRef.current = null;
}
onSelectionChange?.(null);
highlightLayerRef.current?.removeAllMeshes();
onDelete?.();
}
if (event.ctrlKey && event.key === "z") {
console.log("Undo (Ctrl+Z)");
undo();
}
if (event.ctrlKey && event.key === "y") {
console.log("Redo (Ctrl+Y)");
redo();
}
};
const undo = () => {
if (history.length > 0) {
// Undo from history if available
const lastMesh = history.pop();
if (lastMesh) {
lastMesh.flat().forEach(mesh => mesh.setEnabled(true));
setRedoStack((prev) => [...prev, lastMesh]);
onUndo?.();
}
} else if (groupedMeshesRef.current.length > 0) {
// Find the last enabled element
const lastEnabledIndex = groupedMeshesRef.current.slice().reverse().findIndex(item =>
Array.isArray(item)
? item.some(mesh => mesh instanceof BABYLON.Mesh && mesh.isEnabled())
: item instanceof BABYLON.Mesh && item.isEnabled()
);
if (lastEnabledIndex !== -1) {
const lastAdded = groupedMeshesRef.current[groupedMeshesRef.current.length - 1 - lastEnabledIndex];
(Array.isArray(lastAdded) ? lastAdded : [lastAdded]).forEach(mesh => {
if (mesh instanceof BABYLON.Mesh && mesh.isEnabled()) {
mesh.setEnabled(false);
}
});
console.log("Undo last added enabled element:", lastAdded);
setRedoStack((prev) => [...prev, [lastAdded]]);
onUndo?.();
}
}
};
const redo = () => {
if (redoStack.length === 0) return;
const lastRedo = redoStack.pop();
if (lastRedo) {
lastRedo.flat().forEach(mesh => mesh.setEnabled(false)); // Disable again on redo
setHistory((prev) => [...prev]);
onRedo?.();
}
};
scene.getEngine().getRenderingCanvas()?.addEventListener("pointermove", handlePointerMove);
scene.getEngine().getRenderingCanvas()?.addEventListener("pointerdown", handlePointerDown);
window.addEventListener("keydown", handleKeyDown);
return () => {
scene.getEngine().getRenderingCanvas()?.removeEventListener("pointermove", handlePointerMove);
scene.getEngine().getRenderingCanvas()?.removeEventListener("pointerdown", handlePointerDown);
window.removeEventListener("keydown", handleKeyDown);
highlightLayer.dispose();
};
}, [scene, selectableMeshes, history, redoStack, onSelectionChange, onUndo, onRedo, onDelete, clickCount]);
return null;
};
export default MeshSelectionControl;