Поддержка ARCore и Sceneform для навигации в помещении

#java #android #navigation #arcore #sceneform

Вопрос:

Я разрабатываю приложение AR для Android для навигации в помещении. Я реализовал класс beaconService, чтобы получать значения моих 3 маяков и сохранять их с помощью базы данных SQLite. Приняв эти значения, я оцениваю местоположение пользователя с помощью метода трилатерации. Несмотря на то,что я сталкиваюсь с некоторыми проблемами с местоположением, которое я вычисляю (возможно, из-за неправильного расположения моих 3 маяков), я хочу использовать это местоположение (это массив значений x, y) для реализации AR-части моего приложения. в частности, я хочу, чтобы пользователь перемещался к месту назначения с помощью Sceneform и ARCore sdk. Я опубликую класс BeaconService и часть того, что я пытался реализовать для определения местоположения пользователя и реализации части AR (часть навигации). ПОЭТОМУ мне нужна помощь в части AR моего приложения.

Класс обслуживания маяков

  protected ScanCallback mScanCallback = new ScanCallback() {
    @RequiresApi(api = Build.VERSION_CODES.P)
    @Override
    public void onScanResult(int callbackType, final ScanResult result) {
        final BluetoothDevice bluetoothDevice = result.getDevice();
        ScanRecord scanRecord = result.getScanRecord();
        assert scanRecord != null;
        List<ADStructure> structures = ADPayloadParser.getInstance().parse(scanRecord.getBytes());
        ConstantsVariables.activity_started_bluetooth = true;
        String deviceName = scanRecord.getDeviceName();

        if (deviceName != null) {
            for (ADStructure structure : structures) {
                if (structure instanceof IBeacon) {
                    final IBeacon iBeacon = (IBeacon) structure;
                    Distance = (float) calculateDistance(applyKalmanFilterToRssi(result.getRssi()));
                    if (db.getCount() == 0 || !db.checkIfSpecificMinorIsInDB(iBeacon.getMinor())) {
                        db.addBeacon(new BeaconInfo(iBeacon.getMinor(), Distance, result.getRssi()));
                    }
                    else {
                        BeaconInfo beaconInfo = db.getBeaconInfo(iBeacon.getMinor());
                        beaconInfo.setMinor(iBeacon.getMinor());
                        beaconInfo.setDistance(Distance);
                        beaconInfo.setRssi(result.getRssi());
                        db.updateBeacon(beaconInfo);
                    }
 }

@NonNull
@Override
public Result doWork() {
    mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
    if (mBluetoothAdapter == null) {
        System.out.println("Bluetooth not supported");
    } else {
        System.out.println("Bluetooth initialized");
    }
    if (mBluetoothAdapter.isEnabled()) {
        mBluetoothLeScanner = mBluetoothAdapter.getBluetoothLeScanner();
        setScanSettings();
        mBluetoothLeScanner.startScan(null, mScanSettings, mScanCallback);
    }
    return Result.success();
}

// Method Apply Filter
private double applyKalmanFilterToRssi(double rssi){
    double filterrssi = kalmanFilter.applyFilter(rssi);
    return filterrssi;
}

private void setScanSettings() {
    ScanSettings.Builder mBuilder = new ScanSettings.Builder();
    mBuilder.setReportDelay(0);
    mBuilder.setScanMode(ScanSettings.SCAN_MODE_LOW_POWER);
    mScanSettings = mBuilder.build();
}

public double calculateDistance(double rssi) {
    int txPower = -56;
    if (rssi == 0) {
        return -1.0; // if we cannot determine accuracy, return -1.
    }
    double ratio = rssi * 1.0 / txPower;
    if (ratio < 1.0) {
        return Math.pow(ratio, 10);
    } else {
        return (0.89976) * Math.pow(ratio, 7.7095)   0.111;
    }
}
 

Класс навигации

Чтобы определить местоположение пользователя, я использую этот метод:

 private void updateUserLocation {
    DatabaseHandler db = new DatabaseHandler(getApplicationContext());
    List<BeaconInfo> beaconList = db.getAllBeacons();

    for (BeaconInfo beaconInfo : beaconList) {
        int MinorType = beaconInfo.getMinor();

        if (MinorType == ConstantsVariables.minor_437) {
            distance437 =  beaconInfo.getDistance();
            distanceVector[0] = distance437 / 100000; //km to meters

        } else if (MinorType == ConstantsVariables.minor_570) {
            distance570 = beaconInfo.getDistance();
            distanceVector[1] = distance570 / 100000;

        } else if (MinorType == ConstantsVariables.minor_574) {
            distance574 = beaconInfo.getDistance();
            distanceVector[2] = distance574 / 100000;
        }
    }
    Log.i(TAG, "Distances: "   distance437   " / "   distance570   " / "   distance574);

    //Trilateration
    NonLinearLeastSquaresSolver solver = new NonLinearLeastSquaresSolver(new TrilaterationFunction(positions, distanceVector), new LevenbergMarquardtOptimizer());
    LeastSquaresOptimizer.Optimum optimum = solver.solve();
    calculatedPosition = optimum.getPoint().toArray();
 

}

For the AR part i have impemented these:

     private void setARFragment() {

         mARFragment = (CloudAnchorFragment) getSupportFragmentManager().findFragmentById(R.id.fragment);
         // When you build a Renderable, Sceneform loads its resources in the background while returning
         // a CompletableFuture. Call thenAccept(), handle(), or check isDone() before calling get().

         create3dModel();
        mARFragment.getArSceneView().getScene().addOnUpdateListener(this::onSceneUpdate);

}

private void create3dModel() {
    ModelRenderable.builder()
            .setSource(this, Uri.parse("model.sfb"))
            .build()
            .thenAccept(renderable -> mObjRenderable = renderable)
            .exceptionally(
                    throwable -> {
                        Toast toast = Toast.makeText(this, "Unable to load obj renderable", Toast.LENGTH_LONG);
                        toast.setGravity(Gravity.CENTER, 0, 0);
                        toast.show();
                        return null;
                    });
}


 private void onSceneUpdate(FrameTime frameTime) {
        // Let the fragment update its state first.
    mARFragment.onUpdate(frameTime);

    // If there is no frame then don't process anything.
    if (mARFragment.getArSceneView().getArFrame() == null) {
        return;
    }

    // If ARCore is not tracking yet, then don't process anything.
    if (mARFragment.getArSceneView().getArFrame().getCamera().getTrackingState() != TrackingState.TRACKING) {
        return;
    }
    
    // Place the anchor 1m in front of the camera if anchorNode is null.
    if (this.mAnchorNode == null) {
        addModelToScene();
}


private void addModelToScene() {
    Session session = mARFragment.getArSceneView().getSession();
    Pose pos = Objects.requireNonNull(mARFragment.getArSceneView().getArFrame()).getCamera().getPose().compose(Pose.makeTranslation(0, 0, -1));
    assert session != null;
    Anchor mAnchor;
    mAnchor = session.createAnchor(pos);
    mAnchorNode = new AnchorNode(mAnchor);
    mAnchorNode.setParent(mARFragment.getArSceneView().getScene());

    // Create the arrow node and add it to the anchor.
    arrow = new Node();
    Quaternion rotation1 = Quaternion.axisAngle(new Vector3(1.3f, 0.5f, 0.0f), 90); // rotate X axis 90 degrees
    Quaternion rotation2 = Quaternion.axisAngle(new Vector3(0.5f, -1.5f, 1.0f), 90); // rotate Y axis 90 degrees
    arrow.setLocalRotation(Quaternion.multiply(rotation1, rotation2));
    arrow.setParent(mAnchorNode);
    //arrow.setRenderable(mObjRenderable);
}
 

So i have to create a new method now that based on the calculatedPosition(user position) and the destination point (is saved) to navigate user to their destination with AR. Any help will be appreciated.