diff --git a/ARKit.js b/ARKit.js index 01bafec..4f3799a 100644 --- a/ARKit.js +++ b/ARKit.js @@ -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, }; diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..c3b367f --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,8 @@ + + +### 2017-12-21 + +- added `onPlaneRemoved` +- added `eulerAngles` to detected planes +- added `onARKitError` to ARKit +- added `origin` property to ARKit diff --git a/README.md b/README.md index cdcfe47..80ec038 100644 --- a/README.md +++ b/README.md @@ -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 > ReactNativeARKit); | `planeDetection` | `Boolean` | `false` | ARKit plane detection. | `lightEstimationEnabled` | `Boolean` | `false` | ARKit light estimation. | `worldAlignment` | `Enumeration`
One of: `ARKit.ARWorldAlignment.Gravity`, `ARKit.ARWorldAlignment.GravityAndHeading`, `ARKit.ARWorldAlignment.Camera` (documentation [here](https://developer.apple.com/documentation/arkit/arworldalignment)) | `ARKit.ARWorldAlignment.Gravity` | **ARWorldAlignmentGravity**
The coordinate system's y-axis is parallel to gravity, and its origin is the initial position of the device. **ARWorldAlignmentGravityAndHeading**
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**
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. | diff --git a/README.md.orig b/README.md.orig new file mode 100644 index 0000000..7dfdae9 --- /dev/null +++ b/README.md.orig @@ -0,0 +1,534 @@ +# react-native-arkit + +[![npm version](https://img.shields.io/npm/v/react-native-arkit.svg?style=flat)](https://www.npmjs.com/package/react-native-arkit) +[![npm downloads](https://img.shields.io/npm/dm/react-native-arkit.svg?style=flat)](https://www.npmjs.com/package/react-native-arkit) + +React Native binding for iOS ARKit. + +**Tutorial**: [How to make an ARKit app in 5 minutes using React Native](https://medium.com/@HippoAR/how-to-make-your-own-arkit-app-in-5-minutes-using-react-native-9d7ce109a4c2) + +**Sample Project**: https://github.com/HippoAR/ReactNativeARKit + +**Note**: ARKit is only supported by devices with A9 or later processors (iPhone 6s/7/SE/8/X, iPad 2017/Pro) on iOS 11. You also need Xcode 9 to build the project. + +There is a Slack group that anyone can join for help / support / general questions. + +[**Join Slack**](https://join.slack.com/t/react-native-ar/shared_invite/enQtMjUzMzg3MjM0MTQ5LWU3Nzg2YjI4MGRjMTM1ZDBlNmIwYTE4YmM0M2U0NmY2YjBiYzQ4YzlkODExMTA0NDkwMzFhYWY4ZDE2M2Q4NGY) + +## Getting started + +`$ yarn add react-native-arkit` + +make sure to use the latest version of yarn (>=1.x.x) + +(npm does not work properly at the moment. See https://github.com/HippoAR/react-native-arkit/issues/103) + + +### Mostly automatic installation + +`$ react-native link react-native-arkit` + +! Currently automatic installation does not work as PocketSVG is missing. Follow the manual installation + +### Manual installation + + +#### iOS + +1. In XCode, in the project navigator, right click `Libraries` ➜ `Add Files to [your project's name]` +2. Go to `node_modules` ➜ add `react-native-arkit/RCTARKit.xcodeproj` and `_PocketSVG/_PocketSVG.xcodeproj` +3. In XCode, in the project navigator, select your project. Add `libRCTARKit.a` `and PocketSVG.framework` to your project's `Build Phases` ➜ `Link Binary With Libraries` +4. In Tab `General` ➜ `Embedded Binaries` ➜ `+` ➜ Add `PocketSVG.framework ios` +5. Run your project (`Cmd+R`)< + + +## Usage + +A simple sample React Native ARKit App + +```javascript +// index.ios.js + +import React, { Component } from 'react'; +import { AppRegistry, View } from 'react-native'; +import { ARKit } from 'react-native-arkit'; + +export default class ReactNativeARKit extends Component { + render() { + return ( + + 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 + > + + + + + + + + + + + + + + + + `, + pathFlatness: 0.1, + // it's also possible to specify a chamfer profile: + chamferRadius: 5, + chamferProfilePathSvg: ` + + `, + extrusion: 10, + }} + /> + + + ); + } +} + +AppRegistry.registerComponent('ReactNativeARKit', () => ReactNativeARKit); + +``` + + + +### Components + +#### `` + +##### Props + +| Prop | Type | Default | Note | +|---|---|---|---| +| `debug` | `Boolean` | `false` | Debug mode will show the 3D axis and feature points detected. +| `planeDetection` | `Boolean` | `false` | ARKit plane detection. +| `lightEstimationEnabled` | `Boolean` | `false` | ARKit light estimation. +| `worldAlignment` | `Enumeration`
One of: `ARKit.ARWorldAlignment.Gravity`, `ARKit.ARWorldAlignment.GravityAndHeading`, `ARKit.ARWorldAlignment.Camera` (documentation [here](https://developer.apple.com/documentation/arkit/arworldalignment)) | `ARKit.ARWorldAlignment.Gravity` | **ARWorldAlignmentGravity**
The coordinate system's y-axis is parallel to gravity, and its origin is the initial position of the device. **ARWorldAlignmentGravityAndHeading**
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**
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 +|---|---|---| +| `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()` +<<<<<<< HEAD +| `onPlaneUpdate` | `{ id, center, extent }` | 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. +======= +| `onPlaneDetected` | `Plane` | When a plane is first detected. +| `onPlaneUpdate` | `Plane` | When a detected plane is updated +| `onPlaneRemoved` | `Plane` | When a detected plane is updated + +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 | + + + +>>>>>>> 75fdf78e4630c7ed64cd148b1dc6ac653c689542 + +##### Static methods + +All methods return a promise with the result. + +| Method Name | Arguments | Notes +|---|---|---| +| `snapshot` | | | Take a screenshot (will save to Photo Library) | +| `snapshotCamera` | | Take a screenshot without 3d models (will save to Photo Library) | +| `getCameraPosition` | | Get the current position of the `ARCamera` | +| `getCurrentLightEstimation` | | Get current light estimation `{ ambientColorTemperature, ambientIntensity}` | +| `getCurrentDetectedFeaturePoints` | | Get current detected feature points (in last current frame) (array) | +| `focusScene` | | Sets the scene's position/rotation to where it was when first rendered (but now relative to your device's current position/rotation) | +| `hitTestPlanes` | point, type | check if a plane has ben hit by point (`{x,y}`) with detection type (any of `ARKit.ARHitTestResultType`). See https://developer.apple.com/documentation/arkit/arhittestresulttype?language=objc for further information | +| `hitTestSceneObjects` | point | check if a scene object has ben hit by point (`{x,y}`) | + + +#### 3D objects + +##### General props + +Most 3d object have these common properties + +| Prop | Type | Description | +|---|---|---| +| `position` | `{ x, y, z }` | The object's position (y is up) | +| `scale` | Number | The scale of the object. Defaults to 1 | +| `eulerAngles` | `{ x, y, z }` | The rotation in eulerAngles | +| `rotation` | TODO | see scenkit documentation | +| `orientation` | TODO | see scenkit documentation | +| `shape` | depends on object | the shape of the object (will probably renamed to geometry in future versions) +| `material` | `{ diffuse, metalness, roughness, lightingModel, shaders }` | the material of the object | +| `transition` | `{duration: 1}` | Some property changes can be animated like in css transitions. Currently you can specify the duration (in seconds). | +| `renderingOrder` | Number | Order in which object is rendered. Usefull to place elements "behind" others, although they are nearer. | +| `categoryBitMask` | Number / bitmask | control which lights affect this object | +| `castsShadow` | `boolean` | whether this object casts hadows | + +*New experimental feature:* + +You can switch properties on mount or onmount by specifying `propsOnMount` and `propsOnUnmount`. +E.g. you can scale an object on unmount: + +``` + +``` + +#### Material properties + +Most objects take a material property with these sub-props: + +| Prop | Type | Description | +|---|---|---| +| `diffuse` | { `path`, `color`, `intensity` } | [diffuse](https://developer.apple.com/documentation/scenekit/scnmaterial/1462589-diffuse?language=objc) +| `specular` | { `path`, `color`, `intensity` } | [specular](https://developer.apple.com/documentation/scenekit/scnmaterial/1462516-specular?language=objc) +| `displacement` | { `path`, `color`, `intensity` } | [displacement](https://developer.apple.com/documentation/scenekit/scnmaterial/2867516-displacement?language=objc) +| `normal` | { `path`, `color`, `intensity` } | [normal](https://developer.apple.com/documentation/scenekit/scnmaterial/1462542-normal) +| `metalness` | number | metalness of the object | +| `roughness` | number | roughness of the object | +| `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) | +| `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. | + + + + + +#### [``](https://developer.apple.com/documentation/scenekit/scnbox) + + +| Prop | Type | +|---|---| +| `shape` | `{ width, height, length, chamfer }` | + +And any common object property (position, material, etc.) + +#### [``](https://developer.apple.com/documentation/scenekit/scnsphere) + + +| Prop | Type | +|---|---| +| `shape` | `{ radius }` | + + + +#### [``](https://developer.apple.com/documentation/scenekit/scncylinder) + + +| Prop | Type | +|---|---| +| `shape` | `{ radius, height }` | + +#### [``](https://developer.apple.com/documentation/scenekit/scncone) + + +| Prop | Type | +|---|---| +| `shape` | `{ topR, bottomR, height }` | + +#### [``](https://developer.apple.com/documentation/scenekit/scnpyramid) + + +| Prop | Type | +|---|---| +| `shape` | `{ width, height, length }` | + +#### [``](https://developer.apple.com/documentation/scenekit/scntube) + + +| Prop | Type | +|---|---| +| `shape` | `{ innerR, outerR, height }` | + +#### [``](https://developer.apple.com/documentation/scenekit/scntorus) + + +| Prop | Type | +|---|---| +| `shape` | `{ ringR, pipeR }` | + +#### [``](https://developer.apple.com/documentation/scenekit/scncapsule) + + +| Prop | Type | +|---|---| +| `shape` | `{ capR, height }` | + +#### [``](https://developer.apple.com/documentation/scenekit/scnplane) + + +| Prop | Type | +|---|---| +| `shape` | `{ width, height }` | + +Notice: planes are veritcally aligned. If you want a horizontal plane, rotate it around the x-axis. + +*Example*: + +This is a horizontal plane that only receives shadows, but is invisible otherwise: + +``` + +``` + + +#### [``](https://developer.apple.com/documentation/scenekit/scntext) + +| Prop | Type | +|---|---| +| `text` | `String` | +| `font` | `{ name, size, depth, chamfer }` | + + + +#### `` + +SceneKit only supports `.scn` and `.dae` formats. + + +| Prop | Type | +|---|---| +| `model` | `{ file, node, scale, alpha }` | + +Objects currently don't take material property. + +#### `` + +Creates a extruded shape by an svg path. +See https://github.com/HippoAR/react-native-arkit/pull/89 for details + +| Prop | Type | +|---|---| +| `shape` | `{ pathSvg, extrusion, pathFlatness, chamferRadius, chamferProfilePathSvg, chamferProfilePathFlatness }` | + + + +#### [``](https://developer.apple.com/documentation/scenekit/scnlight) + +Place lights on the scene! + +You might set `autoenablesDefaultLighting={false}` on The `` component to disable default lighting. You can use `lightEstimationEnabled` and `ARKit.getCurrentLightEstimation()` to find values for intensity and temperature. This produces much nicer results then `autoenablesDefaultLighting`. + + + +| Prop | Type | Description | +|---|---|---| +| `position` | `{ x, y, z }` | | +| `eulerAngles` | `{ x, y, z }` | | +| `type` | any of `ARKit.LightType` | see [here for details](https://developer.apple.com/documentation/scenekit/scnlight.lighttype) | +| `color` | `string` | the color of the light | +| `temperature` | `Number` | The color temperature of the light | +| `intensity` | `Number` | The light intensity | +| `lightCategoryBitMask` | `Number`/`bitmask` | control which objects are lit by this light | +| `castsShadow` | `boolean` | whether to cast shadows on object | +| `shadowMode`| `ARKit.ShadowMode.* | Define the shadowmode. Set to `ARKit.ShadowMode.Deferred` to cast shadows on invisible objects (like an invisible floor plane) | + + + +Most properties described here are also supported: https://developer.apple.com/documentation/scenekit/scnlight + +This feature is new. If you experience any problem, please report an issue! + + +### HOCs (higher order components) + +#### withProjectedPosition() + +this hoc allows you to create 3D components where the position is always relative to the same point on the screen/camera, but sticks to a plane or object. + +Think about a 3D cursor that can be moved across your table or a 3D cursor on a wall. + +You can use the hoc like this: + +``` +const Cursor3D = withProjectedPosition()(({positionProjected, projectionResult}) => { + if(!projectionResult) { + // nothing has been hit, don't render it + return null; + } + return ( + + ) +}) + +``` + +It's recommended that you specify a transition duration (0.1s works nice), as the position gets updated rapidly, but slightly throttled. + +Now you can use your 3D cursor like this: + +##### Attach to a given detected horizontal plane + +Given you have detected a plane with onPlaneDetected, you can make the cursor stick to that plane: + +``` + + +``` + +If you don't have the id, but want to place the cursor on a certain plane (e.g. the first or last one), pass a function for plane. This function will get all hit-results and you can return the one you need: + +``` + results.length > 0 ? results[0] : null + }} +/> + +``` + +You can also add a property `onProjectedPosition` to your cursor which will be called with the hit result on every frame + +It uses https://developer.apple.com/documentation/arkit/arframe/2875718-hittest with some default options. Please file an issue or send a PR if you need more control over the options here! + +##### Attach to a given 3D object + +You can attach the cursor on a 3D object, e.g. a non-horizontal-plane or similar: + +Given there is some 3D object on your scene with `id="my-nodeId"` + +``` + +``` + +Like with planes, you can select the node with a function. + +E.gl you have several "walls" with ids "wall_1", "wall_2", etc. + +``` + results.find(r => r.id.startsWith('wall_')), + }} +/> +``` + + +It uses https://developer.apple.com/documentation/scenekit/scnscenerenderer/1522929-hittest with some default options. Please file an issue or send a PR if you need more control over the options here! + + + +## Contributing + +If you find a bug or would like to request a new feature, just [open an issue](https://github.com/HippoAR/react-native-arkit/issues/new). Your contributions are always welcome! Submit a pull request and see [`CONTRIBUTING.md`](CONTRIBUTING.md) for guidelines. diff --git a/components/lib/createArComponent.js b/components/lib/createArComponent.js index e10e440..d7e6537 100644 --- a/components/lib/createArComponent.js +++ b/components/lib/createArComponent.js @@ -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); } } /** diff --git a/components/lib/processMaterial.js b/components/lib/processMaterial.js index 055a43f..4e4b46f 100644 --- a/components/lib/processMaterial.js +++ b/components/lib/processMaterial.js @@ -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, diff --git a/hocs/withProjectedPosition.js b/hocs/withProjectedPosition.js index ffc8679..09ac9ec 100644 --- a/hocs/withProjectedPosition.js +++ b/hocs/withProjectedPosition.js @@ -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 ( diff --git a/ios/Plane.h b/ios/Plane.h deleted file mode 100644 index ac349ca..0000000 --- a/ios/Plane.h +++ /dev/null @@ -1,19 +0,0 @@ -// -// Plane.h -// RCTARKit -// -// Created by Zehao Li on 7/10/17. -// Copyright © 2017 HippoAR. All rights reserved. -// - -#import -#import - -@interface Plane : SCNNode -- (instancetype)initWithAnchor:(ARPlaneAnchor *)anchor isHidden:(BOOL)hidden; -- (void)update:(ARPlaneAnchor *)anchor; -- (void)setTextureScale; -- (void)hide; -@property (nonatomic, retain) ARPlaneAnchor *anchor; -@property (nonatomic, retain) SCNBox *planeGeometry; -@end diff --git a/ios/Plane.m b/ios/Plane.m deleted file mode 100644 index 511c273..0000000 --- a/ios/Plane.m +++ /dev/null @@ -1,81 +0,0 @@ -// -// Plane.m -// RCTARKit -// -// Created by Zehao Li on 7/10/17. -// Copyright © 2017 HippoAR. All rights reserved. -// - -#import "Plane.h" - -@implementation Plane - -- (instancetype)initWithAnchor:(ARPlaneAnchor *)anchor isHidden:(BOOL)hidden { - self = [super init]; - - self.anchor = anchor; - float width = anchor.extent.x; - float length = anchor.extent.z; - - float planeHeight = 0.01; - - self.planeGeometry = [SCNBox boxWithWidth:width height:planeHeight length:length chamferRadius:0]; - - SCNMaterial *material = [SCNMaterial new]; - UIImage *img = [UIImage imageNamed:@"tron_grid.png"]; - - material.diffuse.contents = img; - - SCNMaterial *transparentMaterial = [SCNMaterial new]; - transparentMaterial.diffuse.contents = [UIColor colorWithWhite:1.0 alpha:0.0]; - - if (hidden) { - self.planeGeometry.materials = @[transparentMaterial, transparentMaterial, transparentMaterial, transparentMaterial, transparentMaterial, transparentMaterial]; - } else { - self.planeGeometry.materials = @[transparentMaterial, transparentMaterial, transparentMaterial, transparentMaterial, material, transparentMaterial]; - } - - SCNNode *planeNode = [SCNNode nodeWithGeometry:self.planeGeometry]; - - planeNode.position = SCNVector3Make(0, -planeHeight / 2, 0); - - planeNode.physicsBody = [SCNPhysicsBody - bodyWithType:SCNPhysicsBodyTypeKinematic - shape: [SCNPhysicsShape shapeWithGeometry:self.planeGeometry options:nil]]; - - [self setTextureScale]; - [self addChildNode:planeNode]; - return self; -} - -- (void)update:(ARPlaneAnchor *)anchor { - self.planeGeometry.width = anchor.extent.x; - self.planeGeometry.length = anchor.extent.z; - - self.position = SCNVector3Make(anchor.center.x, 0, anchor.center.z); - - SCNNode *node = [self.childNodes firstObject]; - //self.physicsBody = nil; - node.physicsBody = [SCNPhysicsBody - bodyWithType:SCNPhysicsBodyTypeKinematic - shape: [SCNPhysicsShape shapeWithGeometry:self.planeGeometry options:nil]]; - [self setTextureScale]; -} - -- (void)setTextureScale { - CGFloat width = self.planeGeometry.width; - CGFloat height = self.planeGeometry.length; - - SCNMaterial *material = self.planeGeometry.materials[4]; - material.diffuse.contentsTransform = SCNMatrix4MakeScale(width, height, 1); - material.diffuse.wrapS = SCNWrapModeRepeat; - material.diffuse.wrapT = SCNWrapModeRepeat; -} - -- (void)hide { - SCNMaterial *transparentMaterial = [SCNMaterial new]; - transparentMaterial.diffuse.contents = [UIColor colorWithWhite:1.0 alpha:0.0]; - self.planeGeometry.materials = @[transparentMaterial, transparentMaterial, transparentMaterial, transparentMaterial, transparentMaterial, transparentMaterial]; -} - -@end diff --git a/ios/RCTARKit.h b/ios/RCTARKit.h index 793a85f..c1561d4 100644 --- a/ios/RCTARKit.h +++ b/ios/RCTARKit.h @@ -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 diff --git a/ios/RCTARKit.m b/ios/RCTARKit.m index 4bc4db7..e09d5e7 100644 --- a/ios/RCTARKit.m +++ b/ios/RCTARKit.m @@ -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 *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 *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 )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 )renderer willUpdateNode:(SCNNode *)node forAnchor:(ARAnchor *)anchor { +} + + - (void)renderer:(id )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 )renderer willUpdateNode:(SCNNode *)node forAnchor:(ARAnchor *)anchor { } - (void)renderer:(id )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 )renderer didRemoveNode:(SCNNode *)node forAnchor:(ARAnchor *)anchor { - // [self.planes removeObjectForKey:anchor.identifier]; +- (void)renderer:(id)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(), ^{ diff --git a/ios/RCTARKit.xcodeproj/project.pbxproj b/ios/RCTARKit.xcodeproj/project.pbxproj index 29e9ce4..ab4e7e7 100644 --- a/ios/RCTARKit.xcodeproj/project.pbxproj +++ b/ios/RCTARKit.xcodeproj/project.pbxproj @@ -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 = ""; }; 10DCBC491F7CE836008C89E7 /* ARGeosManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ARGeosManager.h; sourceTree = ""; }; 10DCBC4A1F7CE836008C89E7 /* ARGeosManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ARGeosManager.m; sourceTree = ""; }; - 10E553271F1391350059B7EC /* Plane.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Plane.h; sourceTree = ""; }; - 10E553281F1391350059B7EC /* Plane.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = Plane.m; sourceTree = ""; }; 10ED47A51F38BC00004DF043 /* DeviceMotion.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DeviceMotion.h; sourceTree = ""; }; 10ED47A61F38BC00004DF043 /* DeviceMotion.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DeviceMotion.m; sourceTree = ""; }; 10FEF60F1F774C8F00EC21AE /* RCTARKitNodes.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RCTARKitNodes.h; sourceTree = ""; }; @@ -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 */, diff --git a/ios/RCTARKitManager.m b/ios/RCTARKitManager.m index 6d7ab83..e5ba792 100644 --- a/ios/RCTARKitManager.m +++ b/ios/RCTARKitManager.m @@ -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]; diff --git a/ios/RCTARKitNodes.h b/ios/RCTARKitNodes.h index c3136bc..ee8572a 100644 --- a/ios/RCTARKitNodes.h +++ b/ios/RCTARKitNodes.h @@ -43,5 +43,7 @@ typedef NS_OPTIONS(NSUInteger, RFReferenceFrame) { - (void)removeNodeForKey:(NSString *)key; - (NSDictionary *)getSceneObjectsHitResult:(const CGPoint)tapPoint; - (void)clear; - +- (NSMutableArray *) mapHitResults:(NSArray *)results; +- (SCNVector3)getAbsolutePositionToOrigin:(const SCNVector3)positionRelative; +- (SCNVector3)getRelativePositionToOrigin:(const SCNVector3)positionAbsolute; @end diff --git a/ios/RCTARKitNodes.m b/ios/RCTARKitNodes.m index a7f4a98..0e29367 100644 --- a/ios/RCTARKitNodes.m +++ b/ios/RCTARKitNodes.m @@ -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,16 +109,15 @@ - (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]; } @@ -124,7 +125,7 @@ - (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 *)results { NSMutableArray *resultsMapped = [NSMutableArray arrayWithCapacity:[results count]]; @@ -163,17 +183,29 @@ - (NSMutableArray *) mapHitResultsWithSceneResults: (NSArray 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 + + +- (NSMutableArray *) mapHitResults:(NSArray *)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); } } diff --git a/ios/RCTConvert+ARKit.m b/ios/RCTConvert+ARKit.m index 5e27d00..d5de35a 100644 --- a/ios/RCTConvert+ARKit.m +++ b/ios/RCTConvert+ARKit.m @@ -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); diff --git a/ios/color-grabber/color-grabber.m b/ios/color-grabber/color-grabber.m index 34b06fc..3c33453 100644 --- a/ios/color-grabber/color-grabber.m +++ b/ios/color-grabber/color-grabber.m @@ -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]; diff --git a/ios/components/ARModelManager.m b/ios/components/ARModelManager.m index 3882cc9..430ad03 100644 --- a/ios/components/ARModelManager.m +++ b/ios/components/ARModelManager.m @@ -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 diff --git a/package.json b/package.json index 6d322f1..f8b0b94 100644 --- a/package.json +++ b/package.json @@ -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 ", "license": "MIT",