Skip to content

Commit

Permalink
fix(scene): fix transform controls being clickable
Browse files Browse the repository at this point in the history
  • Loading branch information
haweston authored and mumanity committed Oct 5, 2023
1 parent de7cc0d commit b846730
Show file tree
Hide file tree
Showing 4 changed files with 76 additions and 15 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { sceneComposerIdContext } from '../../common/sceneComposerIdContext';
import { useEditorState } from '../../store';
import { CameraControlImpl, TweenValueObject } from '../../store/internalInterfaces';
import useActiveCamera from '../../hooks/useActiveCamera';
import useOverwriteRaycaster from '../../hooks/useOverwriteRaycaster';
import { getSafeBoundingBox } from '../../utils/objectThreeUtils';
import { getMatterportSdk } from '../../common/GlobalSettings';
import { MapControls as MapControlsImpl, OrbitControls as OrbitControlsImpl } from '../../three/OrbitControls';
Expand Down Expand Up @@ -52,6 +53,8 @@ export const EditorMainCamera = forwardRef<Camera>((_, forwardedRef) => {

const activeCamera = useActiveCamera();

const [overwriteRaycaster, restoreRaycaster] = useOverwriteRaycaster(transformControls);

const CameraControls = useMemo(() => {
// Remove CameraControls while using TransformControls
if (controlsRemove) {
Expand Down Expand Up @@ -79,12 +82,14 @@ export const EditorMainCamera = forwardRef<Camera>((_, forwardedRef) => {

if (typeof command.target !== 'string') {
if (scene && cameraRef.current) {
overwriteRaycaster();

const position = new THREE.Vector3(...command.target.position);
const direction = new THREE.Vector3(...command.target.target).sub(position).normalize();
const raycaster = new THREE.Raycaster(position, direction);
raycaster.camera = cameraRef.current;
const intersections = raycaster.intersectObjects([scene]);

restoreRaycaster();
if (intersections.length > 0) {
return { position: command.target.position, target: intersections[0].point.toArray() } as FixedCameraTarget;
}
Expand All @@ -97,7 +102,7 @@ export const EditorMainCamera = forwardRef<Camera>((_, forwardedRef) => {

log?.warn('unable to find the correct object in the scene. using the default camera setting.');
};
}, [scene]);
}, [scene, transformControls]);

const controlsRef = useCallback((ref) => {
if (ref) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import { useThree } from '@react-three/fiber';

import useLogger from '../../logger/react-logger/hooks/useLogger';
import { sceneComposerIdContext } from '../../common/sceneComposerIdContext';
import useOverwriteRaycaster from '../../hooks/useOverwriteRaycaster';
import { useStore } from '../../store';
import { TransformControls as TransformControlsImpl } from '../../three/TransformControls';
import { snapObjectToFloor } from '../../three/transformUtils';
Expand All @@ -27,7 +26,6 @@ export function EditorTransformControls() {
const addingWidget = useStore(sceneComposerId)((state) => state.addingWidget);

const [transformControls] = useState(() => new TransformControlsImpl(camera, domElement));
const overwriteRaycaster = useOverwriteRaycaster(transformControls);
const subModelMovementEnabled = getGlobalSettings().featureConfig[COMPOSER_FEATURES.SubModelMovement];

const isTagComponent = !!findComponentByType(selectedSceneNode, KnownComponentType.Tag);
Expand All @@ -41,7 +39,6 @@ export function EditorTransformControls() {
// Set transform controls' camera
useEffect(() => {
setTransformControls(transformControls);
overwriteRaycaster();
if (camera) {
// @ts-ignore: transformControls has a camera prop that TS does not know.
transformControls.camera = camera;
Expand Down
42 changes: 40 additions & 2 deletions packages/scene-composer/src/hooks/useOverwriteRaycaster.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,19 @@ describe('useOverwriteRaycaster', () => {
expect(intersectObjects.length).toBe(4);

const mockRaycaster = jest.fn();
const overwriteRayCaster = renderHook(() => useOverwriteRaycaster(parentCube, mockRaycaster)).result.current;
const [overwriteRayCaster, restoreRaycaster] = renderHook(() => useOverwriteRaycaster(parentCube, mockRaycaster))
.result.current;
overwriteRayCaster();
const intersectObjects2 = raycaster.intersectObjects([parentCube]);

expect(mockRaycaster).toBeCalledTimes(2);
expect(intersectObjects2.length).toBe(0);

restoreRaycaster();
const intersectObjects3 = raycaster.intersectObjects([parentCube]);

expect(mockRaycaster).toBeCalledTimes(2);
expect(intersectObjects3.length).toBe(4);
});

it('it overwrite the raycaster for all objects with default no op function', () => {
Expand All @@ -58,9 +65,40 @@ describe('useOverwriteRaycaster', () => {
//hit each cube on enter and leave
expect(intersectObjects.length).toBe(4);

const overwriteRayCaster = renderHook(() => useOverwriteRaycaster(parentCube)).result.current;
const [overwriteRayCaster, restoreRaycaster] = renderHook(() => useOverwriteRaycaster(parentCube)).result.current;
overwriteRayCaster();
const intersectObjects2 = raycaster.intersectObjects([parentCube]);
expect(intersectObjects2.length).toBe(0);

restoreRaycaster();
const intersectObjects3 = raycaster.intersectObjects([parentCube]);
expect(intersectObjects3.length).toBe(4);
});

it('call restore first should keep previous raycast', () => {
const parentCube = new THREE.Mesh(geometry, material);
parentCube.translateX(position.x);
parentCube.translateY(position.y);
parentCube.translateZ(position.z);
parentCube.updateMatrixWorld();
const cube2 = new THREE.Mesh(geometry, material);
cube2.translateX(position.x + 1);
cube2.translateY(position.y);
cube2.translateZ(position.z);
parentCube.add(cube2);
cube2.updateMatrixWorld();

const mockRaycaster = jest.fn();
const restoreRaycaster = renderHook(() => useOverwriteRaycaster(parentCube, mockRaycaster)).result.current[1];

const intersectObjects = raycaster.intersectObjects([parentCube]);
//hit each cube on enter and leave
expect(intersectObjects.length).toBe(4);

restoreRaycaster();
const intersectObjects2 = raycaster.intersectObjects([parentCube]);
expect(intersectObjects2.length).toBe(4);

expect(mockRaycaster).toBeCalledTimes(0);
});
});
37 changes: 29 additions & 8 deletions packages/scene-composer/src/hooks/useOverwriteRaycaster.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,38 @@
import { useCallback } from 'react';
import { useCallback, useEffect, useRef } from 'react';
import { computeMikkTSpaceTangents } from 'three/examples/jsm/utils/BufferGeometryUtils';

const useOverwriteRaycaster: (object: THREE.Object3D, raycaster?: () => void) => () => void = (
object: THREE.Object3D,
const useOverwriteRaycaster: (
object: THREE.Object3D | undefined,
raycaster?: () => void,
) => {
) => [() => void, () => void] = (object: THREE.Object3D | undefined, raycaster?: () => void) => {
const originalRaycastMap = useRef({});

useEffect(() => {
if (object) {
object.traverse((o: THREE.Object3D) => {
originalRaycastMap.current[o.uuid] = o.raycast;
});
}
}, [object]);

const overwriteRaycaster = useCallback(() => {
const raycastOverride = raycaster ? raycaster : () => {};
object.traverse((object: THREE.Object3D) => {
object.raycast = raycastOverride;
});
if (object) {
object.traverse((o: THREE.Object3D) => {
o.raycast = raycastOverride;
});
}
}, [object, raycaster]);

const restoreRaycaster = useCallback(() => {
if (object) {
object.traverse((o: THREE.Object3D) => {
o.raycast = originalRaycastMap.current[o.uuid];
});
}
}, [object]);

return overwriteRaycaster;
return [overwriteRaycaster, restoreRaycaster];
};

export default useOverwriteRaycaster;

0 comments on commit b846730

Please sign in to comment.