diff --git a/src/components/molecules/Visualizer/Engine/Cesium/common.ts b/src/components/molecules/Visualizer/Engine/Cesium/common.ts index c3bb09d76..47154fb2d 100644 --- a/src/components/molecules/Visualizer/Engine/Cesium/common.ts +++ b/src/components/molecules/Visualizer/Engine/Cesium/common.ts @@ -20,8 +20,14 @@ import { Clock as CesiumClock, JulianDate, ClockStep, + Ellipsoid, + Quaternion, + Matrix3, + Cartographic, + EllipsoidTerrainProvider, + sampleTerrainMostDetailed, } from "cesium"; -import { useCallback } from "react"; +import { useCallback, MutableRefObject } from "react"; import { useCanvas, useImage } from "@reearth/util/image"; import { tweenInterval } from "@reearth/util/raf"; @@ -439,3 +445,145 @@ export function attachTag(entity: Entity | undefined, tag: string, value: any) { entity.properties?.addProperty(tag, value); } } + +export function lookHorizontal(scene: Scene, amount: number) { + const camera = scene.camera; + const ellipsoid = scene.globe.ellipsoid; + const surfaceNormal = ellipsoid.geodeticSurfaceNormal(camera.position, new Cartesian3()); + camera.look(surfaceNormal, amount); +} + +export function lookVertical(scene: Scene, amount: number) { + const camera = scene.camera; + const ellipsoid = scene.globe.ellipsoid; + const lookAxis = projectVectorToSurface(camera.right, camera.position, ellipsoid); + const surfaceNormal = ellipsoid.geodeticSurfaceNormal(camera.position, new Cartesian3()); + const currentAngle = CesiumMath.toDegrees(Cartesian3.angleBetween(surfaceNormal, camera.up)); + const upAfterLook = rotateVectorAboutAxis(camera.up, lookAxis, amount); + const angleAfterLook = CesiumMath.toDegrees(Cartesian3.angleBetween(surfaceNormal, upAfterLook)); + const friction = angleAfterLook < currentAngle ? 1 : (90 - currentAngle) / 90; + camera.look(lookAxis, amount * friction); +} + +export function moveForward(scene: Scene, amount: number) { + const direction = projectVectorToSurface( + scene.camera.direction, + scene.camera.position, + scene.globe.ellipsoid, + ); + scene.camera.move(direction, amount); +} + +export function moveBackward(scene: Scene, amount: number) { + const direction = projectVectorToSurface( + scene.camera.direction, + scene.camera.position, + scene.globe.ellipsoid, + ); + scene.camera.move(direction, -amount); +} + +export function moveUp(scene: Scene, amount: number) { + const surfaceNormal = scene.globe.ellipsoid.geodeticSurfaceNormal( + scene.camera.position, + new Cartesian3(), + ); + scene.camera.move(surfaceNormal, amount); +} + +export function moveDown(scene: Scene, amount: number) { + const surfaceNormal = scene.globe.ellipsoid.geodeticSurfaceNormal( + scene.camera.position, + new Cartesian3(), + ); + scene.camera.move(surfaceNormal, -amount); +} + +export function moveLeft(scene: Scene, amount: number) { + const direction = projectVectorToSurface( + scene.camera.right, + scene.camera.position, + scene.globe.ellipsoid, + ); + scene.camera.move(direction, -amount); +} + +export function moveRight(scene: Scene, amount: number) { + const direction = projectVectorToSurface( + scene.camera.right, + scene.camera.position, + scene.globe.ellipsoid, + ); + scene.camera.move(direction, amount); +} + +export async function moveOverTerrain(viewer: Viewer, offset = 0) { + const camera = viewer.scene.camera; + const height = await sampleTerrainHeight(viewer.scene, camera.position); + if (height && height !== 0) { + const innerCamera = getCamera(viewer); + if (innerCamera && innerCamera?.height < height + offset) { + camera.moveUp(height + offset - innerCamera.height); + } + } +} + +export async function flyToGround( + viewer: Viewer, + cancelCameraFlight: MutableRefObject<(() => void) | undefined>, + camera?: { + lat?: number; + lng?: number; + height?: number; + heading?: number; + pitch?: number; + roll?: number; + fov?: number; + }, + options?: { + duration?: number; + easing?: (time: number) => number; + }, + offset = 0, +) { + const height = await sampleTerrainHeight(viewer.scene, viewer.scene.camera.position); + const tarHeight = height ? height + offset : offset; + const groundCamera = { ...camera, height: tarHeight }; + cancelCameraFlight.current?.(); + cancelCameraFlight.current = flyTo( + viewer.scene?.camera, + { ...getCamera(viewer), ...groundCamera }, + options, + ); +} + +function projectVectorToSurface(vector: Cartesian3, position: Cartesian3, ellipsoid: Ellipsoid) { + const surfaceNormal = ellipsoid.geodeticSurfaceNormal(position, new Cartesian3()); + const magnitudeOfProjectionOnSurfaceNormal = Cartesian3.dot(vector, surfaceNormal); + const projectionOnSurfaceNormal = Cartesian3.multiplyByScalar( + surfaceNormal, + magnitudeOfProjectionOnSurfaceNormal, + new Cartesian3(), + ); + return Cartesian3.subtract(vector, projectionOnSurfaceNormal, new Cartesian3()); +} + +function rotateVectorAboutAxis(vector: Cartesian3, rotateAxis: Cartesian3, rotateAmount: number) { + const quaternion = Quaternion.fromAxisAngle(rotateAxis, -rotateAmount, new Quaternion()); + const rotation = Matrix3.fromQuaternion(quaternion, new Matrix3()); + const rotatedVector = Matrix3.multiplyByVector(rotation, vector, vector.clone()); + return rotatedVector; +} + +async function sampleTerrainHeight( + scene: Scene, + position: Cartesian3, +): Promise { + const terrainProvider = scene.terrainProvider; + if (terrainProvider instanceof EllipsoidTerrainProvider) return 0; + + const [sample] = await sampleTerrainMostDetailed(terrainProvider, [ + Cartographic.fromCartesian(position, scene.globe.ellipsoid, new Cartographic()), + ]); + return sample.height; +} diff --git a/src/components/molecules/Visualizer/Engine/Cesium/useEngineRef.test.tsx b/src/components/molecules/Visualizer/Engine/Cesium/useEngineRef.test.tsx index af12190fd..64dfc362b 100644 --- a/src/components/molecules/Visualizer/Engine/Cesium/useEngineRef.test.tsx +++ b/src/components/molecules/Visualizer/Engine/Cesium/useEngineRef.test.tsx @@ -1,5 +1,12 @@ import { renderHook } from "@testing-library/react"; -import { ClockStep, JulianDate, Viewer as CesiumViewer } from "cesium"; +import { + ClockStep, + JulianDate, + Viewer as CesiumViewer, + Cartesian3, + Globe, + Ellipsoid, +} from "cesium"; import { useRef } from "react"; import type { CesiumComponentRef } from "resium"; import { vi, expect, test } from "vitest"; @@ -134,15 +141,13 @@ test("requestRender", () => { const { result } = renderHook(() => { const cesium = useRef>({ cesiumElement: { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore scene: { requestRender: mockRequestRender, }, isDestroyed: () => { return false; }, - }, + } as any, }); const engineRef = useRef(null); useEngineRef(engineRef, cesium); @@ -158,11 +163,7 @@ test("zoom", () => { const { result } = renderHook(() => { const cesium = useRef>({ cesiumElement: { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore scene: { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore camera: { zoomIn: mockZoomIn, zoomOut: mockZoomOut, @@ -171,7 +172,7 @@ test("zoom", () => { isDestroyed: () => { return false; }, - }, + } as any, }); const engineRef = useRef(null); useEngineRef(engineRef, cesium); @@ -199,8 +200,6 @@ test("getClock", () => { const { result } = renderHook(() => { const cesium = useRef>({ cesiumElement: { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore clock: { startTime, stopTime, @@ -209,8 +208,6 @@ test("getClock", () => { shouldAnimate: false, multiplier: 1, clockStep: ClockStep.SYSTEM_CLOCK, - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore // TODO: should test cesium event onTick: { addEventListener: mockAddEventHandler, @@ -220,7 +217,7 @@ test("getClock", () => { isDestroyed: () => { return false; }, - }, + } as any, }); const engineRef = useRef(null); useEngineRef(engineRef, cesium); @@ -284,12 +281,10 @@ test("captureScreen", () => { cesiumElement: { render: mockViewerRender, isDestroyed: mockViewerIsDestroyed, - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore canvas: { toDataURL: mockCanvasToDataURL, }, - }, + } as any, }); const engineRef = useRef(null); useEngineRef(engineRef, cesium); @@ -303,3 +298,83 @@ test("captureScreen", () => { result.current.current?.captureScreen("image/jpeg", 0.8); expect(mockCanvasToDataURL).toHaveBeenCalledWith("image/jpeg", 0.8); }); + +test("move", () => { + const mockMove = vi.fn(e => e); + const { result } = renderHook(() => { + const cesium = useRef>({ + cesiumElement: { + isDestroyed: () => false, + scene: { + camera: { + position: new Cartesian3(0, 0, 1), + direction: Cartesian3.clone(Cartesian3.UNIT_X), + up: Cartesian3.clone(Cartesian3.UNIT_Z), + right: Cartesian3.clone(Cartesian3.UNIT_Y), + move: mockMove, + }, + globe: new Globe(Ellipsoid.UNIT_SPHERE), + }, + } as any, + }); + const engineRef = useRef(null); + useEngineRef(engineRef, cesium); + return engineRef; + }); + + result.current.current?.moveForward(100); + expect(mockMove).toHaveBeenCalledTimes(1); + expect(mockMove).toHaveBeenLastCalledWith(new Cartesian3(1, 0, 0), 100); + + result.current.current?.moveBackward(100); + expect(mockMove).toHaveBeenCalledTimes(2); + expect(mockMove).toHaveBeenLastCalledWith(new Cartesian3(1, 0, 0), -100); + + result.current.current?.moveUp(100); + expect(mockMove).toHaveBeenCalledTimes(3); + expect(mockMove).toHaveBeenLastCalledWith(new Cartesian3(0, 0, 1), 100); + + result.current.current?.moveDown(100); + expect(mockMove).toHaveBeenCalledTimes(4); + expect(mockMove).toHaveBeenLastCalledWith(new Cartesian3(0, 0, 1), -100); + + result.current.current?.moveRight(100); + expect(mockMove).toHaveBeenCalledTimes(5); + expect(mockMove).toHaveBeenLastCalledWith(new Cartesian3(0, 1, 0), 100); + + result.current.current?.moveLeft(100); + expect(mockMove).toHaveBeenCalledTimes(6); + expect(mockMove).toHaveBeenLastCalledWith(new Cartesian3(0, 1, 0), -100); +}); + +test("look", () => { + const mockLook = vi.fn(e => e); + const { result } = renderHook(() => { + const cesium = useRef>({ + cesiumElement: { + isDestroyed: () => false, + scene: { + camera: { + position: new Cartesian3(0, 0, 1), + direction: Cartesian3.clone(Cartesian3.UNIT_X), + up: Cartesian3.clone(Cartesian3.UNIT_Z), + right: Cartesian3.clone(Cartesian3.UNIT_Y), + look: mockLook, + }, + globe: new Globe(Ellipsoid.UNIT_SPHERE), + }, + } as any, + }); + const engineRef = useRef(null); + useEngineRef(engineRef, cesium); + return engineRef; + }); + + result.current.current?.lookHorizontal(90); + expect(mockLook).toHaveBeenCalledTimes(1); + expect(mockLook).toHaveBeenLastCalledWith(new Cartesian3(0, 0, 1), 90); + + result.current.current?.lookVertical(90); + expect(mockLook).toHaveBeenCalledTimes(2); + expect(mockLook).toHaveBeenLastCalledWith(new Cartesian3(0, 1, 0), 90); +}); diff --git a/src/components/molecules/Visualizer/Engine/Cesium/useEngineRef.ts b/src/components/molecules/Visualizer/Engine/Cesium/useEngineRef.ts index bfc9c9929..b8b423bac 100644 --- a/src/components/molecules/Visualizer/Engine/Cesium/useEngineRef.ts +++ b/src/components/molecules/Visualizer/Engine/Cesium/useEngineRef.ts @@ -8,7 +8,23 @@ import type { MouseEvents, MouseEvent } from "../ref"; import builtinPrimitives from "./builtin"; import Cluster from "./Cluster"; -import { getLocationFromScreenXY, flyTo, lookAt, getCamera, getClock } from "./common"; +import { + getLocationFromScreenXY, + flyTo, + lookAt, + getCamera, + getClock, + lookHorizontal, + lookVertical, + moveForward, + moveBackward, + moveUp, + moveDown, + moveLeft, + moveRight, + moveOverTerrain, + flyToGround, +} from "./common"; export default function useEngineRef( ref: Ref, @@ -120,6 +136,66 @@ export default function useEngineRef( viewer.render(); return viewer.canvas.toDataURL(type, encoderOptions); }, + enableScreenSpaceCameraController: (enabled = true) => { + const viewer = cesium.current?.cesiumElement; + if (!viewer || viewer.isDestroyed() || !viewer.scene) return; + const enable = !!enabled; + viewer.scene.screenSpaceCameraController.enableRotate = enable; + viewer.scene.screenSpaceCameraController.enableTranslate = enable; + viewer.scene.screenSpaceCameraController.enableZoom = enable; + viewer.scene.screenSpaceCameraController.enableTilt = enable; + viewer.scene.screenSpaceCameraController.enableLook = enable; + }, + lookHorizontal: amount => { + const viewer = cesium.current?.cesiumElement; + if (!viewer || viewer.isDestroyed() || !viewer.scene || !amount) return; + lookHorizontal(viewer.scene, amount); + }, + lookVertical: amount => { + const viewer = cesium.current?.cesiumElement; + if (!viewer || viewer.isDestroyed() || !viewer.scene || !amount) return; + lookVertical(viewer.scene, amount); + }, + moveForward: amount => { + const viewer = cesium.current?.cesiumElement; + if (!viewer || viewer.isDestroyed() || !viewer.scene || !amount) return; + moveForward(viewer.scene, amount); + }, + moveBackward: amount => { + const viewer = cesium.current?.cesiumElement; + if (!viewer || viewer.isDestroyed() || !viewer.scene || !amount) return; + moveBackward(viewer.scene, amount); + }, + moveUp: amount => { + const viewer = cesium.current?.cesiumElement; + if (!viewer || viewer.isDestroyed() || !viewer.scene || !amount) return; + moveUp(viewer.scene, amount); + }, + moveDown: amount => { + const viewer = cesium.current?.cesiumElement; + if (!viewer || viewer.isDestroyed() || !viewer.scene || !amount) return; + moveDown(viewer.scene, amount); + }, + moveLeft: amount => { + const viewer = cesium.current?.cesiumElement; + if (!viewer || viewer.isDestroyed() || !viewer.scene || !amount) return; + moveLeft(viewer.scene, amount); + }, + moveRight: amount => { + const viewer = cesium.current?.cesiumElement; + if (!viewer || viewer.isDestroyed() || !viewer.scene || !amount) return; + moveRight(viewer.scene, amount); + }, + moveOverTerrain: async offset => { + const viewer = cesium.current?.cesiumElement; + if (!viewer || viewer.isDestroyed()) return; + moveOverTerrain(viewer, offset); + }, + flyToGround: async (camera, options, offset) => { + const viewer = cesium.current?.cesiumElement; + if (!viewer || viewer.isDestroyed()) return; + flyToGround(viewer, cancelCameraFlight, camera, options, offset); + }, onClick: (cb: ((props: MouseEvent) => void) | undefined) => { mouseEventCallbacks.current.click = cb; }, diff --git a/src/components/molecules/Visualizer/Engine/ref.ts b/src/components/molecules/Visualizer/Engine/ref.ts index 9d2267296..6222e2dc2 100644 --- a/src/components/molecules/Visualizer/Engine/ref.ts +++ b/src/components/molecules/Visualizer/Engine/ref.ts @@ -64,6 +64,17 @@ export type EngineRef = { changeSceneMode: (sceneMode: SceneMode | undefined, duration?: number) => void; getClock: () => Clock | undefined; captureScreen: (type?: string, encoderOptions?: number) => string | undefined; + enableScreenSpaceCameraController: (enabled: boolean) => void; + lookHorizontal: (amount: number) => void; + lookVertical: (amount: number) => void; + moveForward: (amount: number) => void; + moveBackward: (amount: number) => void; + moveUp: (amount: number) => void; + moveDown: (amount: number) => void; + moveLeft: (amount: number) => void; + moveRight: (amount: number) => void; + moveOverTerrain: (offset?: number) => void; + flyToGround: (destination: FlyToDestination, options?: CameraOptions, offset?: number) => void; isMarshalable?: boolean | "json" | ((target: any) => boolean | "json"); builtinPrimitives?: Record; pluginApi?: any; diff --git a/src/components/molecules/Visualizer/Plugin/api.ts b/src/components/molecules/Visualizer/Plugin/api.ts index 6b90a5daf..25524d4c0 100644 --- a/src/components/molecules/Visualizer/Plugin/api.ts +++ b/src/components/molecules/Visualizer/Plugin/api.ts @@ -156,6 +156,17 @@ export function commonReearth({ zoomOut, viewport, captureScreen, + enableScreenSpaceCameraController, + lookHorizontal, + lookVertical, + moveForward, + moveBackward, + moveUp, + moveDown, + moveLeft, + moveRight, + moveOverTerrain, + flyToGround, }: { engineName: string; events: Events; @@ -181,6 +192,17 @@ export function commonReearth({ zoomOut: GlobalThis["reearth"]["visualizer"]["camera"]["zoomOut"]; viewport: () => GlobalThis["reearth"]["visualizer"]["camera"]["viewport"]; captureScreen: GlobalThis["reearth"]["scene"]["captureScreen"]; + enableScreenSpaceCameraController: GlobalThis["reearth"]["camera"]["enableScreenSpaceController"]; + lookHorizontal: GlobalThis["reearth"]["camera"]["lookHorizontal"]; + lookVertical: GlobalThis["reearth"]["camera"]["lookVertical"]; + moveForward: GlobalThis["reearth"]["camera"]["moveForward"]; + moveBackward: GlobalThis["reearth"]["camera"]["moveBackward"]; + moveUp: GlobalThis["reearth"]["camera"]["moveUp"]; + moveDown: GlobalThis["reearth"]["camera"]["moveDown"]; + moveLeft: GlobalThis["reearth"]["camera"]["moveLeft"]; + moveRight: GlobalThis["reearth"]["camera"]["moveRight"]; + moveOverTerrain: GlobalThis["reearth"]["camera"]["moveOverTerrain"]; + flyToGround: GlobalThis["reearth"]["camera"]["flyToGround"]; }): CommonReearth { return { version: window.REEARTH_CONFIG?.version || "", @@ -198,6 +220,17 @@ export function commonReearth({ get viewport() { return viewport(); }, + enableScreenSpaceController: enableScreenSpaceCameraController, + lookHorizontal, + lookVertical, + moveForward, + moveBackward, + moveUp, + moveDown, + moveLeft, + moveRight, + moveOverTerrain, + flyToGround, }, get property() { return sceneProperty(); @@ -226,6 +259,17 @@ export function commonReearth({ get viewport() { return viewport(); }, + enableScreenSpaceController: enableScreenSpaceCameraController, + lookHorizontal, + lookVertical, + moveForward, + moveBackward, + moveUp, + moveDown, + moveLeft, + moveRight, + moveOverTerrain, + flyToGround, }, layers: { get layersInViewport() { diff --git a/src/components/molecules/Visualizer/Plugin/context.tsx b/src/components/molecules/Visualizer/Plugin/context.tsx index 84161451b..d555deb0f 100644 --- a/src/components/molecules/Visualizer/Plugin/context.tsx +++ b/src/components/molecules/Visualizer/Plugin/context.tsx @@ -19,6 +19,7 @@ import type { CommonReearth } from "./api"; import { commonReearth } from "./api"; import type { CameraPosition, + CameraOptions, Layer, OverriddenInfobox, ReearthEventType, @@ -61,6 +62,17 @@ export type Props = { viewport: () => Rect | undefined; onMouseEvent: (type: keyof MouseEventHandles, fn: any) => void; captureScreen: (type?: string, encoderOptions?: number) => string | undefined; + enableScreenSpaceCameraController: (enabled: boolean) => void; + lookHorizontal: (amount: number) => void; + lookVertical: (amount: number) => void; + moveForward: (amount: number) => void; + moveBackward: (amount: number) => void; + moveUp: (amount: number) => void; + moveDown: (amount: number) => void; + moveLeft: (amount: number) => void; + moveRight: (amount: number) => void; + moveOverTerrain: () => void; + flyToGround: (destination: FlyToDestination, options?: CameraOptions, offset?: number) => void; }; export type Context = { @@ -104,6 +116,17 @@ export function Provider({ viewport, captureScreen, onMouseEvent, + enableScreenSpaceCameraController, + lookHorizontal, + lookVertical, + moveForward, + moveBackward, + moveUp, + moveDown, + moveLeft, + moveRight, + moveOverTerrain, + flyToGround, children, }: Props): JSX.Element { const [ev, emit] = useMemo( @@ -159,6 +182,17 @@ export function Provider({ zoomOut, viewport, captureScreen, + enableScreenSpaceCameraController, + lookHorizontal, + lookVertical, + moveForward, + moveBackward, + moveUp, + moveDown, + moveLeft, + moveRight, + moveOverTerrain, + flyToGround, }), overrideSceneProperty, }), @@ -190,6 +224,17 @@ export function Provider({ zoomOut, viewport, captureScreen, + enableScreenSpaceCameraController, + lookHorizontal, + lookVertical, + moveForward, + moveBackward, + moveUp, + moveDown, + moveLeft, + moveRight, + moveOverTerrain, + flyToGround, ], ); diff --git a/src/components/molecules/Visualizer/Plugin/types.ts b/src/components/molecules/Visualizer/Plugin/types.ts index 2a9c958a0..f9a0b1e2b 100644 --- a/src/components/molecules/Visualizer/Plugin/types.ts +++ b/src/components/molecules/Visualizer/Plugin/types.ts @@ -281,6 +281,21 @@ export type Camera = { readonly flyTo: (destination: FlyToDestination, options?: CameraOptions) => void; /** Moves the camera position to look at the specified destination. */ readonly lookAt: (destination: LookAtDestination, options?: CameraOptions) => void; + readonly enableScreenSpaceController: (enabled: boolean) => void; + readonly lookHorizontal: (amount: number) => void; + readonly lookVertical: (amount: number) => void; + readonly moveForward: (amount: number) => void; + readonly moveBackward: (amount: number) => void; + readonly moveUp: (amount: number) => void; + readonly moveDown: (amount: number) => void; + readonly moveLeft: (amount: number) => void; + readonly moveRight: (amount: number) => void; + readonly moveOverTerrain: (offset?: number) => void; + readonly flyToGround: ( + destination: FlyToDestination, + options?: CameraOptions, + offset?: number, + ) => void; }; /** Represents the camera position and state */ diff --git a/src/components/molecules/Visualizer/hooks.ts b/src/components/molecules/Visualizer/hooks.ts index fd19078e7..b43ff8119 100644 --- a/src/components/molecules/Visualizer/hooks.ts +++ b/src/components/molecules/Visualizer/hooks.ts @@ -432,6 +432,17 @@ function useProviderProps( | "viewport" | "onMouseEvent" | "captureScreen" + | "enableScreenSpaceCameraController" + | "lookHorizontal" + | "lookVertical" + | "moveForward" + | "moveBackward" + | "moveUp" + | "moveDown" + | "moveLeft" + | "moveRight" + | "moveOverTerrain" + | "flyToGround" >, engineRef: RefObject, layers: LayerStore, @@ -526,6 +537,81 @@ function useProviderProps( [engineRef], ); + const enableScreenSpaceCameraController = useCallback( + (enabled: boolean) => engineRef?.current?.enableScreenSpaceCameraController(enabled), + [engineRef], + ); + + const lookHorizontal = useCallback( + (amount: number) => { + engineRef.current?.lookHorizontal(amount); + }, + [engineRef], + ); + + const lookVertical = useCallback( + (amount: number) => { + engineRef.current?.lookVertical(amount); + }, + [engineRef], + ); + + const moveForward = useCallback( + (amount: number) => { + engineRef.current?.moveForward(amount); + }, + [engineRef], + ); + + const moveBackward = useCallback( + (amount: number) => { + engineRef.current?.moveBackward(amount); + }, + [engineRef], + ); + + const moveUp = useCallback( + (amount: number) => { + engineRef.current?.moveUp(amount); + }, + [engineRef], + ); + + const moveDown = useCallback( + (amount: number) => { + engineRef.current?.moveDown(amount); + }, + [engineRef], + ); + + const moveLeft = useCallback( + (amount: number) => { + engineRef.current?.moveLeft(amount); + }, + [engineRef], + ); + + const moveRight = useCallback( + (amount: number) => { + engineRef.current?.moveRight(amount); + }, + [engineRef], + ); + + const moveOverTerrain = useCallback( + (offset?: number) => { + return engineRef.current?.moveOverTerrain(offset); + }, + [engineRef], + ); + + const flyToGround = useCallback( + (dest: FlyToDestination, options?: CameraOptions, offset?: number) => { + engineRef.current?.flyToGround(dest, options, offset); + }, + [engineRef], + ); + return { ...props, engine, @@ -538,5 +624,16 @@ function useProviderProps( viewport, onMouseEvent, captureScreen, + enableScreenSpaceCameraController, + lookHorizontal, + lookVertical, + moveForward, + moveBackward, + moveUp, + moveDown, + moveLeft, + moveRight, + moveOverTerrain, + flyToGround, }; } diff --git a/src/components/molecules/Visualizer/storybook.tsx b/src/components/molecules/Visualizer/storybook.tsx index 90e2f2215..b0fd19cdc 100644 --- a/src/components/molecules/Visualizer/storybook.tsx +++ b/src/components/molecules/Visualizer/storybook.tsx @@ -93,6 +93,17 @@ export const context: ProviderProps = { viewport: act("viewport"), onMouseEvent: act("onMouseEvent"), captureScreen: act("captureScreen"), + enableScreenSpaceCameraController: act("enableScreenSpaceCameraController"), + lookHorizontal: act("lookHorizontal"), + lookVertical: act("lookVertical"), + moveForward: act("moveForward"), + moveBackward: act("moveBackward"), + moveUp: act("moveUp"), + moveDown: act("moveDown"), + moveLeft: act("moveLeft"), + moveRight: act("moveRight"), + moveOverTerrain: act("moveOverTerrain"), + flyToGround: act("flyToGround"), }; function act any>(