I’m building a native iOS webxr experience, where we load a scene and a user is able to place the scene on the floor. Everything works in Chrome on Android ![]()
Now we’re porting it to iOS, so we can show the same experience in an app clip. It seems their could be a bug when calling addAnchorPointUsingHitTestResultAsync, in rare occasions it does work but often we get an exception in:
Anchor System::Session::Frame::CreateAnchor(Pose pose, NativeTrackablePtr) const {
return m_impl->sessionImpl.CreateAnchor(pose);
}
The exception:
sessionImpl -> com.facebook.react.JavaScript (15): EXC_BAD_ACCESS (code=1, address=0x0)
App.tsx:
/**
* 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, Alert } from 'react-native';
import { EngineView, useEngine, EngineViewCallbacks } from '@babylonjs/react-native';
import { Scene, Vector3, ArcRotateCamera, Camera, WebXRSessionManager, SceneLoader, TransformNode, DeviceSourceManager, DeviceType, PointerInput, WebXRTrackingState, IMouseEvent, WebXRFeatureName, IWebXRHitResult, CreateTorus, Quaternion, WebXRHitTest, FreeCamera, WebXRAnchorSystem, IPointerEvent, PickingInfo, WebXRState, DirectionalLight, ShadowGenerator } 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);
console.log('Hallo');
setScene(scene);
// scene.createDefaultCamera(true);
// (scene.activeCamera as ArcRotateCamera).beta -= Math.PI / 8;
var camera = new FreeCamera('webxr-camera', new Vector3(0, 1, -5), scene);
camera.setTarget(Vector3.Zero());
camera.minZ = 0.01;
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 -= .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 startXR = () => {
(async () => {
console.log('Start XR');
if (!scene || !rootNode) return;
rootNode.getChildMeshes().forEach(m => m.isVisible = false);
rootNode.rotationQuaternion = new Quaternion();
const xr = await scene.createDefaultXRExperienceAsync({ disableDefaultUI: true, disableTeleportation: true });
console.log('XR')
const fm = xr.baseExperience.featuresManager;
const xrTest = fm.enableFeature(WebXRFeatureName.HIT_TEST, 'latest')as WebXRHitTest;
const xrPlanes = fm.enableFeature(WebXRFeatureName.PLANE_DETECTION, 'latest');
const anchors = fm.enableFeature(WebXRFeatureName.ANCHOR_SYSTEM, 'latest') as WebXRAnchorSystem;
const xrBackgroundRemover = fm.enableFeature(WebXRFeatureName.BACKGROUND_REMOVER, 'latest');
var isPlaced = false;
var marker = CreateTorus(
'marker',
{
diameter: 0.15,
thickness: 0.05,
},
scene
);
marker.rotationQuaternion = new Quaternion();
marker.isVisible = false;
var hitTest: IWebXRHitResult;
xrTest.onHitTestResultObservable.add((result) => {
if (result.length) {
marker.isVisible = !isPlaced;
// marker.isVisible = true;
hitTest = result[0];
marker.position = hitTest.position;
marker.rotationQuaternion = hitTest.rotationQuaternion;
rootNode.position = hitTest.position;
rootNode.rotationQuaternion = hitTest.rotationQuaternion;
} else {
marker.isVisible = false;
}
});
if (anchors) {
anchors.onAnchorAddedObservable.add((anchor) => {
console.log('Anchor added');
rootNode.getChildMeshes().forEach((m) => (m.isVisible = true));
// model.isVisible = true;
// model.isEnabled(true);
anchor.attachedNode = rootNode;
// shadowGenerator.addShadowCaster(model.attached)
});
}
scene.onPointerDown = (evt: IPointerEvent, pickInfo: PickingInfo) => {
if (hitTest && anchors && xr.baseExperience.state === WebXRState.IN_XR) {
console.log('hittest', hitTest);
console.log('session.inXRSession', session.inXRSession);
anchors.addAnchorPointUsingHitTestResultAsync(hitTest);
marker.isVisible = false;
isPlaced = true;
}
};
const session = await xr.baseExperience.enterXRAsync('immersive-ar', 'unbounded', xr.renderTarget);
session.onXRSessionInit.addOnce(() => {
console.log('Session Init');
})
})();
}
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="Start" onPress={ () => startXR() } />
<EngineView camera={camera} onInitialized={onInitialized} displayFrameRate={true} antiAliasing={2} />
</View>
</>
);
};
const App = () => {
const [toggleScreen, setToggleScreen] = useState(false);
return (
<>
<StatusBar barStyle="dark-content" />
<SafeAreaView style={{ flex: 1, backgroundColor: 'white' }}>
<EngineScreen style={{ flex: 1 }} />
</SafeAreaView>
</>
);
};
export default App;
I mentioned it in a discussion about a problem I had starting XR on ios, I don’t think these are related. The XR issue is solved ![]()


