diff --git a/packages/scene-composer/package.json b/packages/scene-composer/package.json index d3f1dacb7..7aae6b584 100644 --- a/packages/scene-composer/package.json +++ b/packages/scene-composer/package.json @@ -155,9 +155,9 @@ "coverageThreshold": { "global": { "lines": 77.56, - "statements": 76.71, - "functions": 77.64, - "branches": 63.57, + "statements": 76.73, + "functions": 77.68, + "branches": 63.67, "branchesTrue": 100 } } diff --git a/packages/scene-composer/src/components/three-fiber/CameraComponent/__tests__/CameraComponent.spec.tsx b/packages/scene-composer/src/components/three-fiber/CameraComponent/__tests__/CameraComponent.spec.tsx index b841c2ce0..2a1a812b9 100644 --- a/packages/scene-composer/src/components/three-fiber/CameraComponent/__tests__/CameraComponent.spec.tsx +++ b/packages/scene-composer/src/components/three-fiber/CameraComponent/__tests__/CameraComponent.spec.tsx @@ -28,14 +28,6 @@ jest.mock('@react-three/fiber', () => { }; }); -jest.mock('@react-three/drei/core/useHelper', () => { - const originalModule = jest.requireActual('@react-three/drei/core/useHelper'); - return { - ...originalModule, - useHelper: jest.fn(() => ({})), - }; -}); - jest.mock('../../../../hooks/useSelectedNode', () => { return jest.fn().mockReturnValue({ selectedSceneNodeRef: 'testRef', @@ -43,7 +35,7 @@ jest.mock('../../../../hooks/useSelectedNode', () => { }); jest.mock('../../../../hooks/useActiveCamera', () => { - return jest.fn(); + return jest.fn().mockReturnValue({ activeCameraName: 'test-camera', setActiveCameraName: jest.fn() }); }); describe('CameraComponent', () => { diff --git a/packages/scene-composer/src/components/three-fiber/CameraComponent/__tests__/__snapshots__/CameraComponent.spec.tsx.snap b/packages/scene-composer/src/components/three-fiber/CameraComponent/__tests__/__snapshots__/CameraComponent.spec.tsx.snap index ca75ea67d..3729673e7 100644 --- a/packages/scene-composer/src/components/three-fiber/CameraComponent/__tests__/__snapshots__/CameraComponent.spec.tsx.snap +++ b/packages/scene-composer/src/components/three-fiber/CameraComponent/__tests__/__snapshots__/CameraComponent.spec.tsx.snap @@ -13,20 +13,9 @@ exports[`CameraComponent should render correctly for orthographic camera 1`] = ` -
- -
+
`; @@ -36,17 +25,9 @@ exports[`CameraComponent should render correctly for perspective camera 1`] = ` -
- -
+
`; diff --git a/packages/scene-composer/src/components/three-fiber/CameraComponent/index.tsx b/packages/scene-composer/src/components/three-fiber/CameraComponent/index.tsx index 5048a6d16..108aeedba 100644 --- a/packages/scene-composer/src/components/three-fiber/CameraComponent/index.tsx +++ b/packages/scene-composer/src/components/three-fiber/CameraComponent/index.tsx @@ -1,8 +1,6 @@ import * as THREE from 'three'; -import React, { useEffect } from 'react'; -import { useHelper } from '@react-three/drei/core/useHelper'; -import { OrthographicCamera, PerspectiveCamera } from '@react-three/drei'; -import { Camera, useThree } from '@react-three/fiber'; +import React, { useEffect, useMemo } from 'react'; +import { useThree } from '@react-three/fiber'; import useLifecycleLogging from '../../../logger/react-logger/hooks/useLifecycleLogging'; import { ISceneNodeInternal, useEditorState, ICameraComponentInternal } from '../../../store'; @@ -20,14 +18,10 @@ interface ICameraComponentProps { const CameraComponent: React.FC = ({ node, component }: ICameraComponentProps) => { const sceneComposerId = useSceneComposerId(); useLifecycleLogging('CameraComponent'); - const camera = React.useRef(); - - const cameraHelperRef = useHelper(camera, THREE.CameraHelper); + const { isEditing, getObject3DBySceneNodeRef } = useEditorState(sceneComposerId); const size = useThree((state) => state.size); const { fov, far, near, zoom } = component; - const { isEditing, isLoadingModel, getObject3DBySceneNodeRef } = useEditorState(sceneComposerId); - const { selectedSceneNodeRef } = useSelectedNode(); const { activeCameraName, setActiveCameraSettings } = useActiveCamera(); @@ -47,40 +41,21 @@ const CameraComponent: React.FC = ({ node, component }: I } }, [activeCameraName]); - useEffect(() => { - if (cameraHelperRef.current) { - cameraHelperRef.current.visible = isEditing() && !isLoadingModel; + const cameraHelper = useMemo(() => { + let camera: THREE.OrthographicCamera | THREE.PerspectiveCamera; + if (component.cameraType === 'Orthographic') { + camera = new THREE.OrthographicCamera(-3, 3, 3, -3, near, far); + camera.zoom = zoom; + } else { + camera = new THREE.PerspectiveCamera(fov, size.width / size.height, near, far); + camera.zoom = zoom; } - }, [isEditing, isLoadingModel, cameraHelperRef]); - - let cameraNode: JSX.Element; - if (component.cameraType === 'Orthographic') { - cameraNode = ( - - - - ); - } else { - cameraNode = ( - - - - ); - } + return new THREE.CameraHelper(camera); + }, [component.cameraType]); return ( - {isEditing() && selectedSceneNodeRef === node.ref && cameraNode} + {isEditing() && selectedSceneNodeRef === node.ref && } ); }; diff --git a/packages/scene-composer/src/hooks/useEditorHelper.ts b/packages/scene-composer/src/hooks/useEditorHelper.ts index 608141ca9..beef2fcf6 100644 --- a/packages/scene-composer/src/hooks/useEditorHelper.ts +++ b/packages/scene-composer/src/hooks/useEditorHelper.ts @@ -1,8 +1,9 @@ import * as THREE from 'three'; import { MutableRefObject, useEffect, useRef } from 'react'; -import { useThree, useFrame } from '@react-three/fiber'; +import { useFrame } from '@react-three/fiber'; import { useStore } from '../store'; +import { getFinalTransform } from '../utils/nodeUtils'; type Helper = THREE.Object3D & { update: () => void; @@ -28,24 +29,34 @@ export function useEditorHelper( ) { const helper = useRef(); - const scene = useThree((state) => state.scene); - useEffect(() => { if (isEditing) { if (proto && object3D.current) { helper.current = new (proto as any)(object3D.current, ...args); + if (helper.current) { - scene.add(helper.current); + const helperTransform = { + position: helper.current.position.clone(), + rotation: helper.current.rotation.clone(), + scale: helper.current.scale.clone(), + }; + const finalTransform = getFinalTransform(helperTransform, object3D.current); + const group = new THREE.Group(); + group.add(helper.current); + group.position.copy(finalTransform.position); + group.rotation.copy(finalTransform.rotation); + group.scale.copy(finalTransform.scale); + object3D.current.add(group); } } } return () => { - if (helper.current) { - scene.remove(helper.current); + if (helper.current?.parent) { + object3D.current?.remove(helper.current.parent); } }; - }, [isEditing, scene, proto, object3D, args]); + }, [isEditing, proto, object3D, args]); useStore(sceneComposerId).subscribe((state) => { if (helper.current) { diff --git a/packages/scene-composer/src/utils/nodeUtils.ts b/packages/scene-composer/src/utils/nodeUtils.ts index 547ce8d56..4db361aaf 100644 --- a/packages/scene-composer/src/utils/nodeUtils.ts +++ b/packages/scene-composer/src/utils/nodeUtils.ts @@ -46,7 +46,7 @@ type Transform = { * @param {THREE.Object3D} parent * @returns {Transform} final transform based on all ancestors. */ -const getFinalTransform = (transform: Transform, parent?: THREE.Object3D | null): Transform => { +export const getFinalTransform = (transform: Transform, parent?: THREE.Object3D | null): Transform => { if (!parent) return transform; const finalPosition = parent.worldToLocal(transform.position.clone()); diff --git a/packages/scene-composer/tests/hooks/useEditorHelper.spec.tsx b/packages/scene-composer/tests/hooks/useEditorHelper.spec.tsx index 2c1aeb141..cdf5d832f 100644 --- a/packages/scene-composer/tests/hooks/useEditorHelper.spec.tsx +++ b/packages/scene-composer/tests/hooks/useEditorHelper.spec.tsx @@ -8,23 +8,18 @@ import { useStore } from '../../src/store'; let container = null; let helper = null; -let scene = null; jest.mock('@react-three/fiber', () => { const originalModule = jest.requireActual('@react-three/fiber'); return { __esModule: true, ...originalModule, - useThree: jest.fn(() => scene), useFrame: jest.fn(), }; }); beforeEach(() => { - scene = { - add: jest.fn(), - remove: jest.fn(), - } as any; + jest.clearAllMocks(); container = document.createElement('div') as any; document.body.appendChild(container as any); }); @@ -36,11 +31,13 @@ afterEach(() => { }); const object3D = new Object3D(); +object3D.add = jest.fn(); +object3D.remove = jest.fn(); const mutableRefObj = { current: object3D, }; -class HelperType {} +class HelperType extends Object3D {} interface TestComponentPros { isEditing: boolean; @@ -57,14 +54,13 @@ describe('return correct editor helper.', () => { act(() => { render(, container); }); - expect((scene as any).add).toBeCalledTimes(1); + expect(object3D.add).toBeCalledTimes(1); act(() => { unmountComponentAtNode(container as any); }); - expect((scene as any).remove).toBeCalledTimes(1); - expect((helper as any).current.visible).toBe(undefined); + expect(object3D.remove).toBeCalledTimes(1); useStore('sceneComposerId').setState({}); expect((helper as any).current.visible).toBe(true); }); @@ -73,9 +69,9 @@ describe('return correct editor helper.', () => { act(() => { render(, container); }); - expect((scene as any).add).toBeCalledTimes(0); + expect(object3D.add).toBeCalledTimes(0); unmountComponentAtNode(container as any); - expect((scene as any).remove).toBeCalledTimes(0); + expect(object3D.remove).toBeCalledTimes(0); expect((helper as any).current).toBe(undefined); useStore('sceneComposerId').setState({}); expect((helper as any).current).toBe(undefined);