Skip to content

Added support for UI rotation gestures #123

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions ARKit.js
Original file line number Diff line number Diff line change
@@ -16,6 +16,7 @@ import PropTypes from 'prop-types';
import React, { Component } from 'react';

import { pickColors, pickColorsFromFile } from './lib/pickColors';
import { position, transition } from './components/lib/propTypes';
import generateId from './components/lib/generateId';

const ARKitManager = NativeModules.ARKitManager;
@@ -69,9 +70,12 @@ class ARKit extends Component {
{...this.props}
onTapOnPlaneUsingExtent={this.callback('onTapOnPlaneUsingExtent')}
onTapOnPlaneNoExtent={this.callback('onTapOnPlaneNoExtent')}
onRotationGesture={this.callback('onRotationGesture')}
onPlaneDetected={this.callback('onPlaneDetected')}
onPlaneRemoved={this.callback('onPlaneRemoved')}
onPlaneUpdate={this.callback('onPlaneUpdate')}
onTrackingState={this.callback('onTrackingState')}
onARKitError={this.callback('onARKitError')}
onEvent={this._onEvent}
/>
{state}
@@ -175,10 +179,16 @@ ARKit.pickColorsFromFile = pickColorsFromFile;
ARKit.propTypes = {
debug: PropTypes.bool,
planeDetection: PropTypes.bool,
origin: PropTypes.shape({
position,
transition,
}),
lightEstimationEnabled: PropTypes.bool,
autoenablesDefaultLighting: PropTypes.bool,
worldAlignment: PropTypes.number,
onARKitError: PropTypes.func,
onPlaneDetected: PropTypes.func,
onPlaneRemoved: PropTypes.func,
onFeaturesDetected: PropTypes.func,
// onLightEstimation is called rapidly, better poll with
// ARKit.getCurrentLightEstimation()
@@ -187,6 +197,7 @@ ARKit.propTypes = {
onTrackingState: PropTypes.func,
onTapOnPlaneUsingExtent: PropTypes.func,
onTapOnPlaneNoExtent: PropTypes.func,
onRotationGesture: PropTypes.func,
onEvent: PropTypes.func,
};

8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@


### 2017-12-21

- added `onPlaneRemoved`
- added `eulerAngles` to detected planes
- added `onARKitError` to ARKit
- added `origin` property to ARKit
21 changes: 18 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -69,6 +69,7 @@ export default class ReactNativeARKit extends Component {
onLightEstimation={e => console.log(e.nativeEvent)}
onPlaneDetected={console.log} // event listener for plane detection
onPlaneUpdate={console.log} // event listener for plane update
onPlaneRemoved={console.log} // arkit sometimes removes detected planes
>
<ARKit.Box
position={{ x: 0, y: 0, z: 0 }}
@@ -176,15 +177,29 @@ AppRegistry.registerComponent('ReactNativeARKit', () => ReactNativeARKit);
| `planeDetection` | `Boolean` | `false` | ARKit plane detection.
| `lightEstimationEnabled` | `Boolean` | `false` | ARKit light estimation.
| `worldAlignment` | `Enumeration` <br /> One of: `ARKit.ARWorldAlignment.Gravity`, `ARKit.ARWorldAlignment.GravityAndHeading`, `ARKit.ARWorldAlignment.Camera` (documentation [here](https://developer.apple.com/documentation/arkit/arworldalignment)) | `ARKit.ARWorldAlignment.Gravity` | **ARWorldAlignmentGravity** <br /> The coordinate system's y-axis is parallel to gravity, and its origin is the initial position of the device. **ARWorldAlignmentGravityAndHeading** <br /> The coordinate system's y-axis is parallel to gravity, its x- and z-axes are oriented to compass heading, and its origin is the initial position of the device. **ARWorldAlignmentCamera** <br /> The scene coordinate system is locked to match the orientation of the camera.|
| `origin` | `{position, transition}` | Usually `{0,0,0}` is where you launched the app. If you want to have a different origin, you can set it here. E.g. if you set `origin={{position: {0,-1, 0}, transition: {duration: 1}}}` the new origin will be one meter below. If you have any objects already placed, they will get moved down using the given transition. All hit-test functions or similar will report coordinates relative to that new origin as `position`. You can get the original coordinates with `positionAbsolute` in these functions |

##### Events

| Event Name | Returns | Notes
|---|---|---|
| `onPlaneDetected` | `{ id, center, extent }` | When a plane is first detected.
| `onARKitError` | `ARKiterror` | will report whether an error occured while initializing ARKit. A common error is when the user has not allowed camera access. Another error is, if you use `worldAlignment=GravityAndHeading` and location service is turned off |
| `onLightEstimation` | `{ ambientColorTemperature, ambientIntensity }` | Light estimation on every frame. Called rapidly, better use polling. See `ARKit.getCurrentLightEstimation()`
| `onFeaturesDetected` | `{ featurePoints}` | Detected Features on every frame (currently also not throttled). Usefull to display custom dots for detected features. You can also poll this information with `ARKit.getCurrentDetectedFeaturePoints()`
| `onPlaneUpdate` | `{ id, center, extent }` | When a detected plane is updated
| `onPlaneDetected` | `Plane` | When a plane is first detected.
| `onPlaneUpdate` | `Plane` | When a detected plane is updated
| `onPlaneRemoved` | `Plane` | When a detected plane is updated
| `onRotationGesture` | `{ rotation, velocity }` | The rotation gesture recognizer enters the `begin` state as soon as the position of the user’s fingers changes in a way that indicates that rotation has begun. After the initial change, subsequent changes cause the gesture recognizer to enter the `change` state and update the angle of rotation. When the user’s fingers lift from the screen, the gesture recognizer enters the `end` state.

The `Plane` object has the following properties:

| Property | Description
|---|---|
| `id` | a unique id identifying the plane |
| `position` | the position of the plane (relative to the origin) |
| `positionAbsolute` | the absolute position of the plane |
| `extent` | the extent of the plane |
| `eulerAngles` | the rotation of the plane |

##### Static methods

@@ -253,7 +268,7 @@ Most objects take a material property with these sub-props:
| `doubleSided` | boolean | render both sides, default is `true` |
| `litPerPixel` | boolean | calculate lighting per-pixel or vertex [litPerPixel](https://developer.apple.com/documentation/scenekit/scnmaterial/1462580-litperpixel) |
| `lightingModel` | `ARKit.LightingModel.*` | [LightingModel](https://developer.apple.com/documentation/scenekit/scnmaterial.lightingmodel) |
| `blendMode` | `ARKit.BlendMode.*` | [BlendMode](https://developer.apple.com/documentation/scenekit/scnmaterial/1462585-blendmode) |
| `blendMode` | `ARKit.BlendMode.*` | [BlendMode](https://developer.apple.com/documentation/scenekit/scnmaterial/1462585-blendmode) |
| `fillMode` | `ARKit.FillMode.*` | [FillMode](https://developer.apple.com/documentation/scenekit/scnmaterial/2867442-fillmode)
| `shaders` | Object with keys from `ARKit.ShaderModifierEntryPoint.*` and shader strings as values | [Shader modifiers](https://developer.apple.com/documentation/scenekit/scnshadable) |
| `colorBufferWriteMask` | `ARKit.ColorMask.*` | [color mask](https://developer.apple.com/documentation/scenekit/scncolormask). Set to ARKit.ColorMask.None so that an object is transparent, but receives deferred shadows. |
534 changes: 534 additions & 0 deletions README.md.orig

Large diffs are not rendered by default.

31 changes: 24 additions & 7 deletions components/lib/createArComponent.js
Original file line number Diff line number Diff line change
@@ -88,6 +88,7 @@ export default (mountConfig, propTypes = {}, nonUpdateablePropKeys = []) => {
: mountConfig.mount;

const mount = (id, props) => {
if (DEBUG) console.log(`[${id}] [${new Date().getTime()}] mount`, props);
mountFunc(
getNonNodeProps(props),
{
@@ -98,6 +99,16 @@ export default (mountConfig, propTypes = {}, nonUpdateablePropKeys = []) => {
);
};

const update = (id, props) => {
if (DEBUG) console.log(`[${id}] [${new Date().getTime()}] update`, props);
ARGeosManager.updateNode(id, props);
};

const unmount = id => {
if (DEBUG) console.log(`[${id}] [${new Date().getTime()}] unmount`);
ARGeosManager.unmount(id);
};

const ARComponent = class extends Component {
identifier = null;
componentDidMount() {
@@ -108,7 +119,7 @@ export default (mountConfig, propTypes = {}, nonUpdateablePropKeys = []) => {
const {
transition: transitionOnMount = { duration: 0 },
} = fullPropsOnMount;
if (DEBUG) console.log('mount', fullPropsOnMount);

this.doPendingTimers();
mount(this.identifier, fullPropsOnMount);

@@ -137,13 +148,20 @@ export default (mountConfig, propTypes = {}, nonUpdateablePropKeys = []) => {
);
if (nonAllowedUpdates.length > 0) {
throw new Error(
`prop can't be updated: '${nonAllowedUpdates.join(', ')}'`,
`[${this
.identifier}] prop can't be updated: '${nonAllowedUpdates.join(
', ',
)}'`,
);
}
}

if (some(changedKeys, k => nonUpdateablePropKeys.includes(k))) {
if (DEBUG) console.log('need to remount node because of ', changedKeys);
if (DEBUG)
console.log(
`[${this.identifier}] need to remount node because of `,
changedKeys,
);
mount(this.identifier, { ...this.props, ...props });
} else {
// every property is updateable
@@ -158,8 +176,7 @@ export default (mountConfig, propTypes = {}, nonUpdateablePropKeys = []) => {
...parseMaterials(pick(props, changedKeys)),
};

if (DEBUG) console.log('update node', propsToupdate);
ARGeosManager.updateNode(this.identifier, propsToupdate);
update(this.identifier, propsToupdate);
}
}

@@ -171,11 +188,11 @@ export default (mountConfig, propTypes = {}, nonUpdateablePropKeys = []) => {

this.componentWillUpdate(fullProps);
this.delayed(() => {
ARGeosManager.unmount(this.identifier);
unmount(this.identifier);
}, duration * 1000);
} else {
this.doPendingTimers();
ARGeosManager.unmount(this.identifier);
unmount(this.identifier);
}
}
/**
4 changes: 2 additions & 2 deletions components/lib/processMaterial.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { processColor } from 'react-native';
import { isString, mapValues, set } from 'lodash';
import { isObject, isString, mapValues, set } from 'lodash';

// https://developer.apple.com/documentation/scenekit/scnmaterial
const propsWithMaps = ['normal', 'diffuse', 'displacement', 'specular'];
@@ -15,7 +15,7 @@ export default function processMaterial(material) {
(prop, key) =>
propsWithMaps.includes(key)
? {
...prop,
...(isObject(prop) ? prop : {}),
color: processColor(
// allow for setting a diffuse colorstring { diffuse: 'colorstring'}
key === 'diffuse' && isString(prop) ? prop : prop.color,
7 changes: 5 additions & 2 deletions hocs/withProjectedPosition.js
Original file line number Diff line number Diff line change
@@ -11,7 +11,7 @@ const roundPoint = ({ x, y, z }, precision) => ({
z: round(z, precision),
});

export default ({ throttleMs = 33 } = {}) => C =>
export default ({ throttleMs = 33, overwritePosition = {} } = {}) => C =>
withAnimationFrame(
class extends Component {
projectionRunning = true;
@@ -91,7 +91,10 @@ export default ({ throttleMs = 33 } = {}) => C =>
render() {
return (
<C
positionProjected={this.state.positionProjected}
positionProjected={{
...this.state.positionProjected,
...overwritePosition,
}}
projectionResult={this.state.projectionResult}
{...this.props}
/>
19 changes: 0 additions & 19 deletions ios/Plane.h

This file was deleted.

81 changes: 0 additions & 81 deletions ios/Plane.m

This file was deleted.

4 changes: 4 additions & 0 deletions ios/RCTARKit.h
Original file line number Diff line number Diff line change
@@ -37,16 +37,20 @@ typedef void (^RCTARKitReject)(NSString *code, NSString *message, NSError *error
@property (nonatomic, assign) BOOL planeDetection;
@property (nonatomic, assign) BOOL lightEstimationEnabled;
@property (nonatomic, assign) BOOL autoenablesDefaultLighting;
@property (nonatomic, assign) NSDictionary* origin;
@property (nonatomic, assign) ARWorldAlignment worldAlignment;

@property (nonatomic, copy) RCTBubblingEventBlock onPlaneDetected;
@property (nonatomic, copy) RCTBubblingEventBlock onPlaneRemoved;
@property (nonatomic, copy) RCTBubblingEventBlock onFeaturesDetected;
@property (nonatomic, copy) RCTBubblingEventBlock onLightEstimation;
@property (nonatomic, copy) RCTBubblingEventBlock onPlaneUpdate;
@property (nonatomic, copy) RCTBubblingEventBlock onTrackingState;
@property (nonatomic, copy) RCTBubblingEventBlock onTapOnPlaneUsingExtent;
@property (nonatomic, copy) RCTBubblingEventBlock onTapOnPlaneNoExtent;
@property (nonatomic, copy) RCTBubblingEventBlock onRotationGesture;
@property (nonatomic, copy) RCTBubblingEventBlock onEvent;
@property (nonatomic, copy) RCTBubblingEventBlock onARKitError;


@property NSMutableDictionary *planes; // plane detected
176 changes: 97 additions & 79 deletions ios/RCTARKit.m
Original file line number Diff line number Diff line change
@@ -7,8 +7,7 @@
//

#import "RCTARKit.h"
#import "Plane.h"

#import "RCTConvert+ARKit.h"

@import CoreLocation;

@@ -62,6 +61,9 @@ - (instancetype)initWithARView:(ARSCNView *)arView {
UITapGestureRecognizer *tapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleTapFrom:)];
tapGestureRecognizer.numberOfTapsRequired = 1;
[self.arView addGestureRecognizer:tapGestureRecognizer];

UIRotationGestureRecognizer *rotationGestureRecognizer = [[UIRotationGestureRecognizer alloc] initWithTarget:self action:@selector(handleRotationFrom:)];
[self.arView addGestureRecognizer:rotationGestureRecognizer];

self.touchDelegates = [NSMutableArray array];
self.rendererDelegates = [NSMutableArray array];
@@ -77,7 +79,7 @@ - (instancetype)initWithARView:(ARSCNView *)arView {

arView.scene.rootNode.name = @"root";

self.planes = [NSMutableDictionary new];


// start ARKit
[self addSubview:arView];
@@ -88,6 +90,7 @@ - (instancetype)initWithARView:(ARSCNView *)arView {

- (void)layoutSubviews {
[super layoutSubviews];
//NSLog(@"setting view bounds %@", NSStringFromCGRect(self.bounds));
self.arView.frame = self.bounds;
}

@@ -99,6 +102,15 @@ - (void)resume {
[self.session runWithConfiguration:self.configuration];
}

- (void)session:(ARSession *)session didFailWithError:(NSError *)error {
if(self.onARKitError) {
self.onARKitError(RCTJSErrorFromNSError(error));
} else {
NSLog(@"Initializing ARKIT failed with Error: %@ %@", error, [error userInfo]);

}

}
- (void)reset {
if (ARWorldTrackingConfiguration.isSupported) {
[self.session runWithConfiguration:self.configuration options:ARSessionRunOptionRemoveExistingAnchors | ARSessionRunOptionResetTracking];
@@ -150,6 +162,29 @@ - (void)setPlaneDetection:(BOOL)planeDetection {
[self resume];
}

-(NSDictionary*)origin {
return @{
@"position": vectorToJson(self.nodeManager.localOrigin.position)
};
}

-(void)setOrigin:(NSDictionary*)json {

if(json[@"transition"]) {
NSDictionary * transition =json[@"transition"];
if(transition[@"duration"]) {
[SCNTransaction setAnimationDuration:[transition[@"duration"] floatValue]];
} else {
[SCNTransaction setAnimationDuration:0.0];
}

} else {
[SCNTransaction setAnimationDuration:0.0];
}
SCNVector3 position = [RCTConvert SCNVector3:json[@"position"]];
[self.nodeManager.localOrigin setPosition:position];
}

- (BOOL)lightEstimationEnabled {
ARConfiguration *configuration = self.configuration;
return configuration.lightEstimationEnabled;
@@ -195,6 +230,9 @@ - (NSDictionary *)readCameraPosition {
static NSDictionary * vectorToJson(const SCNVector3 v) {
return @{ @"x": @(v.x), @"y": @(v.y), @"z": @(v.z) };
}
static NSDictionary * vector_float3ToJson(const simd_float3 v) {
return @{ @"x": @(v.x), @"y": @(v.y), @"z": @(v.z) };
}
static NSDictionary * vector4ToJson(const SCNVector4 v) {
return @{ @"x": @(v.x), @"y": @(v.y), @"z": @(v.z), @"w": @(v.w) };
}
@@ -216,7 +254,8 @@ - (NSDictionary *)readCamera {
}

- (SCNVector3)projectPoint:(SCNVector3)point {
return [self.arView projectPoint:point];
return [self.arView projectPoint:[self.nodeManager getAbsolutePositionToOrigin:point]];

}


@@ -302,7 +341,7 @@ - (UIImage *)cropImage:(UIImage *)imageToCrop toRect:(CGRect)rect
UIGraphicsBeginImageContext(src.size);

CGContextRef context = UIGraphicsGetCurrentContext();
[src drawAtPoint:CGPointMake(0, 0)];
[src drawAtPoint:CGPointMake(0, 0)];
if (orientation == UIImageOrientationRight) {
CGContextRotateCTM (context, radians(90));
} else if (orientation == UIImageOrientationLeft) {
@@ -313,7 +352,7 @@ - (UIImage *)cropImage:(UIImage *)imageToCrop toRect:(CGRect)rect
CGContextRotateCTM (context, radians(90));
}



UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
@@ -382,27 +421,6 @@ - (void)hitTestPlane:(const CGPoint)tapPoint types:(ARHitTestResultType)types re
resolve([self getPlaneHitResult:tapPoint types:types]);
}

static NSMutableArray * mapHitResults(NSArray<ARHitTestResult *> *results) {
NSMutableArray *resultsMapped = [NSMutableArray arrayWithCapacity:[results count]];
[results enumerateObjectsUsingBlock:^(id obj, NSUInteger index, BOOL *stop) {
ARHitTestResult *result = (ARHitTestResult *) obj;

[resultsMapped addObject:(@{
@"distance": @(result.distance),
@"id": result.anchor.identifier.UUIDString,
@"point": @{
@"x": @(result.worldTransform.columns[3].x),
@"y": @(result.worldTransform.columns[3].y),
@"z": @(result.worldTransform.columns[3].z)
}

} )];
}];
return resultsMapped;
}





static NSDictionary * getPlaneHitResult(NSMutableArray *resultsMapped, const CGPoint tapPoint) {
@@ -418,7 +436,7 @@ - (void)hitTestPlane:(const CGPoint)tapPoint types:(ARHitTestResultType)types re

- (NSDictionary *)getPlaneHitResult:(const CGPoint)tapPoint types:(ARHitTestResultType)types; {
NSArray<ARHitTestResult *> *results = [self.arView hitTest:tapPoint types:types];
NSMutableArray * resultsMapped = mapHitResults(results);
NSMutableArray * resultsMapped = [self.nodeManager mapHitResults:results];
NSDictionary *planeHitResult = getPlaneHitResult(resultsMapped, tapPoint);
return planeHitResult;
}
@@ -440,6 +458,23 @@ - (void)handleTapFrom: (UITapGestureRecognizer *)recognizer {
}
}

- (void)handleRotationFrom: (UIRotationGestureRecognizer *)recognizer {

if( recognizer.state == UIGestureRecognizerStateBegan ||
recognizer.state == UIGestureRecognizerStateChanged ||
recognizer.state == UIGestureRecognizerStateEnded) {

if(self.onRotationGesture) {
NSDictionary *rotationGesture = @{
@"rotation": @(recognizer.rotation),
@"velocity": @(recognizer.velocity)
};

self.onRotationGesture(rotationGesture);
}
}
}



#pragma mark - ARSCNViewDelegate
@@ -461,73 +496,56 @@ - (void)renderer:(id <SCNSceneRenderer>)renderer didRenderScene:(SCNScene *)scen
}


- (NSDictionary *)makePlaneDetectionResult:(SCNNode *)node planeAnchor:(ARPlaneAnchor *)planeAnchor {

return @{
@"id": planeAnchor.identifier.UUIDString,
@"alignment": @(planeAnchor.alignment),
@"eulerAngles":vectorToJson(node.eulerAngles),
@"position": vectorToJson([self.nodeManager getRelativePositionToOrigin:node.position]),
@"positionAbsolute": vectorToJson(node.position),
@"center": vector_float3ToJson(planeAnchor.center),
@"extent": vector_float3ToJson(planeAnchor.extent),
// node is deprecated
@"node": vectorToJson(node.position)
};
}

- (void)renderer:(id <SCNSceneRenderer>)renderer willUpdateNode:(SCNNode *)node forAnchor:(ARAnchor *)anchor {
}


- (void)renderer:(id <SCNSceneRenderer>)renderer didAddNode:(SCNNode *)node forAnchor:(ARAnchor *)anchor {
if (![anchor isKindOfClass:[ARPlaneAnchor class]]) {
return;
}

SCNNode *parent = [node parentNode];
NSLog(@"plane detected");
// NSLog(@"%f %f %f", parent.position.x, parent.position.y, parent.position.z);


ARPlaneAnchor *planeAnchor = (ARPlaneAnchor *)anchor;

// NSLog(@"%@", @{
// @"id": planeAnchor.identifier.UUIDString,
// @"alignment": @(planeAnchor.alignment),
// @"node": @{ @"x": @(node.position.x), @"y": @(node.position.y), @"z": @(node.position.z) },
// @"center": @{ @"x": @(planeAnchor.center.x), @"y": @(planeAnchor.center.y), @"z": @(planeAnchor.center.z) },
// @"extent": @{ @"x": @(planeAnchor.extent.x), @"y": @(planeAnchor.extent.y), @"z": @(planeAnchor.extent.z) },
// @"camera": @{ @"x": @(self.cameraOrigin.position.x), @"y": @(self.cameraOrigin.position.y), @"z": @(self.cameraOrigin.position.z) }
// });

if (self.onPlaneDetected) {
self.onPlaneDetected(@{
@"id": planeAnchor.identifier.UUIDString,
@"alignment": @(planeAnchor.alignment),
@"node": @{ @"x": @(node.position.x), @"y": @(node.position.y), @"z": @(node.position.z) },
@"center": @{ @"x": @(planeAnchor.center.x), @"y": @(planeAnchor.center.y), @"z": @(planeAnchor.center.z) },
@"extent": @{ @"x": @(planeAnchor.extent.x), @"y": @(planeAnchor.extent.y), @"z": @(planeAnchor.extent.z) },
// @"camera": @{ @"x": @(self.cameraOrigin.position.x), @"y": @(self.cameraOrigin.position.y), @"z": @(self.cameraOrigin.position.z) }
});
self.onPlaneDetected([self makePlaneDetectionResult:node planeAnchor:planeAnchor]);
}

//Plane *plane = [[Plane alloc] initWithAnchor: (ARPlaneAnchor *)anchor isHidden: NO];
//[self.planes setObject:plane forKey:anchor.identifier];
//[node addChildNode:plane];
}

- (void)renderer:(id <SCNSceneRenderer>)renderer willUpdateNode:(SCNNode *)node forAnchor:(ARAnchor *)anchor {
}

- (void)renderer:(id <SCNSceneRenderer>)renderer didUpdateNode:(SCNNode *)node forAnchor:(ARAnchor *)anchor {
ARPlaneAnchor *planeAnchor = (ARPlaneAnchor *)anchor;

if (self.onPlaneUpdate) {
self.onPlaneUpdate(@{
@"id": planeAnchor.identifier.UUIDString,
@"alignment": @(planeAnchor.alignment),
@"node": @{ @"x": @(node.position.x), @"y": @(node.position.y), @"z": @(node.position.z) },
@"center": @{ @"x": @(planeAnchor.center.x), @"y": @(planeAnchor.center.y), @"z": @(planeAnchor.center.z) },
@"extent": @{ @"x": @(planeAnchor.extent.x), @"y": @(planeAnchor.extent.y), @"z": @(planeAnchor.extent.z) },
// @"camera": @{ @"x": @(self.cameraOrigin.position.x), @"y": @(self.cameraOrigin.position.y), @"z": @(self.cameraOrigin.position.z) }
});
}

Plane *plane = [self.planes objectForKey:anchor.identifier];
if (plane == nil) {
return;
self.onPlaneUpdate([self makePlaneDetectionResult:node planeAnchor:planeAnchor]);
}

[plane update:(ARPlaneAnchor *)anchor];
}

- (void)renderer:(id <SCNSceneRenderer>)renderer didRemoveNode:(SCNNode *)node forAnchor:(ARAnchor *)anchor {
// [self.planes removeObjectForKey:anchor.identifier];
- (void)renderer:(id<SCNSceneRenderer>)renderer didRemoveNode:(SCNNode *)node forAnchor:(ARAnchor *)anchor {
ARPlaneAnchor *planeAnchor = (ARPlaneAnchor *)anchor;
if (self.onPlaneRemoved) {
self.onPlaneRemoved([self makePlaneDetectionResult:node planeAnchor:planeAnchor]);
}
}




#pragma mark - ARSessionDelegate

- (ARFrame * _Nullable)currentFrame {
@@ -541,14 +559,14 @@ - (NSDictionary *)getCurrentLightEstimation {
- (NSMutableArray *)getCurrentDetectedFeaturePoints {
NSMutableArray * featurePoints = [NSMutableArray array];
for (int i = 0; i < [self currentFrame].rawFeaturePoints.count; i++) {
vector_float3 point = [self currentFrame].rawFeaturePoints.points[i];

vector_float3 positionV = [self currentFrame].rawFeaturePoints.points[i];
SCNVector3 position = [self.nodeManager getRelativePositionToOrigin:SCNVector3Make(positionV[0],positionV[1],positionV[2])];
NSString * pointId = [NSString stringWithFormat:@"featurepoint_%lld",[self currentFrame].rawFeaturePoints.identifiers[i]];

[featurePoints addObject:@{
@"x": @(point[0]),
@"y": @(point[1]),
@"z": @(point[2]),
@"x": @(position.x),
@"y": @(position.y),
@"z": @(position.z),
@"id":pointId,
}];

@@ -563,7 +581,7 @@ - (void)session:(ARSession *)session didUpdateFrame:(ARFrame *)frame {
}
}
if (self.onFeaturesDetected) {
NSMutableArray * featurePoints = [self getCurrentDetectedFeaturePoints];
NSArray * featurePoints = [self getCurrentDetectedFeaturePoints];
dispatch_async(dispatch_get_main_queue(), ^{


6 changes: 0 additions & 6 deletions ios/RCTARKit.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
@@ -12,7 +12,6 @@
1021FE1D1F3EDB9B000E7339 /* ARTextManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 1021FE191F3EDB90000E7339 /* ARTextManager.m */; };
105F124E1F7C0719006D4BA3 /* RCTConvert+ARKit.m in Sources */ = {isa = PBXBuildFile; fileRef = 105F124D1F7C0718006D4BA3 /* RCTConvert+ARKit.m */; };
10DCBC4B1F7CE836008C89E7 /* ARGeosManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 10DCBC4A1F7CE836008C89E7 /* ARGeosManager.m */; };
10E553291F1391350059B7EC /* Plane.m in Sources */ = {isa = PBXBuildFile; fileRef = 10E553281F1391350059B7EC /* Plane.m */; };
10ED47A71F38BC01004DF043 /* DeviceMotion.m in Sources */ = {isa = PBXBuildFile; fileRef = 10ED47A61F38BC00004DF043 /* DeviceMotion.m */; };
10FEF6141F774C9000EC21AE /* RCTARKitIO.m in Sources */ = {isa = PBXBuildFile; fileRef = 10FEF6101F774C8F00EC21AE /* RCTARKitIO.m */; };
10FEF6151F774C9000EC21AE /* RCTARKitNodes.m in Sources */ = {isa = PBXBuildFile; fileRef = 10FEF6121F774C9000EC21AE /* RCTARKitNodes.m */; };
@@ -52,8 +51,6 @@
105F124D1F7C0718006D4BA3 /* RCTConvert+ARKit.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "RCTConvert+ARKit.m"; sourceTree = "<group>"; };
10DCBC491F7CE836008C89E7 /* ARGeosManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ARGeosManager.h; sourceTree = "<group>"; };
10DCBC4A1F7CE836008C89E7 /* ARGeosManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ARGeosManager.m; sourceTree = "<group>"; };
10E553271F1391350059B7EC /* Plane.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Plane.h; sourceTree = "<group>"; };
10E553281F1391350059B7EC /* Plane.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = Plane.m; sourceTree = "<group>"; };
10ED47A51F38BC00004DF043 /* DeviceMotion.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DeviceMotion.h; sourceTree = "<group>"; };
10ED47A61F38BC00004DF043 /* DeviceMotion.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DeviceMotion.m; sourceTree = "<group>"; };
10FEF60F1F774C8F00EC21AE /* RCTARKitNodes.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RCTARKitNodes.h; sourceTree = "<group>"; };
@@ -109,8 +106,6 @@
106999E21F3EC2FB00032829 /* components */,
10ED47A51F38BC00004DF043 /* DeviceMotion.h */,
10ED47A61F38BC00004DF043 /* DeviceMotion.m */,
10E553271F1391350059B7EC /* Plane.h */,
10E553281F1391350059B7EC /* Plane.m */,
B3E7B5881CC2AC0600A0062D /* RCTARKit.h */,
B3E7B5891CC2AC0600A0062D /* RCTARKit.m */,
10FEF6131F774C9000EC21AE /* RCTARKitDelegate.h */,
@@ -206,7 +201,6 @@
10DCBC4B1F7CE836008C89E7 /* ARGeosManager.m in Sources */,
10FEF6151F774C9000EC21AE /* RCTARKitNodes.m in Sources */,
B1990B221FCEEBD60001AE2F /* color-grabber.m in Sources */,
10E553291F1391350059B7EC /* Plane.m in Sources */,
1021FE1C1F3EDB98000E7339 /* ARModelManager.m in Sources */,
1021FE1D1F3EDB9B000E7339 /* ARTextManager.m in Sources */,
105F124E1F7C0719006D4BA3 /* RCTConvert+ARKit.m in Sources */,
6 changes: 6 additions & 0 deletions ios/RCTARKitManager.m
Original file line number Diff line number Diff line change
@@ -17,6 +17,8 @@ @implementation RCTARKitManager

RCT_EXPORT_MODULE()



- (UIView *)view {
return [ARKit sharedInstance];
}
@@ -95,18 +97,22 @@ - (NSDictionary *)constantsToExport

RCT_EXPORT_VIEW_PROPERTY(debug, BOOL)
RCT_EXPORT_VIEW_PROPERTY(planeDetection, BOOL)
RCT_EXPORT_VIEW_PROPERTY(origin, NSDictionary *)
RCT_EXPORT_VIEW_PROPERTY(lightEstimationEnabled, BOOL)
RCT_EXPORT_VIEW_PROPERTY(autoenablesDefaultLighting, BOOL)
RCT_EXPORT_VIEW_PROPERTY(worldAlignment, NSInteger)

RCT_EXPORT_VIEW_PROPERTY(onPlaneDetected, RCTBubblingEventBlock)
RCT_EXPORT_VIEW_PROPERTY(onPlaneUpdate, RCTBubblingEventBlock)
RCT_EXPORT_VIEW_PROPERTY(onPlaneRemoved, RCTBubblingEventBlock)
RCT_EXPORT_VIEW_PROPERTY(onTrackingState, RCTBubblingEventBlock)
RCT_EXPORT_VIEW_PROPERTY(onFeaturesDetected, RCTBubblingEventBlock)
RCT_EXPORT_VIEW_PROPERTY(onLightEstimation, RCTBubblingEventBlock)
RCT_EXPORT_VIEW_PROPERTY(onTapOnPlaneUsingExtent, RCTBubblingEventBlock)
RCT_EXPORT_VIEW_PROPERTY(onTapOnPlaneNoExtent, RCTBubblingEventBlock)
RCT_EXPORT_VIEW_PROPERTY(onRotationGesture, RCTBubblingEventBlock)
RCT_EXPORT_VIEW_PROPERTY(onEvent, RCTBubblingEventBlock)
RCT_EXPORT_VIEW_PROPERTY(onARKitError, RCTBubblingEventBlock)

RCT_EXPORT_METHOD(pause:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) {
[[ARKit sharedInstance] pause];
4 changes: 3 additions & 1 deletion ios/RCTARKitNodes.h
Original file line number Diff line number Diff line change
@@ -43,5 +43,7 @@ typedef NS_OPTIONS(NSUInteger, RFReferenceFrame) {
- (void)removeNodeForKey:(NSString *)key;
- (NSDictionary *)getSceneObjectsHitResult:(const CGPoint)tapPoint;
- (void)clear;

- (NSMutableArray *) mapHitResults:(NSArray<ARHitTestResult *> *)results;
- (SCNVector3)getAbsolutePositionToOrigin:(const SCNVector3)positionRelative;
- (SCNVector3)getRelativePositionToOrigin:(const SCNVector3)positionAbsolute;
@end
92 changes: 81 additions & 11 deletions ios/RCTARKitNodes.m
Original file line number Diff line number Diff line change
@@ -61,9 +61,10 @@ - (instancetype)init {
}

- (void)setArView:(ARSCNView *)arView {
NSLog(@"setArView");
//NSLog(@"setArView");
_arView = arView;
self.rootNode = arView.scene.rootNode;

self.rootNode.name = @"root";

[self.rootNode addChildNode:self.localOrigin];
@@ -77,6 +78,7 @@ - (void)setArView:(ARSCNView *)arView {
add a node to scene in a reference frame
*/
- (void)addNodeToScene:(SCNNode *)node inReferenceFrame:(NSString *)referenceFrame {
[self registerNode:node forKey:node.name];
if (!referenceFrame) {
referenceFrame = @"Local"; // default to Local frame
}
@@ -107,24 +109,23 @@ - (void)clear {
- (void)addNodeToLocalFrame:(SCNNode *)node {
node.referenceFrame = RFReferenceFrameLocal;

[self.localOrigin addChildNode:node];
//NSLog(@"[RCTARKitNodes] Add node %@ to Local frame at (%.2f, %.2f, %.2f)", node.name, node.position.x, node.position.y, node.position.z);

[self registerNode:node forKey:node.name];
[self.localOrigin addChildNode:node];
}

- (void)addNodeToCameraFrame:(SCNNode *)node {
node.referenceFrame = RFReferenceFrameCamera;
//NSLog(@"[RCTARKitNodes] Add node %@ to Camera frame at (%.2f, %.2f, %.2f)", node.name, node.position.x, node.position.y, node.position.z);
[self registerNode:node forKey:node.name];

[self.cameraOrigin addChildNode:node];
}

- (void)addNodeToFrontOfCameraFrame:(SCNNode *)node {
node.referenceFrame = RFReferenceFrameFrontOfCamera;

//NSLog(@"[RCTARKitNodes] Add node %@ to FrontOfCamera frame at (%.2f, %.2f, %.2f)", node.name, node.position.x, node.position.y, node.position.z);
[self registerNode:node forKey:node.name];

[self.frontOfCamera addChildNode:node];
}

@@ -152,6 +153,25 @@ - (NSDictionary *)getSceneObjectsHitResult:(const CGPoint)tapPoint {
}


static SCNVector3 toSCNVector3(simd_float4 float4) {
SCNVector3 positionAbsolute = SCNVector3Make(float4.x, float4.y, float4.z);
return positionAbsolute;
}

- (SCNVector3)getRelativePositionToOrigin:(const SCNVector3)positionAbsolute {
SCNVector3 originPosition = self.localOrigin.position;
SCNVector3 position = SCNVector3Make(positionAbsolute.x - originPosition.x, positionAbsolute.y- originPosition.y, positionAbsolute.z - originPosition.z);
return position;
}

- (SCNVector3)getAbsolutePositionToOrigin:(const SCNVector3)positionRelative {
SCNVector3 originPosition = self.localOrigin.position;
SCNVector3 position = SCNVector3Make(positionRelative.x + originPosition.x, positionRelative.y+ originPosition.y, positionRelative.z + originPosition.z);
return position;
}



- (NSMutableArray *) mapHitResultsWithSceneResults: (NSArray<SCNHitTestResult *> *)results {

NSMutableArray *resultsMapped = [NSMutableArray arrayWithCapacity:[results count]];
@@ -163,17 +183,29 @@ - (NSMutableArray *) mapHitResultsWithSceneResults: (NSArray<SCNHitTestResult *>
NSString * nodeId = [self findNodeId:node];
if(nodeId) {

SCNVector3 point = result.worldCoordinates;
SCNVector3 positionAbsolute = result.worldCoordinates;
SCNVector3 position = [self getRelativePositionToOrigin:positionAbsolute];
SCNVector3 normal = result.worldNormal;
float distance = [self getCameraDistanceToPoint:point];
float distance = [self getCameraDistanceToPoint:positionAbsolute];

[resultsMapped addObject:(@{
@"id": nodeId,
@"distance": @(distance),
@"positionAbsolute": @{
@"x": @(positionAbsolute.x),
@"y": @(positionAbsolute.y),
@"z": @(positionAbsolute.z)
},
@"position": @{
@"x": @(position.x),
@"y": @(position.y),
@"z": @(position.z)
},
// point is deprecated
@"point": @{
@"x": @(point.x),
@"y": @(point.y),
@"z": @(point.z)
@"x": @(position.x),
@"y": @(position.y),
@"z": @(position.z)
},
@"normal": @{
@"x": @(normal.x),
@@ -192,6 +224,41 @@ - (NSMutableArray *) mapHitResultsWithSceneResults: (NSArray<SCNHitTestResult *>





- (NSMutableArray *) mapHitResults:(NSArray<ARHitTestResult *> *)results {
NSMutableArray *resultsMapped = [NSMutableArray arrayWithCapacity:[results count]];

[results enumerateObjectsUsingBlock:^(id obj, NSUInteger index, BOOL *stop) {
ARHitTestResult *result = (ARHitTestResult *) obj;

SCNVector3 positionAbsolute = toSCNVector3(result.worldTransform.columns[3]);
SCNVector3 position = [self getRelativePositionToOrigin:positionAbsolute];
[resultsMapped addObject:(@{
@"distance": @(result.distance),
@"id": result.anchor.identifier.UUIDString,
@"positionAbsolute": @{
@"x": @(positionAbsolute.x),
@"y": @(positionAbsolute.y),
@"z": @(positionAbsolute.z)
},
@"position": @{
@"x": @(position.x),
@"y": @(position.y),
@"z": @(position.z)
},
// deprecated
@"point": @{
@"x": @(position.x),
@"y": @(position.y),
@"z": @(position.z)
}

} )];
}];
return resultsMapped;
}

#pragma mark - node register
- (void)registerNode:(SCNNode *)node forKey:(NSString *)key {
[self removeNodeForKey:key];
@@ -220,7 +287,7 @@ - (SCNNode *)nodeForKey:(NSString *)key {
}

- (void)removeNodeForKey:(NSString *)key {

//NSLog(@"removing node: %@ ", key);
SCNNode *node = [self.nodes objectForKey:key];
if (node) {
[self.nodes removeObjectForKey:key];
@@ -236,6 +303,7 @@ - (void)removeNodeForKey:(NSString *)key {

- (void)updateNode:(NSString *)nodeId properties:(NSDictionary *) properties {
SCNNode *node = [self.nodes objectForKey:nodeId];
//NSLog(@"updating node %@ :%@", nodeId, properties);
if(node) {
[RCTConvert setNodeProperties:node properties:properties];
if(node.geometry && properties[@"shape"]) {
@@ -251,6 +319,8 @@ - (void)updateNode:(NSString *)nodeId properties:(NSDictionary *) properties {
}


} else {
NSLog(@"WARNING: node does not exists: %@. This means that the node has not been mounted yet, so native calls got out of order", nodeId);
}

}
1 change: 1 addition & 0 deletions ios/RCTConvert+ARKit.m
Original file line number Diff line number Diff line change
@@ -413,6 +413,7 @@ + (void)setNodeProperties:(SCNNode *)node properties:(id)json {
}

if (json[@"scale"]) {

CGFloat scale = [json[@"scale"] floatValue];
node.scale = SCNVector3Make(scale, scale, scale);

4 changes: 2 additions & 2 deletions ios/color-grabber/color-grabber.m
Original file line number Diff line number Diff line change
@@ -95,7 +95,7 @@ - (NSArray *)getColorsFromImage:(UIImage *)image options:(NSDictionary *)options
int red = CLAMP([reds[r] intValue], 0, 255);
int green = CLAMP([greens[g] intValue], 0, 255);
int blue = CLAMP([blues[b] intValue], 0, 255);
NSLog([NSString stringWithFormat:@"r: %d, g: %d, b: %d",red,green,blue]);
//NSLog([NSString stringWithFormat:@"r: %d, g: %d, b: %d",red,green,blue]);

NSString * rgbString = [NSString stringWithFormat:@"%i,%i,%i",red,green,blue];
[flexibleColours addObject:rgbString];
@@ -167,7 +167,7 @@ - (NSArray *)getColorsFromImage:(UIImage *)image options:(NSDictionary *)options
for (NSString * key in temp){
float count = [temp[key] floatValue];
float percentage = count/totalCount;
NSLog(@"%f",percentage);
//NSLog(@"%f",percentage);
NSArray * rgb = [key componentsSeparatedByString:@","];
float r = [rgb[0] floatValue];
float g = [rgb[1] floatValue];
62 changes: 38 additions & 24 deletions ios/components/ARModelManager.m
Original file line number Diff line number Diff line change
@@ -16,38 +16,52 @@ @implementation ARModelManager
RCT_EXPORT_MODULE()

RCT_EXPORT_METHOD(mount:(NSDictionary *)property node:(SCNNode *)node frame:(NSString *)frame) {
NSDictionary *model = property[@"model"];
CGFloat scale = [model[@"scale"] floatValue];

NSString *path = [NSString stringWithFormat:@"%@", model[@"file"]];
SCNNode *modelNode = [[RCTARKitIO sharedInstance] loadModel:path nodeName:model[@"node"] withAnimation:YES];
modelNode.scale = SCNVector3Make(scale, scale, scale);
NSDictionary* materialJson;
if(property[@"material"] ) {
materialJson = property[@"material"];
}


if(materialJson) {
for(id idx in node.geometry.materials) {
SCNMaterial* material = (SCNMaterial* )idx;
[RCTConvert setMaterialProperties:material properties:materialJson];
// we need to mount first, otherwise, if the loading of the model is slow, it will be registered too late
[[RCTARKitNodes sharedInstance] addNodeToScene:node inReferenceFrame:frame];
// we need to do the model loading in its own queue, otherwise it can block, so that react-to-native-calls get out of order
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSDictionary *model = property[@"model"];
CGFloat scale = [model[@"scale"] floatValue];

NSString *path = [NSString stringWithFormat:@"%@", model[@"file"]];
//NSLog(@"mounting model: %@ %@", node.name, path);
SCNNode *modelNode = [[RCTARKitIO sharedInstance] loadModel:path nodeName:model[@"node"] withAnimation:YES];
modelNode.scale = SCNVector3Make(scale, scale, scale);
// transfer some properties to modeNode like "castsShadow"
modelNode.castsShadow = node.castsShadow;


NSDictionary* materialJson;
if(property[@"material"] ) {
materialJson = property[@"material"];
}
}

for(id idx in modelNode.childNodes) {
// iterate over all childnodes and apply shaders
SCNNode* node = (SCNNode *)idx;


if(materialJson) {
for(id idx in node.geometry.materials) {
for(id idx in modelNode.geometry.materials) {
SCNMaterial* material = (SCNMaterial* )idx;
[RCTConvert setMaterialProperties:material properties:materialJson];
}
}

for(id idx in modelNode.childNodes) {
// iterate over all childnodes and apply shaders

SCNNode* childNode = (SCNNode *)idx;
childNode.castsShadow = node.castsShadow;
if(materialJson) {
for(id idx in childNode.geometry.materials) {
SCNMaterial* material = (SCNMaterial* )idx;
[RCTConvert setMaterialProperties:material properties:materialJson];
}
}

}
[node addChildNode:modelNode];
});


}
[node addChildNode:modelNode];
[[RCTARKitNodes sharedInstance] addNodeToScene:node inReferenceFrame:frame];
}

@end
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -4,7 +4,7 @@
"type": "git",
"url": "https://github.com/HippoAR/react-native-arkit.git"
},
"version": "0.6.1-beta.1",
"version": "0.7.1",
"description": "React Native binding for iOS ARKit",
"author": "Zehao Li <qft.gtr@gmail.com>",
"license": "MIT",