@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
JavaScript
;
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;