UNPKG

@bezlepkin/nativescript-ar

Version:

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

984 lines (983 loc) 38.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 arbox_1 = require("./nodes/ios/arbox"); const argroup_1 = require("./nodes/ios/argroup"); const arimage_1 = require("./nodes/ios/arimage"); const armaterialfactory_1 = require("./nodes/ios/armaterialfactory"); const armodel_1 = require("./nodes/ios/armodel"); const arplane_1 = require("./nodes/ios/arplane"); const arsphere_1 = require("./nodes/ios/arsphere"); const artext_1 = require("./nodes/ios/artext"); const artube_1 = require("./nodes/ios/artube"); const aruiview_1 = require("./nodes/ios/aruiview"); const arvideo_1 = require("./nodes/ios/arvideo"); const main_queue = dispatch_get_current_queue(); const sdkVersion = parseInt(core_1.Device.sdkVersion); const ARState = { planes: new Map(), shapes: new Map(), }; const addUIView = (options, parentNode, sceneView, renderer) => { return new Promise((resolve, reject) => { const view = aruiview_1.ARUIView.create(options, sceneView, renderer); ARState.shapes.set(view.id, view); parentNode.addChildNode(view.ios); resolve(view); }); }; const addNode = (options, parentNode, renderer) => { return new Promise((resolve, reject) => { const group = argroup_1.ARGroup.create(options, renderer); ARState.shapes.set(group.id, group); parentNode.addChildNode(group.ios); resolve(group); }); }; const addVideo = (options, parentNode, renderer) => { return new Promise((resolve, reject) => { const video = arvideo_1.ARVideo.create(options, renderer); ARState.shapes.set(video.id, video); parentNode.addChildNode(video.ios); resolve(video); }); }; const addImage = (options, parentNode, renderer) => { return arimage_1.ARImage.create(options, renderer).then(image => { ARState.shapes.set(image.id, image); parentNode.addChildNode(image.ios); return image; }); }; const addText = (options, parentNode, renderer) => { return new Promise((resolve, reject) => { const text = artext_1.ARText.create(options, renderer); ARState.shapes.set(text.id, text); parentNode.addChildNode(text.ios); resolve(text); }); }; const addBox = (options, parentNode, renderer) => { return new Promise((resolve, reject) => { const box = arbox_1.ARBox.create(options, renderer); ARState.shapes.set(box.id, box); parentNode.addChildNode(box.ios); resolve(box); }); }; const addPlane = (options, parentNode) => { return new Promise((resolve, reject) => { const plane = arplane_1.ARPlane.createExternal(options); ARState.shapes.set(plane.id, plane); parentNode.addChildNode(plane.ios); resolve(plane); }); }; const addModel = (options, parentNode, renderer) => { return new Promise((resolve, reject) => { const model = armodel_1.ARModel.create(options, renderer); setTimeout(() => { ARState.shapes.set(model.id, model); }); parentNode.addChildNode(model.ios); resolve(model); }); }; const addSphere = (options, parentNode, renderer) => { return new Promise((resolve, reject) => { const sphere = arsphere_1.ARSphere.create(options, renderer); ARState.shapes.set(sphere.id, sphere); parentNode.addChildNode(sphere.ios); resolve(sphere); }); }; const addTube = (options, parentNode, renderer) => { return new Promise((resolve, reject) => { const tube = artube_1.ARTube.create(options, renderer); ARState.shapes.set(tube.id, tube); parentNode.addChildNode(tube.ios); resolve(tube); }); }; class AR extends ar_common_1.AR { static isSupported() { try { return !!ARSCNView && NSProcessInfo.processInfo.environment.objectForKey("SIMULATOR_DEVICE_NAME") === null; } catch (ignore) { return false; } } static isImageTrackingSupported() { try { return !!ARImageTrackingConfiguration && ARImageTrackingConfiguration.isSupported; } catch (ignore) { return false; } } static isFaceTrackingSupported() { try { return !!ARFaceTrackingConfiguration && ARFaceTrackingConfiguration.isSupported; } catch (ignore) { return false; } } setDebugLevel(to) { if (!this.sceneView) { return; } if (to === "WORLD_ORIGIN") { this.sceneView.debugOptions = ARSCNDebugOptionShowWorldOrigin; } else if (to === "FEATURE_POINTS") { this.sceneView.debugOptions = ARSCNDebugOptionShowFeaturePoints; } else if (to === "PHYSICS_SHAPES") { this.sceneView.debugOptions = 1; } else { this.sceneView.debugOptions = 0; } } grabScreenshot() { return new Promise((resolve, reject) => { if (this.sceneView) { resolve(core_1.ImageSource.fromDataSync(this.sceneView.snapshot())); return; } reject("sceneView is not available"); }); } startRecordingVideo() { return new Promise((resolve, reject) => { if (!this.recorder) { this.recorder = RecordAR.alloc().initWithARSceneKit(this.sceneView); } if (this.recorder.status === 1) { this.recorder.record(); resolve(true); } else { reject(); } }); } stopRecordingVideo() { return new Promise((resolve, reject) => { this.recorder.stop(nsUrl => resolve(nsUrl.absoluteString)); }); } toggleStatistics(on) { if (!this.sceneView) { return; } this.sceneView.showsStatistics = !!on; } setPlaneDetection(to) { if (!this.sceneView) { return; } let arPlaneDetection = 0; if (to === "HORIZONTAL") { arPlaneDetection = 1; } else if (to === "VERTICAL") { arPlaneDetection = 2; } const config = this.configuration; config.planeDetection = arPlaneDetection; if (sdkVersion >= 13) { try { config.frameSemantics = 3; } catch (ignore) { } } if (this.sceneView.session.configuration) { this.sceneView.session.runWithConfiguration(config); } } togglePlaneVisibility(on) { const material = armaterialfactory_1.ARMaterialFactory.getMaterial(this.planeMaterial); ARState.planes.forEach(plane => { plane.setMaterial(material, on ? this.planeOpacity : 0); }); } getCameraPosition() { const p = this.sceneView.defaultCameraController.pointOfView.worldPosition; return { x: p.x, y: p.y, z: p.z }; } getCameraRotationRad() { let rot = this.sceneView.defaultCameraController.pointOfView.eulerAngles; return { x: rot.x, y: rot.y, z: rot.z }; } getCameraRotation() { const rot = this.getCameraRotationRad(); 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() { if (!AR.isSupported()) { console.log("############### AR is not supported on this device."); return; } if (this.trackingMode === "IMAGE") { if (!AR.isImageTrackingSupported()) { console.log("############### Image tracking is not supported on this device. It's probably not running iOS 12+."); return; } const imageTrackingConfig = ARImageTrackingConfiguration.new(); if (this.trackingImagesBundle) { const trackingImages = ARReferenceImage.referenceImagesInGroupNamedBundle(this.trackingImagesBundle, null); if (!trackingImages) { console.log("Could not load images from bundle!"); return; } imageTrackingConfig.trackingImages = trackingImages; imageTrackingConfig.maximumNumberOfTrackedImages = Math.min(trackingImages.count, 50); } this.configuration = imageTrackingConfig; } else if (this.trackingMode === "FACE") { if (!AR.isFaceTrackingSupported()) { console.log("############### Face tracking is not supported on this device. A device running iOS 12+ is required, with a front-facing TrueDepth camera."); return; } this.configuration = ARFaceTrackingConfiguration.new(); } else { this.configuration = ARWorldTrackingConfiguration.new(); } this.sceneView = ARSCNView.new(); this.sceneView.delegate = this.delegate = ARSCNViewDelegateImpl.createWithOwnerResultCallbackAndOptions(new WeakRef(this), data => { }, {}); this.toggleStatistics(this.showStatistics); this.sceneView.autoenablesDefaultLighting = true; this.sceneView.automaticallyUpdatesLighting = true; this.sceneView.scene.rootNode.name = "root"; const scene = SCNScene.new(); this.sceneView.scene = scene; if (this.trackingMode === "WORLD") { this.setPlaneDetection(this.planeDetection); this.addBottomPlane(scene); } this.configuration.lightEstimationEnabled = true; this.sceneView.session.runWithConfiguration(this.configuration); this.sceneTapHandler = SceneTapHandlerImpl.initWithOwner(new WeakRef(this)); const tapGestureRecognizer = UITapGestureRecognizer.alloc().initWithTargetAction(this.sceneTapHandler, "tap"); tapGestureRecognizer.numberOfTapsRequired = 1; this.sceneView.addGestureRecognizer(tapGestureRecognizer); this.sceneLongPressHandler = SceneLongPressHandlerImpl.initWithOwner(new WeakRef(this)); const longPressGestureRecognizer = UILongPressGestureRecognizer.alloc().initWithTargetAction(this.sceneLongPressHandler, "longpress"); longPressGestureRecognizer.minimumPressDuration = 0.5; this.sceneView.addGestureRecognizer(longPressGestureRecognizer); this.scenePanHandler = ScenePanHandlerImpl.initWithOwner(new WeakRef(this)); const panGestureRecognizer = UIPanGestureRecognizer.alloc().initWithTargetAction(this.scenePanHandler, "pan"); panGestureRecognizer.minimumNumberOfTouches = 1; this.sceneView.addGestureRecognizer(panGestureRecognizer); this.sceneRotationHandler = SceneRotationHandlerImpl.initWithOwner(new WeakRef(this)); const rotationGestureRecognizer = UIRotationGestureRecognizer.alloc().initWithTargetAction(this.sceneRotationHandler, "rotate"); this.sceneView.addGestureRecognizer(rotationGestureRecognizer); this.scenePinchHandler = ScenePinchHandlerImpl.initWithOwner(new WeakRef(this)); const pinchGestureRecognizer = UIPinchGestureRecognizer.alloc().initWithTargetAction(this.scenePinchHandler, "pinch"); this.sceneView.addGestureRecognizer(pinchGestureRecognizer); this.sceneView.antialiasingMode = 2; setTimeout(() => { this.nativeView.addSubview(this.sceneView); const eventData = { eventName: ar_common_1.AR.arLoadedEvent, object: this, ios: this.sceneView }; this.notify(eventData); }); } resolveParentNode(options) { if (options.parentNode && options.parentNode.ios) { return options.parentNode.ios; } return this.sceneView.scene.rootNode; } addBottomPlane(scene) { const bottomPlane = SCNBox.boxWithWidthHeightLengthChamferRadius(1000, 0.5, 1000, 0); const bottomMaterial = SCNMaterial.new(); bottomMaterial.diffuse.contents = UIColor.colorWithWhiteAlpha(1.0, 0.0); const materialArray = NSMutableArray.alloc().initWithCapacity(6); materialArray.addObject(bottomMaterial); bottomPlane.materials = materialArray; const bottomNode = SCNNode.nodeWithGeometry(bottomPlane); bottomNode.position = new ar_common_1.ARPosition(0, -25, 0); bottomNode.physicsBody = SCNPhysicsBody.bodyWithTypeShape(2, null); bottomNode.physicsBody.categoryBitMask = 0; bottomNode.physicsBody.contactTestBitMask = 1; scene.rootNode.addChildNode(bottomNode); scene.physicsWorld.contactDelegate = this.physicsWorldContactDelegate = SCNPhysicsContactDelegateImpl.createWithOwner(new WeakRef(this)); } createNativeView() { let v = super.createNativeView(); this.initAR(); return v; } onLayout(left, top, right, bottom) { super.onLayout(left, top, right, bottom); if (this.sceneView) { this.sceneView.layer.frame = this.ios.layer.bounds; } } sceneLongPressed(recognizer) { if (recognizer.state !== 1) { return; } const tapPoint = recognizer.locationInView(this.sceneView); const hitTestResults = this.sceneView.hitTestOptions(tapPoint, { SCNHitTestBoundingBoxOnlyKey: true, SCNHitTestFirstFoundOnlyKey: true }); if (hitTestResults.count === 0) { return; } const hitResult = hitTestResults.firstObject; const savedModel = this.getTargetARNodeFromSCNNode(hitResult.node, "onLongPress"); if (savedModel) { savedModel.onLongPress({ x: tapPoint.x, y: tapPoint.y }); } } getHitTargetWithProperty(position, property) { const hitTestResults = this.sceneView.hitTestOptions(position, NSDictionary.dictionaryWithDictionary({ SCNHitTestBoundingBoxOnlyKey: false, SCNHitTestFirstFoundOnlyKey: false })); if (hitTestResults.count === 0) { return undefined; } let i = 0; let savedModel = null; while (hitTestResults.count > i) { savedModel = this.getTargetARNodeFromSCNNode(hitTestResults.objectAtIndex(i).node); if (savedModel && (!!savedModel[property])) { return savedModel; } i++; } return undefined; } getTargetARNodeFromSCNNode(node, functionName) { if (!(node && node.name)) { return undefined; } const shape = ARState.shapes.get(node.name); if (shape && (!functionName || shape[functionName])) { return shape; } return node.parentNode ? this.getTargetARNodeFromSCNNode(node.parentNode, functionName) : undefined; } scenePanned(recognizer) { let state = recognizer.state; if (state === 5 || state === 4) { return; } let position = recognizer.locationInView(null); let translation = recognizer.translationInView(null); if (state === 1) { this.lastPositionForPanning = position; const savedModel = this.getHitTargetWithProperty(position, "draggingEnabled"); if (savedModel) { this.targetNodeForPanning = savedModel; this.targetNodeInitialPan = this.targetNodeForPanning.getWorldPosition(); } else { this.targetNodeForPanning = undefined; } } else if (this.targetNodeForPanning) { if (state === 2) { const pixelsPerMeter = 700; let node = SCNNode.node(); node.position = this.sceneView.defaultCameraController.pointOfView.convertPositionToNode({ x: (translation.x / pixelsPerMeter), y: -(translation.y / pixelsPerMeter), z: 0 }, null); node.rotation = this.sceneView.defaultCameraController.pointOfView.rotation; let p = node.worldPosition; let cp = this.sceneView.defaultCameraController.pointOfView.worldPosition; const pos = this.targetNodeInitialPan; this.targetNodeForPanning.setWorldPosition({ x: pos.x + p.x - cp.x, y: pos.y + p.y - cp.y, z: pos.z + p.z - cp.z }); } else if (state === 3) { this.targetNodeForPanning = undefined; } } } sceneRotated(recognizer) { let state = recognizer.state; if (state === 5 || state === 4) { return; } let position = recognizer.locationInView(this.sceneView); if (state === 1) { const savedModel = this.getHitTargetWithProperty(position, "rotatingEnabled"); if (savedModel && savedModel.ios) { this.targetNodeForRotating = savedModel.ios; } else { this.targetNodeForRotating = undefined; } } else if (this.targetNodeForRotating) { if (state === 2) { const previousAngles = this.targetNodeForRotating.eulerAngles; this.targetNodeForRotating.eulerAngles = { x: previousAngles.x, y: previousAngles.y - recognizer.rotation, z: previousAngles.z }; recognizer.rotation = 0; } else if (state === 3) { this.targetNodeForRotating = undefined; } } } scenePinched(recognizer) { let state = recognizer.state; if (state === 5 || state === 4) { return; } let position = recognizer.locationInView(this.sceneView); if (state === 1) { const savedModel = this.getHitTargetWithProperty(position, "scalingEnabled"); if (savedModel && savedModel.ios) { this.targetNodeForScaling = savedModel.ios; this.targetNodeInitialScale = this.targetNodeForScaling.scale; } else { this.targetNodeForScaling = undefined; } } else if (this.targetNodeForScaling) { if (state === 2) { this.targetNodeForScaling.scale = { x: this.targetNodeInitialScale.x * recognizer.scale, y: this.targetNodeInitialScale.y * recognizer.scale, z: this.targetNodeInitialScale.z * recognizer.scale }; } else if (state === 3) { this.targetNodeForScaling = undefined; } } } sceneTapped(recognizer) { const sceneView = recognizer.view; const tapPoint = recognizer.locationInView(sceneView); const hitTestResults = sceneView.hitTestOptions(tapPoint, null); if (hitTestResults.count === 0) { const eventData = { eventName: ar_common_1.AR.sceneTappedEvent, object: this, position: { x: tapPoint.x, y: tapPoint.y, z: 0 } }; this.notify(eventData); return; } const hitResult = hitTestResults.firstObject; let node = hitResult.node; if (node !== undefined) { let savedModel = this.getTargetARNodeFromSCNNode(node, "onTap"); if (savedModel !== undefined) { savedModel.onTap({ x: tapPoint.x, y: tapPoint.y }); return; } } const planeTapResults = this.sceneView.hitTestTypes(tapPoint, 16); if (planeTapResults.count > 0) { const planeHitResult = planeTapResults.firstObject; const hitResultStr = "" + planeHitResult; const transformStart = hitResultStr.indexOf("worldTransform=<translation=(") + "worldTransform=<translation=(".length; const transformStr = hitResultStr.substring(transformStart, hitResultStr.indexOf(")", transformStart)); const transformParts = transformStr.split(" "); const eventData = { eventName: ar_common_1.AR.planeTappedEvent, object: this, position: { x: +transformParts[0], y: +transformParts[1], z: +transformParts[2] } }; this.notify(eventData); } } addUIView(options) { return addUIView(options, this.resolveParentNode(options), this.sceneView, this.renderer); } addNode(options) { return addNode(options, this.resolveParentNode(options), this.renderer); } addVideo(options) { return addVideo(options, this.resolveParentNode(options), this.renderer); } addImage(options) { return addImage(options, this.resolveParentNode(options), this.renderer); } addModel(options) { return addModel(options, this.resolveParentNode(options), this.renderer); } addPlane(options) { return addPlane(options, this.resolveParentNode(options)); } addBox(options) { return addBox(options, this.resolveParentNode(options), this.renderer); } addSphere(options) { return addSphere(options, this.resolveParentNode(options), this.renderer); } addText(options) { return addText(options, this.resolveParentNode(options), this.renderer); } addTube(options) { return addTube(options, this.resolveParentNode(options), this.renderer); } trackImage(options) { let set; if (this.configuration instanceof ARImageTrackingConfiguration) { set = NSMutableSet.setWithSet(this.configuration.trackingImages); this.configuration.trackingImages = set; } else if (this.configuration instanceof ARWorldTrackingConfiguration) { set = NSMutableSet.setWithSet(this.configuration.detectionImages); this.configuration.detectionImages = set; } else { throw "'trackImage' is only supported with trackingMode: IMAGE"; } const name = options.name || options.image.split('/').pop().split('.').slice(0, -1).join('.'); let img; if (options.image.indexOf('://') > 0) { img = UIImage.imageWithData(NSData.alloc().initWithContentsOfURL(NSURL.URLWithString(options.image))); } else { img = UIImage.imageNamed(options.image); } const refImage = ARReferenceImage.alloc().initWithCGImageOrientationPhysicalWidth(img.CGImage, 1, options.width || 1); refImage.name = name; set.addObject(refImage); this.configuration.maximumNumberOfTrackedImages = Math.min(set.count, 50); this.sceneView.session.runWithConfigurationOptions(this.configuration, 1 | 2); if (!options.onDetectedImage) { return; } this.on(ar_common_1.AR.trackingImageDetectedEvent, (args) => { if (args.imageName === name) { options.onDetectedImage(args); } }); } reset() { this.configuration.planeDetection = 1; this.sceneView.session.runWithConfigurationOptions(this.configuration, 1 | 2); ARState.planes.forEach(plane => plane.remove()); ARState.planes.clear(); ARState.shapes.forEach(node => node.remove()); ARState.shapes.clear(); } } exports.AR = AR; class ScenePinchHandlerImpl extends NSObject { static initWithOwner(owner) { let handler = ScenePinchHandlerImpl.new(); handler._owner = owner; return handler; } pinch(args) { this._owner.get().scenePinched(args); } } ScenePinchHandlerImpl.ObjCExposedMethods = { "pinch": { returns: interop.types.void, params: [interop.types.id] } }; class SceneTapHandlerImpl extends NSObject { static initWithOwner(owner) { let handler = SceneTapHandlerImpl.new(); handler._owner = owner; return handler; } tap(args) { this._owner.get().sceneTapped(args); } } SceneTapHandlerImpl.ObjCExposedMethods = { "tap": { returns: interop.types.void, params: [interop.types.id] } }; class SceneLongPressHandlerImpl extends NSObject { static initWithOwner(owner) { let handler = SceneLongPressHandlerImpl.new(); handler._owner = owner; return handler; } longpress(args) { this._owner.get().sceneLongPressed(args); } } SceneLongPressHandlerImpl.ObjCExposedMethods = { "longpress": { returns: interop.types.void, params: [interop.types.id] } }; class ScenePanHandlerImpl extends NSObject { static initWithOwner(owner) { let handler = ScenePanHandlerImpl.new(); handler._owner = owner; return handler; } pan(args) { this._owner.get().scenePanned(args); } } ScenePanHandlerImpl.ObjCExposedMethods = { "pan": { returns: interop.types.void, params: [interop.types.id] } }; class SceneRotationHandlerImpl extends NSObject { static initWithOwner(owner) { let handler = SceneRotationHandlerImpl.new(); handler._owner = owner; return handler; } rotate(args) { this._owner.get().sceneRotated(args); } } SceneRotationHandlerImpl.ObjCExposedMethods = { "rotate": { returns: interop.types.void, params: [interop.types.id] } }; class ARSCNViewDelegateImpl extends NSObject { constructor() { super(...arguments); this.currentTrackingState = 2; this.hasFace = false; } static new() { try { ARSCNViewDelegateImpl.ObjCProtocols.push(ARSCNViewDelegate); } catch (ignore) { } return super.new(); } static createWithOwnerResultCallbackAndOptions(owner, callback, options) { let delegate = ARSCNViewDelegateImpl.new(); delegate.owner = owner; delegate.options = options; delegate.resultCallback = callback; return delegate; } sessionDidFailWithError(session, error) { console.log(">>> sessionDidFailWithError: " + error); } sessionWasInterrupted(session) { console.log(">>> sessionWasInterrupted: The tracking session has been interrupted. The session will be reset once the interruption has completed"); } sessionInterruptionEnded(session) { console.log(">>> sessionInterruptionEnded, calling reset"); this.owner.get().reset(); } sessionCameraDidChangeTrackingState(session, camera) { if (this.currentTrackingState === camera.trackingState) { return; } this.currentTrackingState = camera.trackingState; let trackingState = null, limitedTrackingStateReason = null; if (camera.trackingState === 0) { trackingState = "Not available"; } else if (camera.trackingState === 1) { trackingState = "Limited"; const reason = camera.trackingStateReason; if (reason === 2) { limitedTrackingStateReason = "Excessive motion"; } else if (reason === 3) { limitedTrackingStateReason = "Insufficient features"; } else if (reason === 1) { limitedTrackingStateReason = "Initializing"; } else if (reason === 0) { limitedTrackingStateReason = "None"; } } else if (camera.trackingState === 2) { trackingState = "Normal"; } if (trackingState !== null) { console.log(`Tracking state changed to: ${trackingState}`); if (limitedTrackingStateReason !== null) { console.log(`Limited tracking state reason: ${limitedTrackingStateReason}`); } } } rendererDidAddNodeForAnchor(renderer, node, anchor) { this.owner.get().renderer = renderer; if (anchor instanceof ARPlaneAnchor) { const owner = this.owner.get(); const plane = arplane_1.ARPlane.create(anchor, owner.planeOpacity, armaterialfactory_1.ARMaterialFactory.getMaterial(owner.planeMaterial)); ARState.planes.set(anchor.identifier.UUIDString, plane); node.addChildNode(plane.ios); const eventData = { eventName: ar_common_1.AR.planeDetectedEvent, object: owner, plane: plane }; owner.notify(eventData); } } rendererDidUpdateNodeForAnchor(renderer, node, anchor) { if (anchor instanceof ARPlaneAnchor) { const plane = ARState.planes.get(anchor.identifier.UUIDString); if (plane) { plane.update(anchor); } return; } const owner = this.owner.get(); if (!(anchor instanceof ARFaceAnchor)) { if (this.hasFace) { this.hasFace = false; owner.notify({ eventName: ar_common_1.AR.trackingFaceDetectedEvent, object: owner, eventType: "LOST" }); } return; } const faceAnchor = anchor; let eventType = "UPDATED"; if (!this.hasFace) { this.hasFace = true; owner.reset(); eventType = "FOUND"; } let faceGeometry; if (this.occlusionNode) { faceGeometry = this.occlusionNode.geometry; } else { faceGeometry = node.geometry; } if (faceGeometry) { const faceAnchor = anchor; faceGeometry.updateFromFaceGeometry(faceAnchor.geometry); } const blendShapes = faceAnchor.blendShapes; const eventData = { eventName: ar_common_1.AR.trackingFaceDetectedEvent, object: owner, eventType, properties: { eyeBlinkLeft: blendShapes.valueForKey(ARBlendShapeLocationEyeBlinkLeft), eyeBlinkRight: blendShapes.valueForKey(ARBlendShapeLocationEyeBlinkRight), jawOpen: blendShapes.valueForKey(ARBlendShapeLocationJawOpen), lookAtPoint: { x: faceAnchor.lookAtPoint[0], y: faceAnchor.lookAtPoint[1], z: faceAnchor.lookAtPoint[2] }, mouthFunnel: blendShapes.valueForKey(ARBlendShapeLocationMouthFunnel), mouthSmileLeft: blendShapes.valueForKey(ARBlendShapeLocationMouthSmileLeft), mouthSmileRight: blendShapes.valueForKey(ARBlendShapeLocationMouthSmileRight), tongueOut: blendShapes.valueForKey(ARBlendShapeLocationTongueOut) } }; dispatch_async(main_queue, () => owner.notify(eventData)); } rendererDidRemoveNodeForAnchor(renderer, node, anchor) { ARState.planes.delete(anchor.identifier.UUIDString); } rendererNodeForAnchor(renderer, anchor) { const node = SCNNode.new(); const owner = this.owner.get(); const sceneViewRenderer = renderer; if (anchor instanceof ARFaceAnchor) { let faceGeometry; if (owner.faceMaterial) { faceGeometry = ARSCNFaceGeometry.faceGeometryWithDevice(sceneViewRenderer.device); const material = faceGeometry.firstMaterial; material.colorBufferWriteMask = 15; material.diffuse.contents = owner.faceMaterial; material.lightingModelName = SCNLightingModelPhysicallyBased; node.addChildNode(SCNNode.nodeWithGeometry(faceGeometry)); } else { faceGeometry = ARSCNFaceGeometry.faceGeometryWithDeviceFillMesh(sceneViewRenderer.device, true); if (faceGeometry) { faceGeometry.firstMaterial.colorBufferWriteMask = 0; } } this.occlusionNode = SCNNode.nodeWithGeometry(faceGeometry); this.occlusionNode.renderingOrder = -1; node.addChildNode(this.occlusionNode); const eventData = { eventName: ar_common_1.AR.trackingFaceDetectedEvent, object: owner, eventType: "FOUND", faceTrackingActions: new ARFaceTrackingActionsImpl(renderer, anchor, node, this, owner.sceneView) }; dispatch_async(main_queue, () => owner.notify(eventData)); } if (!(anchor instanceof ARImageAnchor)) { return node; } const imageAnchor = anchor; const plane = SCNPlane.planeWithWidthHeight(imageAnchor.referenceImage.physicalSize.width, imageAnchor.referenceImage.physicalSize.height); const planeNode = SCNNode.nodeWithGeometry(plane); planeNode.eulerAngles = { x: -3.14159265359 / 2, y: 0, z: 0 }; planeNode.renderingOrder = -1; planeNode.opacity = 1; plane.firstMaterial.diffuse.contents = UIColor.colorWithWhiteAlpha(1, 0); const eventData = { eventName: ar_common_1.AR.trackingImageDetectedEvent, object: owner, position: planeNode.position, size: imageAnchor.referenceImage.physicalSize, imageName: imageAnchor.referenceImage.name, imageTrackingActions: new ARImageTrackingActionsImpl(plane, planeNode, owner.sceneView, renderer) }; dispatch_async(main_queue, () => owner.notify(eventData)); node.addChildNode(planeNode); return node; } } ARSCNViewDelegateImpl.ObjCProtocols = []; class ARImageTrackingActionsImpl { constructor(plane, planeNode, sceneView, renderer) { this.plane = plane; this.planeNode = planeNode; this.sceneView = sceneView; this.renderer = renderer; } playVideo(url, loop) { const videoPlayer = AVPlayer.playerWithURL(NSURL.URLWithString(url)); this.plane.firstMaterial.diffuse.contents = videoPlayer; if (loop === true) { this.AVPlayerItemDidPlayToEndTimeNotificationObserver = core_1.Application.ios.addNotificationObserver(AVPlayerItemDidPlayToEndTimeNotification, (notification) => { if (videoPlayer.currentItem && videoPlayer.currentItem === notification.object) { videoPlayer.seekToTime(CMTimeMake(5, 100)); videoPlayer.play(); } }); } videoPlayer.play(); } stopVideoLoop() { if (this.AVPlayerItemDidPlayToEndTimeNotificationObserver) { core_1.Application.ios.removeNotificationObserver(this.AVPlayerItemDidPlayToEndTimeNotificationObserver, AVPlayerItemDidPlayToEndTimeNotification); this.AVPlayerItemDidPlayToEndTimeNotificationObserver = undefined; } } addBox(options) { return addBox(options, this.planeNode, this.renderer); } addModel(options) { return addModel(options, this.planeNode, this.renderer); } addImage(options) { return addImage(options, this.planeNode, this.renderer); } addUIView(options) { return addUIView(options, this.planeNode, this.sceneView, this.renderer); } addNode(options) { return addNode(options, this.planeNode, this.renderer); } } class ARFaceTrackingActionsImpl { constructor(renderer, anchor, node, owner, sceneView) { this.renderer = renderer; this.anchor = anchor; this.node = node; this.owner = owner; this.sceneView = sceneView; } addModel(options) { return addModel(options, this.node, this.renderer); } addText(options) { return addText(options, this.node, this.renderer); } addUIView(options) { return addUIView(options, this.node, this.sceneView, this.renderer); } } class ARSessionDelegateImpl extends NSObject { constructor() { super(...arguments); this.currentTrackingState = 2; } static new() { try { ARSessionDelegateImpl.ObjCProtocols.push(ARSessionDelegate); } catch (ignore) { } return super.new(); } static createWithOwnerResultCallbackAndOptions(owner, callback, options) { let delegate = ARSessionDelegateImpl.new(); delegate.owner = owner; delegate.options = options; delegate.resultCallback = callback; return delegate; } sessionDidUpdateFrame(session, frame) { console.log("frame updated @ " + new Date().getTime()); } } ARSessionDelegateImpl.ObjCProtocols = []; class SCNPhysicsContactDelegateImpl extends NSObject { static new() { return super.new(); } static createWithOwner(owner) { let delegate = SCNPhysicsContactDelegateImpl.new(); delegate.owner = owner; return delegate; } physicsWorldDidBeginContact(world, contact) { const contactMask = contact.nodeA.physicsBody.categoryBitMask | contact.nodeB.physicsBody.categoryBitMask; if (contactMask === (0 | 1)) { if (contact.nodeA.physicsBody.categoryBitMask === 0) { contact.nodeB.removeFromParentNode(); } else { contact.nodeA.removeFromParentNode(); } } } physicsWorldDidEndContact(world, contact) { } physicsWorldDidUpdateContact(world, contact) { } } SCNPhysicsContactDelegateImpl.ObjCProtocols = [SCNPhysicsContactDelegate];