diff --git a/Apps/BRNPlayground/.gitignore b/Apps/BRNPlayground/.gitignore new file mode 100644 index 000000000..5fbd50620 --- /dev/null +++ b/Apps/BRNPlayground/.gitignore @@ -0,0 +1,14 @@ +*.binlog +*.hprof +*.zip +.DS_Store +.gradle/ +.idea/ +.vs/ +.xcode.env +Pods/ +build/ +dist/ +local.properties +msbuild.binlog +node_modules/ diff --git a/Apps/BRNPlayground/.watchmanconfig b/Apps/BRNPlayground/.watchmanconfig new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/Apps/BRNPlayground/.watchmanconfig @@ -0,0 +1 @@ +{} diff --git a/Apps/BRNPlayground/App.tsx b/Apps/BRNPlayground/App.tsx new file mode 100644 index 000000000..bbd4798b0 --- /dev/null +++ b/Apps/BRNPlayground/App.tsx @@ -0,0 +1,285 @@ +/** + * Generated with the TypeScript template + * https://github.com/react-native-community/react-native-template-typescript + * + * @format + */ + +import React, { + useState, + FunctionComponent, + useEffect, + useCallback, +} from "react"; +import { + SafeAreaView, + StatusBar, + Button, + View, + Text, + ViewProps, + Image, +} from "react-native"; + +import { + EngineView, + useEngine, + EngineViewCallbacks, +} from "@babylonjs/react-native"; +import { + Scene, + Vector3, + ArcRotateCamera, + Camera, + WebXRSessionManager, + SceneLoader, + TransformNode, + DeviceSourceManager, + DeviceType, + PointerInput, + WebXRTrackingState, + IMouseEvent, +} from "@babylonjs/core"; +import "@babylonjs/loaders"; +import Slider from "@react-native-community/slider"; + +const EngineScreen: FunctionComponent = (props: ViewProps) => { + const defaultScale = 1; + const enableSnapshots = false; + + const engine = useEngine(); + const [toggleView, setToggleView] = useState(false); + const [camera, setCamera] = useState(); + const [rootNode, setRootNode] = useState(); + const [scene, setScene] = useState(); + const [xrSession, setXrSession] = useState(); + const [scale, setScale] = useState(defaultScale); + const [snapshotData, setSnapshotData] = useState(); + const [engineViewCallbacks, setEngineViewCallbacks] = + useState(); + const [trackingState, setTrackingState] = useState(); + + useEffect(() => { + if (engine) { + const scene = new Scene(engine); + setScene(scene); + scene.createDefaultCamera(true); + (scene.activeCamera as ArcRotateCamera).beta -= Math.PI / 8; + setCamera(scene.activeCamera!); + scene.createDefaultLight(true); + const rootNode = new TransformNode("Root Container", scene); + setRootNode(rootNode); + + const deviceSourceManager = new DeviceSourceManager(engine); + const handlePointerInput = (event: IMouseEvent) => { + if (event.inputIndex === PointerInput.Move && event.movementX) { + rootNode.rotate(Vector3.Down(), event.movementX * 0.005); + } + }; + + deviceSourceManager.onDeviceConnectedObservable.add((device) => { + if (device.deviceType === DeviceType.Touch) { + const touch = deviceSourceManager.getDeviceSource( + device.deviceType, + device.deviceSlot + )!; + touch.onInputChangedObservable.add((touchEvent) => { + handlePointerInput(touchEvent); + }); + } else if (device.deviceType === DeviceType.Mouse) { + const mouse = deviceSourceManager.getDeviceSource( + device.deviceType, + device.deviceSlot + )!; + mouse.onInputChangedObservable.add((mouseEvent) => { + if (mouse.getInput(PointerInput.LeftClick)) { + handlePointerInput(mouseEvent); + } + }); + } + }); + + const transformContainer = new TransformNode( + "Transform Container", + scene + ); + transformContainer.parent = rootNode; + transformContainer.scaling.scaleInPlace(0.2); + transformContainer.position.y -= 0.2; + + scene.beforeRender = function () { + transformContainer.rotate( + Vector3.Up(), + 0.005 * scene.getAnimationRatio() + ); + }; + + SceneLoader.ImportMeshAsync( + "", + "https://raw.githubusercontent.com/KhronosGroup/glTF-Sample-Models/master/2.0/BoxAnimated/glTF-Binary/BoxAnimated.glb" + ).then((result) => { + const mesh = result.meshes[0]; + mesh.parent = transformContainer; + }); + } + }, [engine]); + + useEffect(() => { + if (rootNode) { + rootNode.scaling = new Vector3(scale, scale, scale); + } + }, [rootNode, scale]); + + const trackingStateToString = ( + trackingState: WebXRTrackingState | undefined + ): string => { + return trackingState === undefined ? "" : WebXRTrackingState[trackingState]; + }; + + const onToggleXr = useCallback(() => { + (async () => { + if (xrSession) { + await xrSession.exitXRAsync(); + } else { + if (rootNode !== undefined && scene !== undefined) { + const xr = await scene.createDefaultXRExperienceAsync({ + disableDefaultUI: true, + disableTeleportation: true, + }); + const session = await xr.baseExperience.enterXRAsync( + "immersive-ar", + "unbounded", + xr.renderTarget + ); + setXrSession(session); + session.onXRSessionEnded.add(() => { + setXrSession(undefined); + setTrackingState(undefined); + }); + + setTrackingState(xr.baseExperience.camera.trackingState); + xr.baseExperience.camera.onTrackingStateChanged.add( + (newTrackingState) => { + setTrackingState(newTrackingState); + } + ); + + // TODO: Figure out why getFrontPosition stopped working + //box.position = (scene.activeCamera as TargetCamera).getFrontPosition(2); + const cameraRay = scene.activeCamera!.getForwardRay(1); + rootNode.position = cameraRay.origin.add( + cameraRay.direction.scale(cameraRay.length) + ); + rootNode.rotate(Vector3.Up(), 3.14159); + } + } + })(); + }, [rootNode, scene, xrSession]); + + const onInitialized = useCallback( + async (engineViewCallbacks: EngineViewCallbacks) => { + setEngineViewCallbacks(engineViewCallbacks); + }, + [engine] + ); + + const onSnapshot = useCallback(async () => { + if (engineViewCallbacks) { + setSnapshotData( + "data:image/jpeg;base64," + (await engineViewCallbacks.takeSnapshot()) + ); + } + }, [engineViewCallbacks]); + + return ( + <> + +