Skip to content
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 packages/dev/core/src/Gizmos/cameraGizmo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,10 +67,10 @@ export class CameraGizmo extends Gizmo implements ICameraGizmo {
}

this._isHovered = !!(pointerInfo.pickInfo && this._rootMesh.getChildMeshes().indexOf(<Mesh>pointerInfo.pickInfo.pickedMesh) != -1);
if (this._isHovered && pointerInfo.event.button === 0) {
if (this._isHovered && pointerInfo.type === PointerEventTypes.POINTERDOWN && pointerInfo.event.button === 0) {
this.onClickedObservable.notifyObservers(this._camera);
}
}, PointerEventTypes.POINTERDOWN);
});
}
protected _camera: Nullable<Camera> = null;

Expand Down
4 changes: 2 additions & 2 deletions packages/dev/core/src/Gizmos/lightGizmo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,10 +70,10 @@ export class LightGizmo extends Gizmo implements ILightGizmo {
}

this._isHovered = !!(pointerInfo.pickInfo && this._rootMesh.getChildMeshes().indexOf(<Mesh>pointerInfo.pickInfo.pickedMesh) != -1);
if (this._isHovered && pointerInfo.event.button === 0) {
if (this._isHovered && pointerInfo.type === PointerEventTypes.POINTERDOWN && pointerInfo.event.button === 0) {
this.onClickedObservable.notifyObservers(this._light);
}
}, PointerEventTypes.POINTERDOWN);
});
}
protected _light: Nullable<Light> = null;

Expand Down
128 changes: 128 additions & 0 deletions packages/dev/inspector-v2/src/components/pickingToolbar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
import type { FunctionComponent } from "react";

import type { AbstractMesh, IMeshDataCache, Scene } from "core/index";
import type { IGizmoService } from "../services/gizmoService";

import { TargetRegular } from "@fluentui/react-icons";
import { useEffect, useMemo, useState } from "react";

import { PointerEventTypes } from "core/Events/pointerEvents";
import { TmpVectors, Vector3 } from "core/Maths/math.vector";
import { ToggleButton } from "shared-ui-components/fluent/primitives/toggleButton";

export const PickingToolbar: FunctionComponent<{
scene: Scene;
selectEntity: (entity: unknown) => void;
gizmoService: IGizmoService;
ignoreBackfaces?: boolean;
}> = (props) => {
const { scene, selectEntity, gizmoService, ignoreBackfaces } = props;

const meshDataCache = useMemo(() => new WeakMap<AbstractMesh, IMeshDataCache>(), [scene]);
// Not sure why changing the cursor on the canvas itself doesn't work, so change it on the parent.
const sceneElement = scene.getEngine().getRenderingCanvas()?.parentElement;

const [pickingEnabled, setPickingEnabled] = useState(false);

useEffect(() => {
if (pickingEnabled && sceneElement) {
const originalCursor = getComputedStyle(sceneElement).cursor;
sceneElement.style.cursor = "crosshair";

const pointerObserver = scene.onPrePointerObservable.add(() => {
let pickedEntity: unknown = null;

// Check camera gizmos.
if (!pickedEntity) {
for (const cameraGizmo of gizmoService.getCameraGizmos(scene)) {
if (cameraGizmo.isHovered) {
pickedEntity = cameraGizmo.camera;
}
}
}

// Check light gizmos.
if (!pickedEntity) {
for (const lightGizmo of gizmoService.getLightGizmos(scene)) {
if (lightGizmo.isHovered) {
pickedEntity = lightGizmo.light;
}
}
}

// Check the main scene.
if (!pickedEntity) {
// Refresh bounding info to ensure morph target and skeletal animations are taken into account.
for (const mesh of scene.meshes) {
let cache = meshDataCache.get(mesh);
if (!cache) {
cache = {};
meshDataCache.set(mesh, cache);
}
mesh.refreshBoundingInfo({ applyMorph: true, applySkeleton: true, cache });
}

const pickingInfo = scene.pick(
scene.unTranslatedPointer.x,
scene.unTranslatedPointer.y,
(mesh) => mesh.isEnabled() && mesh.isVisible && mesh.getTotalVertices() > 0,
false,
undefined,
(p0, p1, p2, ray) => {
if (!ignoreBackfaces) {
return true;
}

const p0p1 = TmpVectors.Vector3[0];
const p1p2 = TmpVectors.Vector3[1];
let normal = TmpVectors.Vector3[2];

p1.subtractToRef(p0, p0p1);
p2.subtractToRef(p1, p1p2);

normal = Vector3.Cross(p0p1, p1p2);

return Vector3.Dot(normal, ray.direction) < 0;
}
);

pickedEntity = pickingInfo.pickedMesh;
}

// If an entity was picked, select it.
if (pickedEntity) {
selectEntity(pickedEntity);
}
}, PointerEventTypes.POINTERTAP);

// Exit picking mode if the escape key is pressed.
const handleKeyDown = (e: KeyboardEvent) => {
if (e.key === "Escape") {
setPickingEnabled(false);
}
};
document.addEventListener("keydown", handleKeyDown);

return () => {
sceneElement.style.cursor = originalCursor;
pointerObserver.remove();
document.removeEventListener("keydown", handleKeyDown);
};
}

return () => {
/* No-op */
};
}, [pickingEnabled, sceneElement, ignoreBackfaces]);

return (
sceneElement && (
<ToggleButton
title={`${pickingEnabled ? "Disable" : "Enable"} Picking`}
enabledIcon={TargetRegular}
value={pickingEnabled}
onChange={() => setPickingEnabled((prev) => !prev)}
/>
)
);
};
4 changes: 4 additions & 0 deletions packages/dev/inspector-v2/src/inspector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ import { TextureExplorerServiceDefinition } from "./services/panes/scene/texture
import { SettingsServiceDefinition } from "./services/panes/settingsService";
import { StatsServiceDefinition } from "./services/panes/statsService";
import { ToolsServiceDefinition } from "./services/panes/toolsService";
import { PickingServiceDefinition } from "./services/pickingService";
import { SceneContextIdentity } from "./services/sceneContext";
import { SelectionServiceDefinition } from "./services/selectionService";
import { ShellServiceIdentity } from "./services/shellService";
Expand Down Expand Up @@ -250,6 +251,9 @@ function _ShowInspector(scene: Nullable<Scene>, options: Partial<IInspectorOptio
// Gizmos for manipulating objects in the scene.
GizmoToolbarServiceDefinition,

// Allows picking objects from the scene to select them.
PickingServiceDefinition,

// Additional services passed in to the Inspector.
...(options.serviceDefinitions ?? []),
],
Expand Down
4 changes: 4 additions & 0 deletions packages/dev/inspector-v2/src/services/gizmoService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ export interface IGizmoService extends IService<typeof GizmoServiceIdentity> {
getUtilityLayer(scene: Scene, layer?: string): Reference<UtilityLayerRenderer>;
getCameraGizmo(camera: Camera): Reference<CameraGizmo>;
getLightGizmo(light: Light): Reference<LightGizmo>;
getCameraGizmos(scene: Scene): readonly CameraGizmo[];
getLightGizmos(scene: Scene): readonly LightGizmo[];
}

export const GizmoServiceDefinition: ServiceDefinition<[IGizmoService], []> = {
Expand Down Expand Up @@ -110,6 +112,8 @@ export const GizmoServiceDefinition: ServiceDefinition<[IGizmoService], []> = {
getUtilityLayer,
getCameraGizmo,
getLightGizmo,
getCameraGizmos: (scene) => scene.cameras.map((camera) => cameraGizmos.get(camera)?.gizmo).filter(Boolean) as readonly CameraGizmo[],
getLightGizmos: (scene) => scene.lights.map((light) => lightGizmos.get(light)?.gizmo).filter(Boolean) as readonly LightGizmo[],
};
},
};
34 changes: 34 additions & 0 deletions packages/dev/inspector-v2/src/services/pickingService.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import type { ServiceDefinition } from "../modularity/serviceDefinition";
import type { IGizmoService } from "./gizmoService";
import type { ISceneContext } from "./sceneContext";
import type { ISelectionService } from "./selectionService";
import type { ISettingsContext } from "./settingsContext";
import type { IShellService } from "./shellService";

import { useCallback } from "react";
import { PickingToolbar } from "../components/pickingToolbar";
import { useObservableState } from "../hooks/observableHooks";
import { GizmoServiceIdentity } from "./gizmoService";
import { SceneContextIdentity } from "./sceneContext";
import { SelectionServiceIdentity } from "./selectionService";
import { SettingsContextIdentity } from "./settingsContext";
import { ShellServiceIdentity } from "./shellService";

export const PickingServiceDefinition: ServiceDefinition<[], [ISceneContext, IShellService, ISelectionService, IGizmoService, ISettingsContext]> = {
friendlyName: "Picking Service",
consumes: [SceneContextIdentity, ShellServiceIdentity, SelectionServiceIdentity, GizmoServiceIdentity, SettingsContextIdentity],
factory: (sceneContext, shellService, selectionService, gizmoService, settingsContext) => {
shellService.addToolbarItem({
key: "Picking Service",
verticalLocation: "top",
horizontalLocation: "left",
suppressTeachingMoment: true,
component: () => {
const scene = useObservableState(() => sceneContext.currentScene, sceneContext.currentSceneObservable);
const selectEntity = useCallback((entity: unknown) => (selectionService.selectedEntity = entity), []);
const ignoreBackfacesForPicking = useObservableState(() => settingsContext.ignoreBackfacesForPicking, settingsContext.settingsChangedObservable);
return scene ? <PickingToolbar scene={scene} selectEntity={selectEntity} gizmoService={gizmoService} ignoreBackfaces={ignoreBackfacesForPicking} /> : null;
},
});
},
};