Surface detection is possible in babylonjs package

Hi Team
I resolved mesh builder issues.
I am able to detect the surface of the ground and was unable to detect the wall

here is my code:

import React, { useCallback, useEffect, useState } from “react”;
import { View, ViewProps, Button, Image, Text } from “react-native”;

import {
EngineView,
EngineViewCallbacks,
useEngine,
} from “@babylonjs/react-native”;

import {
AbstractMesh,
ArcRotateCamera,
Camera,
Color3,
Color4,
DeviceSource,
DeviceSourceManager,
DeviceType,
HemisphericLight,
Mesh,
MeshBuilder,
Nullable,
PointerInput,
Quaternion,
Scene,
SceneLoader,
StandardMaterial,
Texture,
TransformNode,
TubeBuilder,
CreateTube,
Vector3,
WebXRFeatureName,
WebXRHitTest,
WebXRPlaneDetector,
WebXRSessionManager,
WebXRTrackingState,
} from “@babylonjs/core”;
import “@babylonjs/loaders”;
import { StyleProp, StyleSheet, ViewStyle } from “react-native”;
// import { MathJaxSvg } from “react-native-mathjax-html-to-svg”;
import Slider from “@react-native-community/slider”;
import earcut from “earcut”

//import { Slider, Text, Button, Image } from “react-native-elements”;
import { SafeAreaView } from “react-native-safe-area-context”;

const EngineScreen = (props:ViewProps) => {
const defaultScale = 1;
const enableSnapshots = false;

const engine = useEngine();
const [toggleView, setToggleView] = useState(false);
const [camera, setCamera] = useState<ArcRotateCamera | undefined>();
const [rootNode, setRootNode] = useState<TransformNode | undefined>();
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();

const [placementIndicator, setPlacementIndicator] =
useState<AbstractMesh | undefined>();
const [model, setModel] = useState();
const [modelPlaced, setModelPlaced] = useState(false);
const [planeMat, setPlaneMat] = useState();
const [deviceSourceManager, setDeviceSourceManager] =
useState();

useEffect(() => {
async function init() {
if (engine) {
const scene = new Scene(engine);
setScene(scene);
scene.createDefaultCamera(true);
(scene.activeCamera as ArcRotateCamera).beta -= Math.PI / 8;
setCamera(scene.activeCamera! as ArcRotateCamera);
//scene.createDefaultLight(true);
//scene.clearColor = new Color4(0, 0, 0, 0);
const light = new HemisphericLight(
“light1”,
new Vector3(0, 5, 0),
scene
);
light.diffuse = Color3.White();
light.intensity = 1;
light.specular = new Color3(0, 0, 0);

    // Create the placement indicator
    let placementIndicator = MeshBuilder.CreateTorus(
      "placementIndicator",
      {
        diameter:  0.5,
        thickness:  0.005,
        tessellation: 64
      }
    );
    let indicatorMat = new StandardMaterial("noLight", scene);
    indicatorMat.disableLighting = true;
    indicatorMat.emissiveColor = Color3.White();
    placementIndicator.material = indicatorMat;
    placementIndicator.scaling = new Vector3(1, 0.01, 1);
    placementIndicator.setEnabled(false);
    setPlacementIndicator(placementIndicator);

    const rootNode = new TransformNode("Root Container", scene);
    setRootNode(rootNode);

    const transformContainer = new TransformNode(
      "Transform Container",
      scene
    );
    transformContainer.parent = rootNode;
    transformContainer.scaling.scaleInPlace(0.2);
    transformContainer.position.y -= 0.2;

    // import model
    SceneLoader.ImportMeshAsync(
      "",
      "https://raw.githubusercontent.com/KhronosGroup/glTF-Sample-Models/master/2.0/BoxAnimated/glTF-Binary/BoxAnimated.glb"
      //"https://raw.githubusercontent.com/KhronosGroup/glTF-Sample-Models/master/2.0/BrainStem/glTF/BrainStem.gltf"
    ).then((result) => {
      const mesh = result.meshes[0];

      mesh.position.y = 1;
      // if(camera)
      // mesh.lookAt(camera?.position);

     // mesh.scalingDeterminant = 0;
      mesh.parent = transformContainer;
      setModel(mesh);
      // camera?.setTarget(mesh);
    });

    const planeTexture = new Texture(
      "https://i.imgur.com/z7s3C5B.png",
      scene
    );
    planeTexture.hasAlpha = true;
    planeTexture.uScale = 3;
    planeTexture.vScale = 3;
    planeTexture.coordinatesMode = Texture.PROJECTION_MODE;

    const planeMat = new StandardMaterial("noLight", scene);
    planeMat.diffuseTexture = planeTexture;
    setPlaneMat(planeMat);

    const deviceSourceManager = new DeviceSourceManager(engine);
    setDeviceSourceManager(deviceSourceManager);
  }
}
init();

}, [engine]);

////////////////
const resetClick = () => {
if (model && camera && scene && placementIndicator) {
if (xrSession) {
setModelPlaced(false);
model.setEnabled(false);
placementIndicator.setEnabled(true);
} else {
placementIndicator.setEnabled(false);
// reset2D()
}
}
};

useEffect(() => {
if (rootNode) {
rootNode.scaling = new Vector3(scale, scale, scale);
}
}, [rootNode, scale]);

useEffect(() => {
createInputHandling();
}, [xrSession]);

const createInputHandling = useCallback(() => {
async function inputListen() {
// var numInputs = 0;
deviceSourceManager?.onDeviceConnectedObservable.add((device) => {
// numInputs++;

    if (device.deviceType === DeviceType.Touch) {
      const touch: DeviceSource<DeviceType.Touch> =
        deviceSourceManager.getDeviceSource(
          device.deviceType,
          device.deviceSlot
        )!;
      touch.onInputChangedObservable.add((touchEvent) => {
        if (
          model &&
          xrSession &&
          placementIndicator?.isEnabled() &&
          !modelPlaced
        ) {
          placeModel();

          //placementIndicator?.setEnabled(false);
          console.log("model placing input");
          //numInputs--;
        }

        if (
          model &&
          modelPlaced &&
          xrSession &&
          placementIndicator?.isEnabled()
        ) {
          console.log("update input",touchEvent);
          // Calculate the differential between two states.
          // const diff = touchEvent.previousState - touchEvent.currentState;
          // // Single input, do translation.
          // //if (numInputs === 1) {
          // if (touchEvent.inputIndex === PointerInput.Horizontal) {
          //   model.position.x -= diff / 1000;
          // } else {
          //   model.position.z += diff / 750;
          // }
          // }
          // Multi-input do rotation.
          //   if (
          //     numInputs === 2 &&
          //     touchEvent.inputIndex === PointerInput.Horizontal &&
          //     touchEvent.deviceSlot === 0
          //   ) {
          //     console.log("multi touch");
          //     model.rotate(Vector3.Up(), diff / 200);
          //   }
        }
      });
    } else if (device.deviceType === DeviceType.Mouse) {
      const mouse: DeviceSource<DeviceType.Mouse> =
        deviceSourceManager.getDeviceSource(
          device.deviceType,
          device.deviceSlot
        )!;
      mouse.onInputChangedObservable.add((mouseEvent) => {
        if (mouse.getInput(PointerInput.LeftClick)) {
          return;
        }
      });
    }
  });
}
inputListen();

}, [xrSession, placementIndicator, modelPlaced]);

const reset2D = () => {
if (model && scene && camera) {
model.setEnabled(true);
model.position = camera.position.add(
camera.getForwardRay().direction.scale(scale * 1)
);
placementIndicator?.setEnabled(false);
setModelPlaced(false);
//model.scalingDeterminant = 0;
//camera.setTarget(model)
}
};

const placeModel = () => {
console.log(“placeModel”);
if (
xrSession &&
placementIndicator?.isEnabled() &&
scene &&
model &&
!modelPlaced
) {
setModelPlaced(true);
model.rotationQuaternion = Quaternion.Identity();

  model.position = placementIndicator.position.clone();
  placementIndicator?.setEnabled(false);
  model.setEnabled(true);

  //model.scalingDeterminant = 0;
}

};

const trackingStateToString = (
trackingState: WebXRTrackingState | undefined
): string => {
return trackingState === undefined ? “” : WebXRTrackingState[trackingState];
};

const onToggleXr = useCallback(() => {
(async () => {
if (xrSession) {
reset2D();
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
);

      setModelPlaced(false);
      model?.setEnabled(false);

      setXrSession(session);
      session.onXRSessionEnded.add(() => {
        setXrSession(undefined);
        setTrackingState(undefined);
      });

      setTrackingState(xr.baseExperience.camera.trackingState);
      xr.baseExperience.camera.onTrackingStateChanged.add(
        (newTrackingState) => {
          setTrackingState(newTrackingState);
        }
      );
      // Set up the hit test.
      const xrHitTestModule =
        xr.baseExperience.featuresManager.enableFeature(
          WebXRFeatureName.HIT_TEST,
          "latest",
          {
            offsetRay: {
              origin: { x: 0, y: 0, z: 0 },
              direction: { x: 0, y: 0, z: -1 },
            },
          }
        ) as WebXRHitTest;

        
      // Do some plane shtuff.
      const xrPlanes = xr.baseExperience.featuresManager.enableFeature(
        WebXRFeatureName.PLANE_DETECTION,
        "latest"
      ) as WebXRPlaneDetector;
      console.log("Enabled plane detection.");
      const planes: any[] = [];

      xrPlanes.onPlaneAddedObservable.add((webXRPlane) => {
        if (scene) {
          console.log("Plane added.");
          let plane: any = webXRPlane;
          webXRPlane.polygonDefinition.push(
            webXRPlane.polygonDefinition[0]
          );
          try {
            plane.mesh = MeshBuilder.CreatePolygon(
              "plane",
              { shape: plane.polygonDefinition },
              scene,
              earcut
            );
            let tubeMesh: Mesh = CreateTube(
              "tube",
              {
                path: plane.polygonDefinition,
                radius: 0.005,
                sideOrientation: Mesh.FRONTSIDE,
                updatable: true,
              },
              scene
            );
            tubeMesh.setParent(plane.mesh);
            planes[plane.id] = plane.mesh;
            plane.mesh.material = planeMat;

            plane.mesh.rotationQuaternion = new Quaternion();
            plane.transformationMatrix.decompose(
              plane.mesh.scaling,
              plane.mesh.rotationQuaternion,
              plane.mesh.position
            );
          } catch (ex) {
            console.error(ex);
          }
        }
      });

      xrPlanes.onPlaneUpdatedObservable.add((webXRPlane) => {
        console.log("Plane updated.");
        let plane: any = webXRPlane;
        if (plane.mesh) {
          plane.mesh.dispose(false, false);
        }

        const some = plane.polygonDefinition.some((p: any) => !p);
        if (some) {
          return;
        }

        plane.polygonDefinition.push(plane.polygonDefinition[0]);
        try {
          plane.mesh = MeshBuilder.CreatePolygon(
            "plane",
            { shape: plane.polygonDefinition },
            scene,
            earcut
          );
          let tubeMesh: Mesh = CreateTube(
            "tube",
            {
              path: plane.polygonDefinition,
              radius: 0.005,
              sideOrientation: Mesh.FRONTSIDE,
              updatable: true,
            },
            scene
          );
          tubeMesh.setParent(plane.mesh);
          planes[plane.id] = plane.mesh;
          plane.mesh.material = planeMat;
          plane.mesh.rotationQuaternion = new Quaternion();
          plane.transformationMatrix.decompose(
            plane.mesh.scaling,
            plane.mesh.rotationQuaternion,
            plane.mesh.position
          );
          plane.mesh.receiveShadows = true;
        } catch (ex) {
          console.error(ex);
        }
      });

      xrPlanes.onPlaneRemovedObservable.add((webXRPlane) => {
        console.log("Plane removed.");
        let plane: any = webXRPlane;
        if (plane && planes[plane.id]) {
          planes[plane.id].dispose();
        }
      });

      xrHitTestModule.onHitTestResultObservable.add((results) => {
        // if (results.length) {
        //   if (!modelPlaced) {
        //     placementIndicator?.setEnabled(true);
        //   } else {
        //     placementIndicator?.setEnabled(false);
        //   }

        //   if (placementIndicator) {
        //     placementIndicator.position = results[0].position;
        //   }
        // }
      });
    }
  }
})();

}, [
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 (
<>

<Button
title=“Toggle EngineView”
onPress={() => {
setToggleView(!toggleView);
}}
/>
<Button
title={xrSession ? “Stop XR” : “Start XR”}
onPress={() => {
//desabilitar o model assim que inicia a xrsession, no usecallback não funciona
if (!xrSession) {
model?.setEnabled(false);
}
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
          style={props.style}
          camera={camera}
          onInitialized={onInitialized}
        />
        <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={{
            fontSize: 12,
            color: "yellow",
            position: "absolute",
            margin: 10,
          }}
        >
          {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 BabylonXrTest = () => {
const [toggleScreen, setToggleScreen] = useState(false);
//const { topic } = route.params;

return (
<>
<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 style={{ fontSize: 12 }}>
Engine has been disposed, and will be recreated.


)}
<Button
title=“Toggle EngineScreen”
onPress={() => {
setToggleScreen(!toggleScreen);
}}
/>

</>
);
};

export default BabylonXrTest;

const styles = StyleSheet.create({
engineView: {
backgroundColor: “transparent”,
},
});

pasting a long code will be hard for us to debug :slight_smile:

A playground would be the best! or a project.

Now, regarding wall vs. floor - if the wall is not textured (i.e. a white wall with nothing on it) it will be hard for the system to detect. Otherwise - floor and wall detection should work the same.

How we can remove old planes. while updating onPlaneUpdatedObservable.

const xrPlanes = xr.baseExperience.featuresManager.enableFeature(
WebXRFeatureName.PLANE_DETECTION,
“latest”
) as WebXRPlaneDetector;
console.log(“Enabled plane detection.”);
const planes: any = ;

      xrPlanes.onPlaneAddedObservable.add((webXRPlane) => {
        if (scene) {
          console.log("Plane added.");
          let plane: any = webXRPlane;
          webXRPlane.polygonDefinition.push(
            webXRPlane.polygonDefinition[0]
          );
          try {
            plane.mesh = MeshBuilder.CreatePolygon(
              "plane",
              { shape: plane.polygonDefinition },
              scene,
              earcut
            );
            let tubeMesh: Mesh = CreateTube(
              "tube",
              {
                path: plane.polygonDefinition,
                radius: 0.005,
                sideOrientation: Mesh.FRONTSIDE,
                updatable: true,
              },
              scene
            );
            tubeMesh.setParent(plane.mesh);
            planes[plane.id] = plane.mesh;
            plane.mesh.material = planeMat;

            plane.mesh.rotationQuaternion = new Quaternion();
            plane.transformationMatrix.decompose(
              plane.mesh.scaling,
              plane.mesh.rotationQuaternion,
              plane.mesh.position
            );
          } catch (ex) {
            console.error(ex);
          }
        }
      });

      xrPlanes.onPlaneUpdatedObservable.add((webXRPlane) => {
        console.log("Plane updated.");
        let plane: any = webXRPlane;
        if (plane.mesh) {
          plane.mesh.dispose(false, false);
        }

        const some = plane.polygonDefinition.some((p: any) => !p);
        if (some) {
          return;
        }

        plane.polygonDefinition.push(plane.polygonDefinition[0]);
        try {
          plane.mesh = MeshBuilder.CreatePolygon(
            "plane",
            { shape: plane.polygonDefinition },
            scene,
            earcut
          );
          let tubeMesh: Mesh = CreateTube(
            "tube",
            {
              path: plane.polygonDefinition,
              radius: 0.005,
              sideOrientation: Mesh.FRONTSIDE,
              updatable: true,
            },
            scene
          );
          tubeMesh.setParent(plane.mesh);
          planes[plane.id] = plane.mesh;
          plane.mesh.material = planeMat;
          plane.mesh.rotationQuaternion = new Quaternion();
          plane.transformationMatrix.decompose(
            plane.mesh.scaling,
            plane.mesh.rotationQuaternion,
            plane.mesh.position
          );
          plane.mesh.receiveShadows = true;
        } catch (ex) {
          console.error(ex);
        }
      });

      xrPlanes.onPlaneRemovedObservable.add((webXRPlane) => {
        console.log("Plane removed.");
        let plane: any = webXRPlane;
        if (plane && planes[plane.id]) {
          planes[plane.id].dispose();
        }
      });

I wonder - did you read any of what I wrote in my last 2 answers?

Yes @RaananW, i don’t have permission to share repo.
surface detection is working fine.
just i want remove added planes.

planes[plane.id].dispose();

You can share a simplified version of your code. Otherwise we won’t be able to help.


Hi @RaananW @Cedric @jeremy-coleman @carolhmj, The surface detection is working fine.
thanks for your support.

Love to see that! great :slight_smile: