Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(test app): add new RNTA-based test app #641

Merged
merged 11 commits into from
May 23, 2024
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
14 changes: 14 additions & 0 deletions Apps/BRNPlayground/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
*.binlog
*.hprof
*.zip
.DS_Store
.gradle/
.idea/
.vs/
.xcode.env
Pods/
build/
dist/
local.properties
msbuild.binlog
node_modules/
1 change: 1 addition & 0 deletions Apps/BRNPlayground/.watchmanconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
285 changes: 285 additions & 0 deletions Apps/BRNPlayground/App.tsx
Original file line number Diff line number Diff line change
@@ -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<ViewProps> = (props: ViewProps) => {
const defaultScale = 1;
const enableSnapshots = false;

const engine = useEngine();
const [toggleView, setToggleView] = useState(false);
const [camera, setCamera] = useState<Camera>();
const [rootNode, setRootNode] = useState<TransformNode>();
const [scene, setScene] = useState<Scene>();
const [xrSession, setXrSession] = useState<WebXRSessionManager>();
const [scale, setScale] = useState<number>(defaultScale);
const [snapshotData, setSnapshotData] = useState<string>();
const [engineViewCallbacks, setEngineViewCallbacks] =
useState<EngineViewCallbacks>();
const [trackingState, setTrackingState] = useState<WebXRTrackingState>();

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 (
<>
<View style={props.style}>
<Button
title="Toggle EngineView"
onPress={() => {
setToggleView(!toggleView);
}}
/>
<Button
title={xrSession ? "Stop XR" : "Start XR"}
onPress={onToggleXr}
/>
{!toggleView && (
<View style={{ flex: 1 }}>
{enableSnapshots && (
<View style={{ flex: 1 }}>
<Button title={"Take Snapshot"} onPress={onSnapshot} />
<Image style={{ flex: 1 }} source={{ uri: snapshotData }} />
</View>
)}
<EngineView
camera={camera}
onInitialized={onInitialized}
displayFrameRate={true}
antiAliasing={2}
/>
<Slider
style={{
position: "absolute",
minHeight: 50,
margin: 10,
left: 0,
right: 0,
bottom: 0,
}}
minimumValue={0.2}
maximumValue={2}
step={0.01}
value={defaultScale}
onValueChange={setScale}
/>
<Text style={{ color: "yellow", position: "absolute", margin: 3 }}>
{trackingStateToString(trackingState)}
</Text>
</View>
)}
{toggleView && (
<View
style={{ flex: 1, justifyContent: "center", alignItems: "center" }}
>
<Text style={{ fontSize: 24 }}>EngineView has been removed.</Text>
<Text style={{ fontSize: 12 }}>
Render loop stopped, but engine is still alive.
</Text>
</View>
)}
</View>
</>
);
};

const App = () => {
const [toggleScreen, setToggleScreen] = useState(false);

return (
<>
<StatusBar barStyle="dark-content" />
<SafeAreaView style={{ flex: 1, backgroundColor: "white" }}>
{!toggleScreen && <EngineScreen style={{ flex: 1 }} />}
{toggleScreen && (
<View
style={{ flex: 1, justifyContent: "center", alignItems: "center" }}
>
<Text style={{ fontSize: 24 }}>EngineScreen has been removed.</Text>
<Text style={{ fontSize: 12 }}>
Engine has been disposed, and will be recreated.
</Text>
</View>
)}
<Button
title="Toggle EngineScreen"
onPress={() => {
setToggleScreen(!toggleScreen);
}}
/>
</SafeAreaView>
</>
);
};

export default App;
12 changes: 12 additions & 0 deletions Apps/BRNPlayground/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# BabylonReactNative Playground App

This is a new example app for BabylonReactNative, based on `react-native-test-app`.
CedricGuillemet marked this conversation as resolved.
Show resolved Hide resolved

It's still getting fully wired up as a replacement, so if you are seeing this message it means that it's not in its final form yet!

To run it (on an Android device):

1. `npm i`
2. `npm run android`

(assuming that you have your setup working for running [RN Android apps on device](https://reactnative.dev/docs/running-on-device?platform=android))
26 changes: 26 additions & 0 deletions Apps/BRNPlayground/android/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
buildscript {
def androidTestAppDir = "../node_modules/react-native-test-app/android"
apply(from: "${androidTestAppDir}/dependencies.gradle")

repositories {
mavenCentral()
google()
}

dependencies {
getReactNativeDependencies().each { dependency ->
classpath(dependency)
}
}
}

allprojects {
repositories {
maven {
// All of React Native (JS, Obj-C sources, Android binaries) is installed from npm
url("${rootDir}/../node_modules/react-native/android")
}
mavenCentral()
google()
}
}
49 changes: 49 additions & 0 deletions Apps/BRNPlayground/android/gradle.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# Project-wide Gradle settings.

# IDE (e.g. Android Studio) users:
# Gradle settings configured through the IDE *will override*
# any settings specified in this file.

# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html

# Specifies the JVM arguments used for the Gradle Daemon. The setting is
# particularly useful for configuring JVM memory settings for build performance.
# This does not affect the JVM settings for the Gradle client VM.
# The default is `-Xmx512m -XX:MaxMetaspaceSize=256m`.
org.gradle.jvmargs=-Xmx2g -XX:MaxMetaspaceSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8

# When configured, Gradle will fork up to org.gradle.workers.max JVMs to execute
# projects in parallel. To learn more about parallel task execution, see the
# section on Gradle build performance:
# https://docs.gradle.org/current/userguide/performance.html#parallel_execution.
# Default is `false`.
#org.gradle.parallel=true

# AndroidX package structure to make it clearer which packages are bundled with the
# Android operating system, and which are packaged with your app's APK
# https://developer.android.com/topic/libraries/support-library/androidx-rn
android.useAndroidX=true
# Automatically convert third-party libraries to use AndroidX
android.enableJetifier=true
# Jetifier randomly fails on these libraries
android.jetifier.ignorelist=hermes-android

# Use this property to specify which architecture you want to build.
# You can also override it from the CLI using
# ./gradlew <task> -PreactNativeArchitectures=x86_64
reactNativeArchitectures=armeabi-v7a,arm64-v8a,x86,x86_64

# Use this property to enable support to the new architecture.
# This will allow you to use TurboModules and the Fabric render in
# your application. You should enable this flag either if you want
# to write custom TurboModules/Fabric components OR use libraries that
# are providing them.
# Note that this is incompatible with web debugging.
#newArchEnabled=true

# Uncomment the line below if building react-native from source
#ANDROID_NDK_VERSION=26.1.10909125

# Version of Kotlin to build against.
#KOTLIN_VERSION=1.8.22
Binary file not shown.
Loading
Loading