UNPKG

@bezlepkin/nativescript-ar

Version:

NativeScript Augmented Reality plugin. ARKit on iOS and (with the help of Sceneform) ARCore on Android.

553 lines (552 loc) 23.3 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.AR = void 0; const core_1 = require("@nativescript/core"); const ar_common_1 = require("./ar-common"); const imagefragment_android_1 = require("./imagefragment.android"); const arbox_1 = require("./nodes/android/arbox"); const argroup_1 = require("./nodes/android/argroup"); const arimage_1 = require("./nodes/android/arimage"); const armodel_1 = require("./nodes/android/armodel"); const arplane_1 = require("./nodes/android/arplane"); const arsphere_1 = require("./nodes/android/arsphere"); const artube_1 = require("./nodes/android/artube"); const aruiview_1 = require("./nodes/android/aruiview"); const arvideo_1 = require("./nodes/android/arvideo"); const screengrab_android_1 = require("./screengrab-android"); const videorecorder_android_1 = require("./videorecorder.android"); let _fragment, _origin, _videoRecorder; const AppPackageName = useAndroidX() ? global.androidx.core.app : android.support.v4.app; const ContentPackageName = useAndroidX() ? global.androidx.core.content : android.support.v4.content; function useAndroidX() { return global.androidx && global.androidx.appcompat; } const addNode = (options, parentNode) => { return new Promise((resolve, reject) => { argroup_1.ARGroup.create(options, _fragment) .then((group) => { group.android.setParent(parentNode); resolve(group); }).catch(reject); }); }; const addVideo = (options, parentNode) => { return new Promise((resolve, reject) => { arvideo_1.ARVideo.create(options, _fragment) .then((video) => { video.android.setParent(parentNode); resolve(video); }).catch(reject); }); }; const addImage = (options, parentNode) => { return new Promise((resolve, reject) => { arimage_1.ARImage.create(options, _fragment) .then((image) => { image.android.setParent(parentNode); resolve(image); }).catch(reject); }); }; const addPlane = (options, parentNode) => { return new Promise((resolve, reject) => { arplane_1.ARPlane.create(options, _fragment) .then((plane) => { plane.android.setParent(parentNode); resolve(plane); }).catch(reject); }); }; const addModel = (options, parentNode) => { return new Promise((resolve, reject) => { armodel_1.ARModel.create(options, _fragment) .then((model) => { if (parentNode) { model.android.setParent(parentNode); } resolve(model); }).catch(reject); }); }; const addBox = (options, parentNode) => { return new Promise((resolve, reject) => { arbox_1.ARBox.create(options, _fragment) .then((box) => { box.android.setParent(parentNode); if (parentNode && parentNode.getRenderable()) { parentNode.getRenderable().setRenderPriority(Math.max(0, box.android.getRenderable().getRenderPriority() - 1)); } resolve(box); }).catch(reject); }); }; const addSphere = (options, parentNode) => { return new Promise((resolve, reject) => { arsphere_1.ARSphere.create(options, _fragment) .then((sphere) => { sphere.android.setParent(parentNode); if (parentNode && parentNode.getRenderable()) { parentNode.getRenderable().setRenderPriority(Math.max(0, sphere.android.getRenderable().getRenderPriority() - 1)); } resolve(sphere); }).catch(reject); }); }; const addUIView = (options, parentNode) => { return new Promise((resolve, reject) => { aruiview_1.ARUIView.create(options, _fragment) .then((view) => { view.android.setParent(parentNode); resolve(view); }).catch(reject); }); }; const addTube = (options, parentNode) => { return new Promise((resolve, reject) => { artube_1.ARTube.create(options, _fragment) .then((tube) => { tube.android.setParent(parentNode); resolve(tube); }).catch(reject); }); }; const resolveParentNode = (options) => { if (options.parentNode && options.parentNode.android) { return options.parentNode.android; } return getOriginAnchor(); }; const getOriginAnchor = () => { if (!_origin) { const session = _fragment.getArSceneView().getSession(); const pose = com.google.ar.core.Pose.IDENTITY; const anchor = session.createAnchor(pose); const anchorNode = new com.google.ar.sceneform.AnchorNode(anchor); anchorNode.setParent(_fragment.getArSceneView().getScene()); _origin = anchorNode; } return _origin; }; class TNSArFragmentForFaceDetection extends com.google.ar.sceneform.ux.ArFragment { constructor() { super(); return global.__native(this); } getSessionConfiguration(session) { const config = new com.google.ar.core.Config(session); config.setAugmentedFaceMode(com.google.ar.core.Config.AugmentedFaceMode.MESH3D); return config; } getSessionFeatures() { return java.util.EnumSet.of(com.google.ar.core.Session.Feature.FRONT_CAMERA); } onCreateView(inflater, container, savedInstanceState) { const frameLayout = super.onCreateView(inflater, container, savedInstanceState); super.getPlaneDiscoveryController().hide(); super.getPlaneDiscoveryController().setInstructionView(null); return frameLayout; } } class ARImageTrackingActionsImpl { constructor(anchor, planeNode) { this.anchor = anchor; this.planeNode = planeNode; this.video; } playVideo(url, loop) { addVideo({ dimensions: { x: this.anchor.getExtentX(), y: this.anchor.getExtentZ() }, position: { x: 0, y: 0, z: 0 }, scale: 1 / this.planeNode.getLocalScale().x, video: url, loop: loop }, this.planeNode).then(video => this.video = video).catch(console.error); } stopVideoLoop() { if (this.video) { this.video.pause(); } } addBox(options) { return addBox(options, this.planeNode); } addModel(options) { return addModel(options, this.planeNode); } addImage(options) { return addImage(options, this.planeNode); } addUIView(options) { return addUIView(options, this.planeNode); } addNode(options) { return addNode(options, this.planeNode); } } class ARFaceTrackingActionsImpl { constructor(faceNode) { this.faceNode = faceNode; } addModel(options) { return new Promise((resolve, reject) => { addModel(options) .then(model => { this.faceNode.setParent(_fragment.getArSceneView().getScene()); this.faceNode.setFaceRegionsRenderable(model.android.getRenderable()); resolve(model); }) .catch(err => reject); }); } addText(options) { return Promise.reject("addText not implemented for ARFaceTrackingActions"); } addUIView(options) { return Promise.reject("addUIView not implemented for ARFaceTrackingActions"); } } class AR extends ar_common_1.AR { constructor() { super(...arguments); this.faceNodeMap = new Map(); } initNativeView() { super.initNativeView(); this.initAR(); } disposeNativeView() { super.disposeNativeView(); if (_origin) { _origin.setParent(null); _origin = null; } const supportFragmentManager = (core_1.Application.android.foregroundActivity || core_1.Application.android.startActivity).getSupportFragmentManager(); supportFragmentManager.beginTransaction().remove(_fragment).commit(); } getCameraPosition() { const p = Array.create("float", 3); _fragment.getArSceneView().getArFrame().getCamera().getPose().getTranslation(p, 0); return { x: p[0], y: p[1], z: p[2] }; } getCameraRotation() { const q1 = Array.create("float", 4); _fragment.getArSceneView().getArFrame().getCamera().getPose().getRotationQuaternion(q1, 0); const q = { x: q1[2], y: q1[0], z: q1[1], w: q1[3] }; const rot = { z: 0, x: 0, y: 0 }; const sinr_cosp = +2.0 * (q.w * q.x + q.y * q.z); const cosr_cosp = +1.0 - 2.0 * (q.x * q.x + q.y * q.y); rot.z = -(Math.atan2(sinr_cosp, cosr_cosp) + Math.PI / 2); const sinp = +2.0 * (q.w * q.y - q.z * q.x); if (Math.abs(sinp) >= 1) { rot.x = -(sinp / Math.abs(sinp)) * (Math.PI / 2); } else { rot.x = -Math.asin(sinp); } const siny_cosp = +2.0 * (q.w * q.z + q.x * q.y); const cosy_cosp = +1.0 - 2.0 * (q.y * q.y + q.z * q.z); rot.y = Math.atan2(siny_cosp, cosy_cosp) + Math.PI; const toDeg = (rad) => { return ((rad * (180.0 / Math.PI)) + 360) % 360; }; return { x: toDeg(rot.x), y: toDeg(rot.y), z: toDeg(rot.z) }; } initAR() { this.nativeView.setId(android.view.View.generateViewId()); if (this.trackingMode === "FACE") { _fragment = new TNSArFragmentForFaceDetection(); } else { if (this.trackingMode === "IMAGE") { _fragment = new imagefragment_android_1.TNSArFragmentForImageDetection(); _fragment.getImageDetectionSceneView().then(sceneView => { if (this.trackingImagesBundle) { _fragment.addImagesInFolder(this.trackingImagesBundle); } const scene = sceneView.getScene(); const augmentedImages = []; scene.addOnUpdateListener(new com.google.ar.sceneform.Scene.OnUpdateListener({ onUpdate: frameTime => { const frame = sceneView.getArFrame(); if (frame == null) { return; } const updatedAugmentedImages = frame.getUpdatedTrackables(com.google.ar.core.AugmentedImage.class).toArray(); for (let i = 0; i < updatedAugmentedImages.length; i++) { let augmentedImage = updatedAugmentedImages[i]; const state = augmentedImage.getTrackingState(); if (state === com.google.ar.core.TrackingState.PAUSED) { } else if (state === com.google.ar.core.TrackingState.TRACKING) { if (augmentedImages.indexOf(augmentedImage.getName()) === -1) { const anchor = new com.google.ar.sceneform.AnchorNode(augmentedImage.createAnchor(augmentedImage.getCenterPose())); augmentedImages.push(augmentedImage.getName()); scene.addChild(anchor); const planeNode = new com.google.ar.sceneform.Node(); anchor.addChild(planeNode); planeNode.setLocalRotation(new com.google.ar.sceneform.math.Quaternion(new com.google.ar.sceneform.math.Vector3(-90, 0, 0))); const eventData = { eventName: ar_common_1.AR.trackingImageDetectedEvent, object: this, size: { width: augmentedImage.getExtentX(), height: augmentedImage.getExtentZ() }, position: { x: augmentedImage.getCenterPose().tx(), y: augmentedImage.getCenterPose().ty(), z: augmentedImage.getCenterPose().tz() }, imageName: augmentedImage.getName(), imageTrackingActions: new ARImageTrackingActionsImpl(augmentedImage, planeNode) }; this.notify(eventData); } } else if (state === com.google.ar.core.TrackingState.STOPPED) { const i = augmentedImages.indexOf(augmentedImage.getName()); augmentedImages.splice(i, 1); } } } })); }).catch(console.log); } else { _fragment = new com.google.ar.sceneform.ux.ArFragment(); } } const onCamPermissionGranted = () => { if (this.trackingMode === "FACE") { setTimeout(() => { const sceneView = _fragment.getArSceneView(); if (!sceneView) { return; } sceneView.setCameraStreamRenderPriority(com.google.ar.sceneform.rendering.Renderable.RENDER_PRIORITY_FIRST); const scene = sceneView.getScene(); scene.addOnUpdateListener(new com.google.ar.sceneform.Scene.OnUpdateListener({ onUpdate: frameTime => { const faceList = sceneView.getSession().getAllTrackables(com.google.ar.core.AugmentedFace.class); for (let i = 0; i < faceList.size(); i++) { const face = faceList.get(i); if (!this.faceNodeMap.has(face)) { const faceNode = new com.google.ar.sceneform.ux.AugmentedFaceNode(face); this.faceNodeMap.set(face, faceNode); const eventData = { eventType: "FOUND", eventName: ar_common_1.AR.trackingFaceDetectedEvent, object: this, faceTrackingActions: new ARFaceTrackingActionsImpl(faceNode) }; this.notify(eventData); } else { } } this.faceNodeMap.forEach((node, face) => { if (face.getTrackingState() === com.google.ar.core.TrackingState.STOPPED) { node.setParent(null); this.faceNodeMap.delete(node); const eventData = { eventType: "LOST", eventName: ar_common_1.AR.trackingFaceDetectedEvent, object: this }; this.notify(eventData); } }); } })); }, 1000); } const supportFragmentManager = (core_1.Application.android.foregroundActivity || core_1.Application.android.startActivity).getSupportFragmentManager(); supportFragmentManager.beginTransaction().add(this.nativeView.getId(), _fragment).commit(); _fragment.setOnTapArPlaneListener(new com.google.ar.sceneform.ux.BaseArFragment.OnTapArPlaneListener({ onTapPlane: (hitResult, plane, motionEvent) => { const eventData = { eventName: ar_common_1.AR.planeTappedEvent, object: this, position: { x: hitResult.getHitPose().tx(), y: hitResult.getHitPose().ty(), z: hitResult.getHitPose().tz() } }; this.notify(eventData); } })); this.fireArLoadedEvent(1000); }; const cameraPermission = android.Manifest.permission.CAMERA; if (this.wasPermissionGranted(cameraPermission)) { setTimeout(() => onCamPermissionGranted(), 0); } else { this._requestPermission(cameraPermission, () => onCamPermissionGranted()); } } fireArLoadedEvent(attemptsLeft) { if (attemptsLeft-- <= 0) { return; } setTimeout(() => { if (_fragment.getArSceneView() && _fragment.getArSceneView().getSession() && _fragment.getArSceneView().getArFrame() && _fragment.getArSceneView().getArFrame().getCamera() && _fragment.getArSceneView().getArFrame().getCamera().getTrackingState() === com.google.ar.core.TrackingState.TRACKING) { const eventData = { eventName: ar_common_1.AR.arLoadedEvent, object: this, android: _fragment }; this.notify(eventData); } else { this.fireArLoadedEvent(attemptsLeft); } }, 300); } static isSupported() { return true; } getFragment() { return _fragment; } togglePlaneVisibility(to) { _fragment.getArSceneView().getPlaneRenderer().setVisible(to); } setPlaneDetection(to) { } toggleStatistics(on) { console.log("Method not implemented: toggleStatistics"); } setDebugLevel(to) { console.log("Method not implemented: setDebugLevel"); } grabScreenshot() { return (new screengrab_android_1.FragmentScreenGrab()).grabScreenshot(_fragment); } startRecordingVideo() { return new Promise((resolve, reject) => { if (!_videoRecorder) { _videoRecorder = videorecorder_android_1.VideoRecorder.fromFragment(_fragment); } else if (_videoRecorder.isRecording()) { reject("already recording"); return; } const record = () => { _videoRecorder.setVideoQualityAuto(); _videoRecorder.startRecordingVideo(); }; const permission = android.Manifest.permission.WRITE_EXTERNAL_STORAGE; if (!this.wasPermissionGranted(permission)) { this._requestPermission(permission, record, reject); return; } record(); resolve(true); }); } stopRecordingVideo() { return new Promise((resolve, reject) => { if (!(_videoRecorder && _videoRecorder.isRecording())) { reject("not recording"); } _videoRecorder.stopRecordingVideo(); resolve(_videoRecorder.getVideoPath()); }); } reset() { console.log("Method not implemented: reset"); return null; } addNode(options) { return addNode(options, resolveParentNode(options)); } addVideo(options) { return addVideo(options, resolveParentNode(options)); } addImage(options) { return addImage(options, resolveParentNode(options)); } addModel(options) { return addModel(options, resolveParentNode(options)); } addPlane(options) { return addPlane(options, resolveParentNode(options)); } addBox(options) { return addBox(options, resolveParentNode(options)); } addSphere(options) { return addSphere(options, resolveParentNode(options)); } addText(options) { return new Promise((resolve, reject) => { reject("Method not implemented: addText"); }); } addTube(options) { return addTube(options, resolveParentNode(options)); } addUIView(options) { return addUIView(options, resolveParentNode(options)); } trackImage(options) { if (!(_fragment instanceof imagefragment_android_1.TNSArFragmentForImageDetection)) { throw "On Android, this is only supported in trackingMode: IMAGE"; } const name = options.name || options.image.split('/').pop().split('.').slice(0, -1).join('.'); _fragment.addImage(options.image, name, options.width || -1); if (!options.onDetectedImage) { return; } this.on(ar_common_1.AR.trackingImageDetectedEvent, (args) => { if (args.imageName === options.image.split('/').pop().split('.').slice(0, -1).join('.')) { options.onDetectedImage(args); } }); } wasPermissionGranted(permission) { let hasPermission = android.os.Build.VERSION.SDK_INT < 23; if (!hasPermission) { hasPermission = android.content.pm.PackageManager.PERMISSION_GRANTED === ContentPackageName.ContextCompat.checkSelfPermission(core_1.Utils.ad.getApplicationContext(), permission); } return hasPermission; } _requestPermission(permission, onPermissionGranted, reject) { const permissionRequestCode = 678; const onPermissionEvent = (args) => { if (args.requestCode === permissionRequestCode) { for (let i = 0; i < args.permissions.length; i++) { if (args.grantResults[i] === android.content.pm.PackageManager.PERMISSION_DENIED) { core_1.Application.off(core_1.AndroidApplication.activityRequestPermissionsEvent, onPermissionEvent); reject && reject("Please allow access to external storage and try again."); return; } } core_1.Application.off(core_1.AndroidApplication.activityRequestPermissionsEvent, onPermissionEvent); onPermissionGranted(); } }; core_1.Application.android.on(core_1.AndroidApplication.activityRequestPermissionsEvent, onPermissionEvent); AppPackageName.ActivityCompat.requestPermissions(core_1.Application.android.foregroundActivity || core_1.Application.android.startActivity, [permission], permissionRequestCode); } } exports.AR = AR;