diff --git a/ARKit.js b/ARKit.js
index 6d85c689..01bafec5 100644
--- a/ARKit.js
+++ b/ARKit.js
@@ -5,9 +5,6 @@
// Copyright © 2017 HippoAR. All rights reserved.
//
-import PropTypes from 'prop-types';
-import React, { Component } from 'react';
-
import {
StyleSheet,
View,
@@ -15,7 +12,10 @@ import {
NativeModules,
requireNativeComponent,
} from 'react-native';
+import PropTypes from 'prop-types';
+import React, { Component } from 'react';
+import { pickColors, pickColorsFromFile } from './lib/pickColors';
import generateId from './components/lib/generateId';
const ARKitManager = NativeModules.ARKitManager;
@@ -36,9 +36,7 @@ class ARKit extends Component {
reason: 0,
floor: null,
};
- componentWillMount() {
- ARKitManager.clearScene();
- }
+
componentDidMount() {
ARKitManager.resume();
}
@@ -172,12 +170,19 @@ ARKit.exportModel = presetId => {
return ARKitManager.exportModel(property).then(result => ({ ...result, id }));
};
+ARKit.pickColors = pickColors;
+ARKit.pickColorsFromFile = pickColorsFromFile;
ARKit.propTypes = {
debug: PropTypes.bool,
planeDetection: PropTypes.bool,
- lightEstimation: PropTypes.bool,
+ lightEstimationEnabled: PropTypes.bool,
+ autoenablesDefaultLighting: PropTypes.bool,
+ worldAlignment: PropTypes.number,
onPlaneDetected: PropTypes.func,
onFeaturesDetected: PropTypes.func,
+ // onLightEstimation is called rapidly, better poll with
+ // ARKit.getCurrentLightEstimation()
+ onLightEstimation: PropTypes.func,
onPlaneUpdate: PropTypes.func,
onTrackingState: PropTypes.func,
onTapOnPlaneUsingExtent: PropTypes.func,
@@ -187,4 +192,4 @@ ARKit.propTypes = {
const RCTARKit = requireNativeComponent('RCTARKit', ARKit);
-module.exports = ARKit;
+export default ARKit;
diff --git a/DeviceMotion.js b/DeviceMotion.js
index 94a86d25..aa8041fd 100644
--- a/DeviceMotion.js
+++ b/DeviceMotion.js
@@ -16,4 +16,4 @@ const DeviceMotion = {
},
};
-module.exports = DeviceMotion;
+export default DeviceMotion;
diff --git a/README.md b/README.md
index 726bfaa1..cdcfe47d 100644
--- a/README.md
+++ b/README.md
@@ -17,21 +17,29 @@ There is a Slack group that anyone can join for help / support / general questio
## Getting started
-`$ npm install react-native-arkit --save`
+`$ 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` ➜ `react-native-arkit` and add `RCTARKit.xcodeproj`
-3. In XCode, in the project navigator, select your project. Add `libRCTARKit.a` to your project's `Build Phases` ➜ `Link Binary With Libraries`
-4. Run your project (`Cmd+R`)<
+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
@@ -53,7 +61,12 @@ export default class ReactNativeARKit extends Component {
style={{ flex: 1 }}
debug
planeDetection
- lightEstimation
+ // enable light estimation (defaults to true)
+ lightEstimationEnabled
+ // get the current lightEstimation (if enabled)
+ // it fires rapidly, so better poll it from outside with
+ // ARKit.getCurrentLightEstimation()
+ onLightEstimation={e => console.log(e.nativeEvent)}
onPlaneDetected={console.log} // event listener for plane detection
onPlaneUpdate={console.log} // event listener for plane update
>
@@ -98,13 +111,47 @@ export default class ReactNativeARKit extends Component {
position={{ x: 0.2, y: 0.6, z: 0 }}
font={{ size: 0.15, depth: 0.05 }}
/>
+
+
+
+
+ `,
+ pathFlatness: 0.1,
+ // it's also possible to specify a chamfer profile:
+ chamferRadius: 5,
+ chamferProfilePathSvg: `
+
+ `,
+ extrusion: 10,
+ }}
+ />
);
@@ -127,152 +174,338 @@ AppRegistry.registerComponent('ReactNativeARKit', () => ReactNativeARKit);
|---|---|---|---|
| `debug` | `Boolean` | `false` | Debug mode will show the 3D axis and feature points detected.
| `planeDetection` | `Boolean` | `false` | ARKit plane detection.
-| `lightEstimation` | `Boolean` | `false` | ARKit light estimation.
+| `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.|
##### Events
| Event Name | Returns | Notes
|---|---|---|
| `onPlaneDetected` | `{ id, center, extent }` | When a plane is first detected.
+| `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
##### 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)
-##### Props
| Prop | Type |
|---|---|
-| `position` | `{ x, y, z }` |
-| `eulerAngles` | `{ x, y, z }` |
| `shape` | `{ width, height, length, chamfer }` |
-| `material` | `{ diffuse, metalness, roughness, lightingModel }` |
+
+And any common object property (position, material, etc.)
#### [``](https://developer.apple.com/documentation/scenekit/scnsphere)
-##### Props
| Prop | Type |
|---|---|
-| `position` | `{ x, y, z }` |
-| `eulerAngles` | `{ x, y, z }` |
| `shape` | `{ radius }` |
-| `material` | `{ diffuse, metalness, roughness, lightingModel }` |
+
+
#### [``](https://developer.apple.com/documentation/scenekit/scncylinder)
-##### Props
| Prop | Type |
|---|---|
-| `position` | `{ x, y, z }` |
-| `eulerAngles` | `{ x, y, z }` |
| `shape` | `{ radius, height }` |
-| `material` | `{ diffuse, metalness, roughness, lightingModel }` |
#### [``](https://developer.apple.com/documentation/scenekit/scncone)
-##### Props
| Prop | Type |
|---|---|
-| `position` | `{ x, y, z }` |
-| `eulerAngles` | `{ x, y, z }` |
| `shape` | `{ topR, bottomR, height }` |
-| `material` | `{ diffuse, metalness, roughness, lightingModel }` |
#### [``](https://developer.apple.com/documentation/scenekit/scnpyramid)
-##### Props
| Prop | Type |
|---|---|
-| `position` | `{ x, y, z }` |
-| `eulerAngles` | `{ x, y, z }` |
| `shape` | `{ width, height, length }` |
-| `material` | `{ diffuse, metalness, roughness, lightingModel }` |
#### [``](https://developer.apple.com/documentation/scenekit/scntube)
-##### Props
| Prop | Type |
|---|---|
-| `position` | `{ x, y, z }` |
-| `eulerAngles` | `{ x, y, z }` |
| `shape` | `{ innerR, outerR, height }` |
-| `material` | `{ diffuse, metalness, roughness, lightingModel }` |
#### [``](https://developer.apple.com/documentation/scenekit/scntorus)
-##### Props
| Prop | Type |
|---|---|
-| `position` | `{ x, y, z }` |
-| `eulerAngles` | `{ x, y, z }` |
| `shape` | `{ ringR, pipeR }` |
-| `material` | `{ diffuse, metalness, roughness, lightingModel }` |
#### [``](https://developer.apple.com/documentation/scenekit/scncapsule)
-##### Props
| Prop | Type |
|---|---|
-| `position` | `{ x, y, z }` |
-| `eulerAngles` | `{ x, y, z }` |
| `shape` | `{ capR, height }` |
-| `material` | `{ diffuse, metalness, roughness, lightingModel }` |
#### [``](https://developer.apple.com/documentation/scenekit/scnplane)
-##### Props
| Prop | Type |
|---|---|
-| `position` | `{ x, y, z }` |
-| `eulerAngles` | `{ x, y, z }` |
-| `shape` | `{ width, length }` |
-| `material` | `{ diffuse, metalness, roughness, lightingModel }` |
+| `shape` | `{ width, height }` |
-#### [``](https://developer.apple.com/documentation/scenekit/scntext)
+Notice: planes are veritcally aligned. If you want a horizontal plane, rotate it around the x-axis.
-##### Props
+*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` |
-| `position` | `{ x, y, z }` |
-| `eulerAngles` | `{ x, y, z }` |
| `font` | `{ name, size, depth, chamfer }` |
-| `material` | `{ diffuse, metalness, roughness, lightingModel }` |
+
#### ``
SceneKit only supports `.scn` and `.dae` formats.
-##### Props
| Prop | Type |
|---|---|
-| `position` | `{ x, y, z }` |
-| `eulerAngles` | `{ x, y, z }` |
| `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
diff --git a/components/ARBox.js b/components/ARBox.js
index a673f0c9..b53350ce 100644
--- a/components/ARBox.js
+++ b/components/ARBox.js
@@ -7,6 +7,7 @@
import PropTypes from 'prop-types';
+import { material } from './lib/propTypes';
import createArComponent from './lib/createArComponent';
const ARBox = createArComponent('addBox', {
@@ -16,6 +17,7 @@ const ARBox = createArComponent('addBox', {
length: PropTypes.number,
chamfer: PropTypes.number,
}),
+ material,
});
-module.exports = ARBox;
+export default ARBox;
diff --git a/components/ARCapsule.js b/components/ARCapsule.js
index c647189e..b74df957 100644
--- a/components/ARCapsule.js
+++ b/components/ARCapsule.js
@@ -7,6 +7,7 @@
import PropTypes from 'prop-types';
+import { material } from './lib/propTypes';
import createArComponent from './lib/createArComponent';
const ARCapsule = createArComponent('addCapsule', {
@@ -14,6 +15,7 @@ const ARCapsule = createArComponent('addCapsule', {
capR: PropTypes.number,
height: PropTypes.number,
}),
+ material,
});
-module.exports = ARCapsule;
+export default ARCapsule;
diff --git a/components/ARCone.js b/components/ARCone.js
index d446d9c0..01eef2b0 100644
--- a/components/ARCone.js
+++ b/components/ARCone.js
@@ -7,6 +7,7 @@
import PropTypes from 'prop-types';
+import { material } from './lib/propTypes';
import createArComponent from './lib/createArComponent';
const ARCone = createArComponent('addCone', {
@@ -15,6 +16,7 @@ const ARCone = createArComponent('addCone', {
bottomR: PropTypes.number,
height: PropTypes.number,
}),
+ material,
});
-module.exports = ARCone;
+export default ARCone;
diff --git a/components/ARCylinder.js b/components/ARCylinder.js
index b69093de..ccbd1887 100644
--- a/components/ARCylinder.js
+++ b/components/ARCylinder.js
@@ -7,6 +7,7 @@
import PropTypes from 'prop-types';
+import { material } from './lib/propTypes';
import createArComponent from './lib/createArComponent';
const ARCylinder = createArComponent('addCylinder', {
@@ -14,6 +15,7 @@ const ARCylinder = createArComponent('addCylinder', {
radius: PropTypes.number,
height: PropTypes.number,
}),
+ material,
});
-module.exports = ARCylinder;
+export default ARCylinder;
diff --git a/components/ARGroup.js b/components/ARGroup.js
index f9b45999..4371a4a7 100644
--- a/components/ARGroup.js
+++ b/components/ARGroup.js
@@ -7,4 +7,4 @@ const ARGroup = class extends Component {
}
};
-module.exports = ARGroup;
+export default ARGroup;
diff --git a/components/ARImage.js b/components/ARImage.js
deleted file mode 100644
index e69de29b..00000000
diff --git a/components/ARLight.js b/components/ARLight.js
new file mode 100644
index 00000000..0328e0e9
--- /dev/null
+++ b/components/ARLight.js
@@ -0,0 +1,28 @@
+import PropTypes from 'prop-types';
+
+import { categoryBitMask, color, lightType, shadowMode } from './lib/propTypes';
+import createArComponent from './lib/createArComponent';
+
+const ARLight = createArComponent('addLight', {
+ type: lightType,
+ color,
+ temperature: PropTypes.number,
+ intensity: PropTypes.number,
+ attenuationStartDistance: PropTypes.number,
+ attenuationEndDistance: PropTypes.number,
+ spotInnerAngle: PropTypes.number,
+ spotOuterAngle: PropTypes.number,
+ castsShadow: PropTypes.bool,
+ shadowRadius: PropTypes.number,
+ shadowColor: color,
+ // shadowMapSize: PropTypes.number,
+ shadowSampleCount: PropTypes.number,
+ shadowMode,
+ shadowBias: PropTypes.number,
+ orthographicScale: PropTypes.number,
+ zFar: PropTypes.number,
+ zNear: PropTypes.number,
+ lightCategoryBitMask: categoryBitMask,
+});
+
+export default ARLight;
diff --git a/components/ARModel.js b/components/ARModel.js
index b018b7d7..2103cc74 100644
--- a/components/ARModel.js
+++ b/components/ARModel.js
@@ -15,7 +15,6 @@ import createArComponent from './lib/createArComponent';
const ARModel = createArComponent(
{
mount: NativeModules.ARModelManager.mount,
- pick: ['model', 'material', 'shape'],
},
{
model: PropTypes.shape({
@@ -26,6 +25,7 @@ const ARModel = createArComponent(
}),
material,
},
+ ['model'],
);
-module.exports = ARModel;
+export default ARModel;
diff --git a/components/ARPlane.js b/components/ARPlane.js
index 04ce9de1..10cbe90e 100644
--- a/components/ARPlane.js
+++ b/components/ARPlane.js
@@ -7,6 +7,7 @@
import PropTypes from 'prop-types';
+import { material } from './lib/propTypes';
import createArComponent from './lib/createArComponent';
const ARPlane = createArComponent('addPlane', {
@@ -18,6 +19,7 @@ const ARPlane = createArComponent('addPlane', {
widthSegmentCount: PropTypes.number,
heightSegmentCount: PropTypes.number,
}),
+ material,
});
-module.exports = ARPlane;
+export default ARPlane;
diff --git a/components/ARPyramid.js b/components/ARPyramid.js
index fc91c8ab..90f47acc 100644
--- a/components/ARPyramid.js
+++ b/components/ARPyramid.js
@@ -7,6 +7,7 @@
import PropTypes from 'prop-types';
+import { material } from './lib/propTypes';
import createArComponent from './lib/createArComponent';
const ARPyramid = createArComponent('addPyramid', {
@@ -15,6 +16,7 @@ const ARPyramid = createArComponent('addPyramid', {
length: PropTypes.number,
height: PropTypes.number,
}),
+ material,
});
-module.exports = ARPyramid;
+export default ARPyramid;
diff --git a/components/ARShape.js b/components/ARShape.js
new file mode 100644
index 00000000..91e951f9
--- /dev/null
+++ b/components/ARShape.js
@@ -0,0 +1,19 @@
+import PropTypes from 'prop-types';
+
+import { chamferMode, material } from './lib/propTypes';
+import createArComponent from './lib/createArComponent';
+
+const ARShape = createArComponent('addShape', {
+ shape: PropTypes.shape({
+ extrusion: PropTypes.number,
+ pathSvg: PropTypes.string,
+ pathFlatness: PropTypes.number,
+ chamferMode,
+ chamferRadius: PropTypes.number,
+ chamferProfilePathSvg: PropTypes.string,
+ chamferProfilePathFlatness: PropTypes.number,
+ }),
+ material,
+});
+
+export default ARShape;
diff --git a/components/ARSphere.js b/components/ARSphere.js
index eaccb10e..31e31269 100644
--- a/components/ARSphere.js
+++ b/components/ARSphere.js
@@ -7,12 +7,14 @@
import PropTypes from 'prop-types';
+import { material } from './lib/propTypes';
import createArComponent from './lib/createArComponent';
const ARSphere = createArComponent('addSphere', {
shape: PropTypes.shape({
radius: PropTypes.number,
}),
+ material,
});
-module.exports = ARSphere;
+export default ARSphere;
diff --git a/components/ARSprite.js b/components/ARSprite.js
index f44022af..bcb8be64 100644
--- a/components/ARSprite.js
+++ b/components/ARSprite.js
@@ -1,5 +1,5 @@
import React, { Component } from 'react';
-import withAnimationFrame from 'react-animation-frame';
+import withAnimationFrame from '@panter/react-animation-frame';
import { NativeModules, Animated } from 'react-native';
@@ -13,7 +13,7 @@ const ARSprite = withAnimationFrame(
super(props);
this.state = {
zIndex: new Animated.Value(),
- pos2D: new Animated.ValueXY(), // inits to zero
+ pos2D: new Animated.ValueXY() // inits to zero
};
}
onAnimationFrame() {
@@ -22,9 +22,9 @@ const ARSprite = withAnimationFrame(
{
x: this.state.pos2D.x,
y: this.state.pos2D.y,
- z: this.state.zIndex,
- },
- ]),
+ z: this.state.zIndex
+ }
+ ])
);
}
@@ -34,18 +34,18 @@ const ARSprite = withAnimationFrame(
style={{
position: 'absolute',
transform: this.state.pos2D.getTranslateTransform(),
- ...this.props.style,
+ ...this.props.style
}}
>
{this.props.children}
);
}
- },
+ }
);
ARSprite.propTypes = {
- position,
+ position
};
-module.exports = ARSprite;
+export default ARSprite;
diff --git a/components/ARText.js b/components/ARText.js
index a856ca16..207d0ab8 100644
--- a/components/ARText.js
+++ b/components/ARText.js
@@ -9,10 +9,11 @@ import PropTypes from 'prop-types';
import { NativeModules } from 'react-native';
+import { material } from './lib/propTypes';
import createArComponent from './lib/createArComponent';
const ARText = createArComponent(
- { mount: NativeModules.ARTextManager.mount, pick: ['text', 'font'] },
+ { mount: NativeModules.ARTextManager.mount, pick: ['id', 'text', 'font'] },
{
text: PropTypes.string,
font: PropTypes.shape({
@@ -20,9 +21,11 @@ const ARText = createArComponent(
// weight: PropTypes.string,
size: PropTypes.number,
depth: PropTypes.number,
- chamfer: PropTypes.number,
+ chamfer: PropTypes.number
}),
+ material
},
+ ['text', 'font']
);
-module.exports = ARText;
+export default ARText;
diff --git a/components/ARTorus.js b/components/ARTorus.js
index 25857193..07c926eb 100644
--- a/components/ARTorus.js
+++ b/components/ARTorus.js
@@ -7,6 +7,7 @@
import PropTypes from 'prop-types';
+import { material } from './lib/propTypes';
import createArComponent from './lib/createArComponent';
const ARTorus = createArComponent('addTorus', {
@@ -14,6 +15,7 @@ const ARTorus = createArComponent('addTorus', {
ringR: PropTypes.number,
pipeR: PropTypes.number,
}),
+ material,
});
-module.exports = ARTorus;
+export default ARTorus;
diff --git a/components/ARTube.js b/components/ARTube.js
index 91a594dc..ba203bb4 100644
--- a/components/ARTube.js
+++ b/components/ARTube.js
@@ -7,6 +7,7 @@
import PropTypes from 'prop-types';
+import { material } from './lib/propTypes';
import createArComponent from './lib/createArComponent';
const ARTube = createArComponent('addTube', {
@@ -15,6 +16,7 @@ const ARTube = createArComponent('addTube', {
outerR: PropTypes.number,
height: PropTypes.number,
}),
+ material,
});
-module.exports = ARTube;
+export default ARTube;
diff --git a/components/lib/createArComponent.js b/components/lib/createArComponent.js
index f48ecd78..e10e4404 100644
--- a/components/lib/createArComponent.js
+++ b/components/lib/createArComponent.js
@@ -1,4 +1,5 @@
import { Component } from 'react';
+import { NativeModules, processColor } from 'react-native';
import PropTypes from 'prop-types';
import filter from 'lodash/filter';
import isDeepEqual from 'fast-deep-equal';
@@ -7,45 +8,79 @@ import keys from 'lodash/keys';
import pick from 'lodash/pick';
import some from 'lodash/some';
-import { NativeModules } from 'react-native';
-
import {
+ castsShadow,
+ categoryBitMask,
eulerAngles,
- material,
+ opacity,
orientation,
position,
+ renderingOrder,
rotation,
+ scale,
transition,
} from './propTypes';
-import { processColorInMaterial } from './parseColor';
+import processMaterial from './processMaterial';
import generateId from './generateId';
-const ARGeosManager = NativeModules.ARGeosManager;
-const NODE_PROPS = [
- 'position',
- 'eulerAngles',
- 'rotation',
- 'orientation',
- 'transition',
-];
-const KEYS_THAT_NEED_REMOUNT = ['material', 'shape', 'model'];
-
-const nodeProps = (id, props) => ({
- id,
- ...pick(props, NODE_PROPS),
-});
-
-export default (mountConfig, propTypes = {}) => {
- const getShapeAndMaterialProps = props =>
- typeof mountConfig === 'string'
- ? {
- shape: props.shape,
- material: processColorInMaterial(props.material),
- }
- : {
- ...pick(props, mountConfig.pick),
- material: processColorInMaterial(props.material),
- };
+const { ARGeosManager } = NativeModules;
+
+const PROP_TYPES_IMMUTABLE = {
+ id: PropTypes.string,
+ frame: PropTypes.string,
+};
+const MOUNT_UNMOUNT_ANIMATION_PROPS = {
+ propsOnMount: PropTypes.any,
+ propsOnUnMount: PropTypes.any,
+};
+const PROP_TYPES_NODE = {
+ position,
+ transition,
+ orientation,
+ eulerAngles,
+ rotation,
+ scale,
+ categoryBitMask,
+ castsShadow,
+ renderingOrder,
+ opacity,
+};
+
+const NODE_PROPS = keys(PROP_TYPES_NODE);
+const IMMUTABLE_PROPS = keys(PROP_TYPES_IMMUTABLE);
+const DEBUG = false;
+const TIMERS = {};
+
+/**
+mountConfig,
+propTypes,
+nonUpdateablePropKeys: if a prop key is in this list,
+the property will be updated on scenekit, instead of beeing remounted.
+
+this excludes at the moment: model, font, text, (???)
+* */
+export default (mountConfig, propTypes = {}, nonUpdateablePropKeys = []) => {
+ const allPropTypes = {
+ ...MOUNT_UNMOUNT_ANIMATION_PROPS,
+ ...PROP_TYPES_IMMUTABLE,
+ ...PROP_TYPES_NODE,
+ ...propTypes,
+ };
+ // any custom props (material, shape, ...)
+ const nonNodePropKeys = keys(propTypes);
+
+ const parseMaterials = props => ({
+ ...props,
+ ...(props.shadowColor
+ ? { shadowColor: processColor(props.shadowColor) }
+ : {}),
+ ...(props.material ? { material: processMaterial(props.material) } : {}),
+ });
+
+ const getNonNodeProps = props => ({
+ ...pick(props, nonNodePropKeys),
+ ...parseMaterials(props),
+ });
const mountFunc =
typeof mountConfig === 'string'
@@ -54,18 +89,36 @@ export default (mountConfig, propTypes = {}) => {
const mount = (id, props) => {
mountFunc(
- getShapeAndMaterialProps(props),
- nodeProps(id, props),
+ getNonNodeProps(props),
+ {
+ id,
+ ...pick(props, NODE_PROPS),
+ },
props.frame,
);
};
const ARComponent = class extends Component {
identifier = null;
-
componentDidMount() {
this.identifier = this.props.id || generateId();
- mount(this.identifier, this.props);
+ const { propsOnMount, ...props } = this.props;
+ if (propsOnMount) {
+ const fullPropsOnMount = { ...props, ...propsOnMount };
+ const {
+ transition: transitionOnMount = { duration: 0 },
+ } = fullPropsOnMount;
+ if (DEBUG) console.log('mount', fullPropsOnMount);
+ this.doPendingTimers();
+ mount(this.identifier, fullPropsOnMount);
+
+ this.delayed(() => {
+ this.props = propsOnMount;
+ this.componentWillUpdate({ ...props, transition: transitionOnMount });
+ }, transitionOnMount.duration * 1000);
+ } else {
+ mount(this.identifier, props);
+ }
}
componentWillUpdate(props) {
@@ -78,38 +131,83 @@ export default (mountConfig, propTypes = {}) => {
return;
}
- if (some(KEYS_THAT_NEED_REMOUNT, k => changedKeys.includes(k))) {
- // remount
- // TODO: we should be able to update
+ if (__DEV__) {
+ const nonAllowedUpdates = filter(changedKeys, k =>
+ IMMUTABLE_PROPS.includes(k),
+ );
+ if (nonAllowedUpdates.length > 0) {
+ throw new Error(
+ `prop can't be updated: '${nonAllowedUpdates.join(', ')}'`,
+ );
+ }
+ }
- mount(this.identifier, props);
+ if (some(changedKeys, k => nonUpdateablePropKeys.includes(k))) {
+ if (DEBUG) console.log('need to remount node because of ', changedKeys);
+ mount(this.identifier, { ...this.props, ...props });
} else {
- // always include transition
- ARGeosManager.update(
- this.identifier,
- pick(props, ['transition', ...changedKeys]),
- );
+ // every property is updateable
+ // send only these changed property to the native part
+
+ const propsToupdate = {
+ // always inclue transition
+ transition: {
+ ...this.props.transition,
+ ...props.transition,
+ },
+ ...parseMaterials(pick(props, changedKeys)),
+ };
+
+ if (DEBUG) console.log('update node', propsToupdate);
+ ARGeosManager.updateNode(this.identifier, propsToupdate);
}
}
componentWillUnmount() {
- ARGeosManager.unmount(this.identifier);
+ const { propsOnUnmount, ...props } = this.props;
+ if (propsOnUnmount) {
+ const fullProps = { ...props, ...propsOnUnmount };
+ const { transition: { duration = 0 } = {} } = fullProps;
+
+ this.componentWillUpdate(fullProps);
+ this.delayed(() => {
+ ARGeosManager.unmount(this.identifier);
+ }, duration * 1000);
+ } else {
+ this.doPendingTimers();
+ ARGeosManager.unmount(this.identifier);
+ }
+ }
+ /**
+ do something delayed, but keep order of events per id
+ * */
+ delayed(callback, duration) {
+ this.doPendingTimers();
+ TIMERS[this.identifier] = {
+ handle: global.setTimeout(() => {
+ callback.call(this);
+ delete TIMERS[this.identifier];
+ }, duration),
+ callback,
+ };
+ }
+
+ doPendingTimers() {
+ if (TIMERS[this.identifier]) {
+ // timer is running, do it now, otherwise we might change order
+ // e.g. it could be that an unmount happens after a remount
+ global.clearTimeout(TIMERS[this.identifier].handle);
+ TIMERS[this.identifier].callback.call(this);
+ delete TIMERS[this.identifier];
+ }
}
render() {
return null;
}
};
- ARComponent.propTypes = {
- frame: PropTypes.string,
- position,
- transition,
- eulerAngles,
- rotation,
- orientation,
- material,
- ...propTypes,
- };
+
+ ARComponent.propTypes = allPropTypes;
return ARComponent;
};
diff --git a/components/lib/generateId.js b/components/lib/generateId.js
index e932303f..50956e66 100644
--- a/components/lib/generateId.js
+++ b/components/lib/generateId.js
@@ -3,6 +3,7 @@
const digits = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
function toSex(num, base) {
+ /* eslint no-param-reassign:0 */
if (base) {
num = parseInt(num, base);
}
diff --git a/components/lib/parseColor.js b/components/lib/parseColor.js
deleted file mode 100644
index 7f67d5aa..00000000
--- a/components/lib/parseColor.js
+++ /dev/null
@@ -1,16 +0,0 @@
-import { processColor } from 'react-native';
-
-export function processColorInMaterial(material) {
- if (!material) {
- return material;
- }
-
- if (!material.diffuse && !material.color) {
- return material;
- }
-
- return {
- ...material,
- diffuse: processColor(material.diffuse || material.color),
- };
-}
diff --git a/components/lib/processMaterial.js b/components/lib/processMaterial.js
new file mode 100644
index 00000000..055a43f4
--- /dev/null
+++ b/components/lib/processMaterial.js
@@ -0,0 +1,26 @@
+import { processColor } from 'react-native';
+import { isString, mapValues, set } from 'lodash';
+
+// https://developer.apple.com/documentation/scenekit/scnmaterial
+const propsWithMaps = ['normal', 'diffuse', 'displacement', 'specular'];
+
+export default function processMaterial(material) {
+ // previously it was possible to set { material: { color:'colorstring'}}... translate this to { material: { diffuse: { color: 'colorstring'}}}
+ if (material.color) {
+ set(material, 'diffuse.color', material.color);
+ }
+
+ return mapValues(
+ material,
+ (prop, key) =>
+ propsWithMaps.includes(key)
+ ? {
+ ...prop,
+ color: processColor(
+ // allow for setting a diffuse colorstring { diffuse: 'colorstring'}
+ key === 'diffuse' && isString(prop) ? prop : prop.color,
+ ),
+ }
+ : prop,
+ );
+}
diff --git a/components/lib/propTypes.js b/components/lib/propTypes.js
index 97a48d45..e8c84202 100644
--- a/components/lib/propTypes.js
+++ b/components/lib/propTypes.js
@@ -1,8 +1,7 @@
+import { NativeModules } from 'react-native';
import { values } from 'lodash';
import PropTypes from 'prop-types';
-import { NativeModules } from 'react-native';
-
const ARKitManager = NativeModules.ARKitManager;
export const position = PropTypes.shape({
@@ -10,6 +9,9 @@ export const position = PropTypes.shape({
y: PropTypes.number,
z: PropTypes.number,
});
+
+export const scale = PropTypes.number;
+export const categoryBitMask = PropTypes.number;
export const transition = PropTypes.shape({
duration: PropTypes.number,
});
@@ -44,13 +46,42 @@ export const lightingModel = PropTypes.oneOf(
values(ARKitManager.LightingModel),
);
+export const castsShadow = PropTypes.bool;
+export const renderingOrder = PropTypes.number;
export const blendMode = PropTypes.oneOf(values(ARKitManager.BlendMode));
+export const chamferMode = PropTypes.oneOf(values(ARKitManager.ChamferMode));
+export const color = PropTypes.string;
+export const fillMode = PropTypes.oneOf(values(ARKitManager.FillMode));
-export const material = PropTypes.shape({
+export const lightType = PropTypes.oneOf(values(ARKitManager.LightType));
+export const shadowMode = PropTypes.oneOf(values(ARKitManager.ShadowMode));
+export const colorBufferWriteMask = PropTypes.oneOf(
+ values(ARKitManager.ColorMask),
+);
+
+export const opacity = PropTypes.number;
+
+export const materialProperty = PropTypes.shape({
+ path: PropTypes.string,
color: PropTypes.string,
+ intensity: PropTypes.number,
+});
+
+export const material = PropTypes.shape({
+ color,
+ normal: materialProperty,
+ specular: materialProperty,
+ displacement: materialProperty,
+ diffuse: PropTypes.oneOfType([PropTypes.string, materialProperty]),
metalness: PropTypes.number,
roughness: PropTypes.number,
blendMode,
lightingModel,
shaders,
+ writesToDepthBuffer: PropTypes.bool,
+ colorBufferWriteMask,
+ doubleSided: PropTypes.bool,
+ litPerPixel: PropTypes.bool,
+ transparency: PropTypes.number,
+ fillMode,
});
diff --git a/hocs/withProjectedPosition.js b/hocs/withProjectedPosition.js
new file mode 100644
index 00000000..ffc86790
--- /dev/null
+++ b/hocs/withProjectedPosition.js
@@ -0,0 +1,102 @@
+import React, { Component } from 'react';
+import withAnimationFrame from '@panter/react-animation-frame';
+import { round, isFunction } from 'lodash';
+import { NativeModules } from 'react-native';
+
+const ARKitManager = NativeModules.ARKitManager;
+
+const roundPoint = ({ x, y, z }, precision) => ({
+ x: round(x, precision),
+ y: round(y, precision),
+ z: round(z, precision),
+});
+
+export default ({ throttleMs = 33 } = {}) => C =>
+ withAnimationFrame(
+ class extends Component {
+ projectionRunning = true;
+ _isMounted = false;
+ constructor(props) {
+ super(props);
+ this.state = {
+ positionProjected: props.position || { x: 0, y: 0, z: 0 },
+ projectionResult: null,
+ };
+ this.handleAnimation(props);
+ }
+ componentWillMount() {
+ this._isMounted = true;
+ }
+ handleAnimation({ projectionEnabled }) {
+ if (projectionEnabled) {
+ if (!this.projectionRunning) {
+ this.projectionRunning = true;
+ this.props.startAnimation();
+ }
+ } else if (this.projectionRunning) {
+ this.projectionRunning = false;
+ this.props.endAnimation();
+ }
+ }
+ componentWillUpdate({ projectionEnabled = true }) {
+ this.handleAnimation({ projectionEnabled });
+ }
+
+ componentWillUnmount() {
+ this._isMounted = false;
+ }
+
+ onResult(result) {
+ if (this._isMounted) {
+ if (result) {
+ this.setState({
+ positionProjected: roundPoint(result.point, 3),
+ projectionResult: result,
+ });
+ } else {
+ this.setState({
+ positionProjected: null,
+ projectionResult: null,
+ });
+ }
+ if (this.props.onProjectedPosition) {
+ this.props.onProjectedPosition(result);
+ }
+ }
+ }
+
+ onAnimationFrame() {
+ const { x, y, plane, node } = this.props.projectPosition || {};
+
+ if (plane) {
+ ARKitManager.hitTestPlanes(
+ { x, y },
+ ARKitManager.ARHitTestResultType.ExistingPlane,
+ ).then(({ results }) => {
+ const result = isFunction(plane)
+ ? plane(results)
+ : results.find(r => r.id === plane);
+ this.onResult(result);
+ });
+ } else if (node) {
+ ARKitManager.hitTestSceneObjects({ x, y }).then(({ results }) => {
+ const result = isFunction(node)
+ ? node(results)
+ : results.find(r => r.id === node);
+ this.onResult(result);
+ });
+ }
+ }
+
+ render() {
+ return (
+
+ );
+ }
+ },
+ throttleMs,
+ );
diff --git a/index.js b/index.js
index d54139eb..0bdb4b4d 100644
--- a/index.js
+++ b/index.js
@@ -5,22 +5,27 @@
// Copyright © 2017 HippoAR. All rights reserved.
//
-import ARKit from './ARKit';
-import DeviceMotion from './DeviceMotion';
-
import ARBox from './components/ARBox';
-import ARSphere from './components/ARSphere';
-import ARCylinder from './components/ARCylinder';
-import ARCone from './components/ARCone';
-import ARPyramid from './components/ARPyramid';
-import ARTube from './components/ARTube';
-import ARTorus from './components/ARTorus';
import ARCapsule from './components/ARCapsule';
-import ARPlane from './components/ARPlane';
-import ARText from './components/ARText';
+import ARCone from './components/ARCone';
+import ARCylinder from './components/ARCylinder';
+import ARGroup from './components/ARGroup';
+import ARKit from './ARKit';
+import ARLight from './components/ARLight';
import ARModel from './components/ARModel';
+import ARPlane from './components/ARPlane';
+import ARPyramid from './components/ARPyramid';
+import ARShape from './components/ARShape';
+import ARSphere from './components/ARSphere';
import ARSprite from './components/ARSprite';
-import ARGroup from './components/ARGroup';
+import ARText from './components/ARText';
+import ARTorus from './components/ARTorus';
+import ARTube from './components/ARTube';
+import DeviceMotion from './DeviceMotion';
+import startup from './startup';
+import withProjectedPosition from './hocs/withProjectedPosition';
+
+import * as colorUtils from './lib/colorUtils';
ARKit.Box = ARBox;
ARKit.Sphere = ARSphere;
@@ -35,12 +40,18 @@ ARKit.Text = ARText;
ARKit.Model = ARModel;
ARKit.Sprite = ARSprite;
ARKit.Group = ARGroup;
+ARKit.Shape = ARShape;
+ARKit.Light = ARLight;
+
+startup();
-module.exports = {
+export {
+ colorUtils,
ARKit,
DeviceMotion,
ARBox,
ARSphere,
+ ARSprite,
ARCylinder,
ARCone,
ARPyramid,
@@ -50,5 +61,7 @@ module.exports = {
ARPlane,
ARText,
ARModel,
+ ARLight,
ARGroup,
+ withProjectedPosition,
};
diff --git a/ios/RCTARKit.h b/ios/RCTARKit.h
index 34033e8b..793a85f4 100644
--- a/ios/RCTARKit.h
+++ b/ios/RCTARKit.h
@@ -35,10 +35,13 @@ typedef void (^RCTARKitReject)(NSString *code, NSString *message, NSError *error
@property (nonatomic, assign) BOOL debug;
@property (nonatomic, assign) BOOL planeDetection;
-@property (nonatomic, assign) BOOL lightEstimation;
+@property (nonatomic, assign) BOOL lightEstimationEnabled;
+@property (nonatomic, assign) BOOL autoenablesDefaultLighting;
+@property (nonatomic, assign) ARWorldAlignment worldAlignment;
@property (nonatomic, copy) RCTBubblingEventBlock onPlaneDetected;
@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;
@@ -58,12 +61,14 @@ typedef void (^RCTARKitReject)(NSString *code, NSString *message, NSError *error
- (void)hitTestSceneObjects:(CGPoint)tapPoint resolve:(RCTARKitResolve) resolve reject:(RCTARKitReject)reject;
- (SCNVector3)projectPoint:(SCNVector3)point;
- (float)getCameraDistanceToPoint:(SCNVector3)point;
-- (UIImage *)getSnaphshot;
-- (UIImage *)getSnaphshotCamera;
+- (UIImage *)getSnapshot:(NSDictionary*)selection;
+- (UIImage *)getSnapshotCamera:(NSDictionary*)selection;
- (void)focusScene;
- (void)clearScene;
- (NSDictionary *)readCameraPosition;
- (NSDictionary *)readCamera;
+- (NSDictionary* )getCurrentLightEstimation;
+- (NSArray * )getCurrentDetectedFeaturePoints;
diff --git a/ios/RCTARKit.m b/ios/RCTARKit.m
index 365c155a..4bc4db72 100644
--- a/ios/RCTARKit.m
+++ b/ios/RCTARKit.m
@@ -74,6 +74,7 @@ - (instancetype)initWithARView:(ARSCNView *)arView {
// configuration(s)
arView.autoenablesDefaultLighting = YES;
+
arView.scene.rootNode.name = @"root";
self.planes = [NSMutableDictionary new];
@@ -135,12 +136,12 @@ - (void)setDebug:(BOOL)debug {
}
- (BOOL)planeDetection {
- ARWorldTrackingConfiguration *configuration = (ARWorldTrackingConfiguration *) self.session.configuration;
+ ARWorldTrackingConfiguration *configuration = (ARWorldTrackingConfiguration *) self.configuration;
return configuration.planeDetection == ARPlaneDetectionHorizontal;
}
- (void)setPlaneDetection:(BOOL)planeDetection {
- ARWorldTrackingConfiguration *configuration = (ARWorldTrackingConfiguration *) self.session.configuration;
+ ARWorldTrackingConfiguration *configuration = (ARWorldTrackingConfiguration *) self.configuration;
if (planeDetection) {
configuration.planeDetection = ARPlaneDetectionHorizontal;
} else {
@@ -149,14 +150,39 @@ - (void)setPlaneDetection:(BOOL)planeDetection {
[self resume];
}
-- (BOOL)lightEstimation {
- ARConfiguration *configuration = self.session.configuration;
+- (BOOL)lightEstimationEnabled {
+ ARConfiguration *configuration = self.configuration;
return configuration.lightEstimationEnabled;
}
-- (void)setLightEstimation:(BOOL)lightEstimation {
- ARConfiguration *configuration = self.session.configuration;
- configuration.lightEstimationEnabled = lightEstimation;
+
+- (void)setLightEstimationEnabled:(BOOL)lightEstimationEnabled {
+ ARConfiguration *configuration = self.configuration;
+ configuration.lightEstimationEnabled = lightEstimationEnabled;
+ [self resume];
+}
+- (void)setAutoenablesDefaultLighting:(BOOL)autoenablesDefaultLighting {
+ self.arView.autoenablesDefaultLighting = autoenablesDefaultLighting;
+}
+
+- (BOOL)autoenablesDefaultLighting {
+ return self.arView.autoenablesDefaultLighting;
+}
+
+- (ARWorldAlignment)worldAlignment {
+ ARConfiguration *configuration = self.configuration;
+ return configuration.worldAlignment;
+}
+
+- (void)setWorldAlignment:(ARWorldAlignment)worldAlignment {
+ ARConfiguration *configuration = self.configuration;
+ if (worldAlignment == ARWorldAlignmentGravityAndHeading) {
+ configuration.worldAlignment = ARWorldAlignmentGravityAndHeading;
+ } else if (worldAlignment == ARWorldAlignmentCamera) {
+ configuration.worldAlignment = ARWorldAlignmentCamera;
+ } else {
+ configuration.worldAlignment = ARWorldAlignmentGravity;
+ }
[self resume];
}
@@ -179,12 +205,13 @@ - (NSDictionary *)readCamera {
SCNVector4 rotation = self.nodeManager.cameraOrigin.rotation;
SCNVector4 orientation = self.nodeManager.cameraOrigin.orientation;
SCNVector3 eulerAngles = self.nodeManager.cameraOrigin.eulerAngles;
+ SCNVector3 direction = self.nodeManager.cameraDirection;
return @{
@"position":vectorToJson(position),
@"rotation":vector4ToJson(rotation),
@"orientation":vector4ToJson(orientation),
@"eulerAngles":vectorToJson(eulerAngles),
-
+ @"direction":vectorToJson(direction),
};
}
@@ -192,21 +219,10 @@ - (SCNVector3)projectPoint:(SCNVector3)point {
return [self.arView projectPoint:point];
}
-static float getDistance(const SCNVector3 pointA, const SCNVector3 pointB) {
- float xd = pointB.x - pointA.x;
- float yd = pointB.y - pointA.y;
- float zd = pointB.z - pointA.z;
- float distance = sqrt(xd * xd + yd * yd + zd * zd);
-
- if (distance < 0){
- return (distance * -1);
- } else {
- return (distance);
- }
-}
+
- (float)getCameraDistanceToPoint:(SCNVector3)point {
- return getDistance(self.nodeManager.cameraOrigin.position, point);
+ return [self.nodeManager getCameraDistanceToPoint:point];
}
@@ -223,7 +239,6 @@ -(ARWorldTrackingConfiguration *)configuration {
_configuration = [ARWorldTrackingConfiguration new];
_configuration.planeDetection = ARPlaneDetectionHorizontal;
-
return _configuration;
}
@@ -237,16 +252,19 @@ - (void)hitTestSceneObjects:(const CGPoint)tapPoint resolve:(RCTARKitResolve)res
}
-- (UIImage *)getSnaphshot {
+- (UIImage *)getSnapshot:(NSDictionary *)selection {
UIImage *image = [self.arView snapshot];
- return image;
+
+
+ return [self cropImage:image toSelection:selection];
+
}
-- (UIImage *)getSnaphsotCamera {
+- (UIImage *)getSnapshotCamera:(NSDictionary *)selection {
CVPixelBufferRef pixelBuffer = self.arView.session.currentFrame.capturedImage;
CIImage *ciImage = [CIImage imageWithCVPixelBuffer:pixelBuffer];
@@ -259,11 +277,102 @@ - (UIImage *)getSnaphsotCamera {
UIImage *image = [UIImage imageWithCGImage:videoImage scale: 1.0 orientation:UIImageOrientationRight];
CGImageRelease(videoImage);
- return image;
+
+ UIImage *cropped = [self cropImage:image toSelection:selection];
+ return cropped;
+
}
+- (UIImage *)cropImage:(UIImage *)imageToCrop toRect:(CGRect)rect
+{
+ //CGRect CropRect = CGRectMake(rect.origin.x, rect.origin.y, rect.size.width, rect.size.height+15);
+
+ CGImageRef imageRef = CGImageCreateWithImageInRect([imageToCrop CGImage], rect);
+ UIImage *cropped = [UIImage imageWithCGImage:imageRef];
+ CGImageRelease(imageRef);
+
+ return cropped;
+}
+
+static inline double radians (double degrees) {return degrees * M_PI/180;}
+UIImage* rotate(UIImage* src, UIImageOrientation orientation)
+{
+ UIGraphicsBeginImageContext(src.size);
+
+ CGContextRef context = UIGraphicsGetCurrentContext();
+ [src drawAtPoint:CGPointMake(0, 0)];
+ if (orientation == UIImageOrientationRight) {
+ CGContextRotateCTM (context, radians(90));
+ } else if (orientation == UIImageOrientationLeft) {
+ CGContextRotateCTM (context, radians(-90));
+ } else if (orientation == UIImageOrientationDown) {
+ // NOTHING
+ } else if (orientation == UIImageOrientationUp) {
+ CGContextRotateCTM (context, radians(90));
+ }
+
+
+
+ UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
+ UIGraphicsEndImageContext();
+ return image;
+}
+- (UIImage *)cropImage:(UIImage *)imageToCrop toSelection:(NSDictionary *)selection
+{
+
+ // selection is in view-coordinate system
+ // where as the image is a camera picture with arbitary size
+ // also, the camera picture is cut of so that it "covers" the self.bounds
+ // if selection is nil, crop to the viewport
+
+ UIImage * image = rotate(imageToCrop, imageToCrop.imageOrientation);
+
+ float arViewWidth = self.bounds.size.width;
+ float arViewHeight = self.bounds.size.height;
+ float imageWidth = image.size.width;
+ float imageHeight = image.size.height;
+
+ float arViewRatio = arViewHeight/arViewWidth;
+ float imageRatio = imageHeight/imageWidth;
+ float imageToArWidth = imageWidth/arViewWidth;
+ float imageToArHeight = imageHeight/arViewHeight;
+
+ float finalHeight;
+ float finalWidth;
+
+
+ if (arViewRatio > imageRatio)
+ {
+ finalHeight = arViewHeight*imageToArHeight;
+ finalWidth = arViewHeight*imageToArHeight /arViewRatio;
+ }
+ else
+ {
+ finalWidth = arViewWidth*imageToArWidth;
+ finalHeight = arViewWidth * imageToArWidth * arViewRatio;
+ }
+
+ float topOffset = (image.size.height - finalHeight)/2;
+ float leftOffset = (image.size.width - finalWidth)/2;
+
+
+ float x = leftOffset;
+ float y = topOffset;
+ float width = finalWidth;
+ float height = finalHeight;
+ if(selection && selection != [NSNull null]) {
+ x = leftOffset+ [selection[@"x"] floatValue]*imageToArWidth;
+ y = topOffset+[selection[@"y"] floatValue]*imageToArHeight;
+ width = [selection[@"width"] floatValue]*imageToArWidth;
+ height = [selection[@"height"] floatValue]*imageToArHeight;
+ }
+ CGRect rect = CGRectMake(x, y, width, height);
+
+ UIImage *cropped = [self cropImage:image toRect:rect];
+ return cropped;
+}
#pragma mark - plane hit detection
@@ -277,8 +386,10 @@ - (void)hitTestPlane:(const CGPoint)tapPoint types:(ARHitTestResultType)types re
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),
@@ -392,21 +503,6 @@ - (void)renderer:(id )renderer willUpdateNode:(SCNNode *)node
- (void)renderer:(id )renderer didUpdateNode:(SCNNode *)node forAnchor:(ARAnchor *)anchor {
ARPlaneAnchor *planeAnchor = (ARPlaneAnchor *)anchor;
- SCNNode *parent = [node parentNode];
- // NSLog(@"%@", parent.name);
- // NSLog(@"%f %f %f", node.position.x, node.position.y, node.position.z);
- // NSLog(@"%f %f %f %f", node.rotation.x, node.rotation.y, node.rotation.z, node.rotation.w);
-
-
- // 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.onPlaneUpdate) {
self.onPlaneUpdate(@{
@"id": planeAnchor.identifier.UUIDString,
@@ -434,6 +530,32 @@ - (void)renderer:(id )renderer didRemoveNode:(SCNNode *)node f
#pragma mark - ARSessionDelegate
+- (ARFrame * _Nullable)currentFrame {
+ return self.arView.session.currentFrame;
+}
+
+- (NSDictionary *)getCurrentLightEstimation {
+ return [self wrapLightEstimation:[self currentFrame].lightEstimate];
+}
+
+- (NSMutableArray *)getCurrentDetectedFeaturePoints {
+ NSMutableArray * featurePoints = [NSMutableArray array];
+ for (int i = 0; i < [self currentFrame].rawFeaturePoints.count; i++) {
+ vector_float3 point = [self currentFrame].rawFeaturePoints.points[i];
+
+ NSString * pointId = [NSString stringWithFormat:@"featurepoint_%lld",[self currentFrame].rawFeaturePoints.identifiers[i]];
+
+ [featurePoints addObject:@{
+ @"x": @(point[0]),
+ @"y": @(point[1]),
+ @"z": @(point[2]),
+ @"id":pointId,
+ }];
+
+ }
+ return featurePoints;
+}
+
- (void)session:(ARSession *)session didUpdateFrame:(ARFrame *)frame {
for (id sessionDelegate in self.sessionDelegates) {
if ([sessionDelegate respondsToSelector:@selector(session:didUpdateFrame:)]) {
@@ -441,20 +563,7 @@ - (void)session:(ARSession *)session didUpdateFrame:(ARFrame *)frame {
}
}
if (self.onFeaturesDetected) {
- NSMutableArray * featurePoints = [NSMutableArray array];
- for (int i = 0; i < frame.rawFeaturePoints.count; i++) {
- vector_float3 point = frame.rawFeaturePoints.points[i];
-
- NSString * pointId = [NSString stringWithFormat:@"featurepoint_%lld",frame.rawFeaturePoints.identifiers[i]];
-
- [featurePoints addObject:@{
- @"x": @(point[0]),
- @"y": @(point[1]),
- @"z": @(point[2]),
- @"id":pointId,
- }];
-
- }
+ NSMutableArray * featurePoints = [self getCurrentDetectedFeaturePoints];
dispatch_async(dispatch_get_main_queue(), ^{
@@ -465,6 +574,31 @@ - (void)session:(ARSession *)session didUpdateFrame:(ARFrame *)frame {
}
});
}
+
+ if (self.lightEstimationEnabled && self.onLightEstimation) {
+ /** this is called rapidly and is therefore demanding, better poll it from outside with getCurrentLightEstimation **/
+
+
+
+ dispatch_async(dispatch_get_main_queue(), ^{
+ if(self.onLightEstimation) {
+ NSDictionary *estimate = [self getCurrentLightEstimation];
+ self.onLightEstimation(estimate);
+ }
+ });
+
+ }
+
+}
+
+- (NSDictionary *)wrapLightEstimation:(ARLightEstimate *)estimate {
+ if(!estimate) {
+ return nil;
+ }
+ return @{
+ @"ambientColorTemperature":@(estimate.ambientColorTemperature),
+ @"ambientIntensity":@(estimate.ambientIntensity),
+ };
}
diff --git a/ios/RCTARKit.xcodeproj/project.pbxproj b/ios/RCTARKit.xcodeproj/project.pbxproj
index aa4ab109..29e9ce49 100644
--- a/ios/RCTARKit.xcodeproj/project.pbxproj
+++ b/ios/RCTARKit.xcodeproj/project.pbxproj
@@ -16,6 +16,7 @@
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 */; };
+ B1990B221FCEEBD60001AE2F /* color-grabber.m in Sources */ = {isa = PBXBuildFile; fileRef = B1990B211FCEEBD60001AE2F /* color-grabber.m */; };
B3E7B58A1CC2AC0600A0062D /* RCTARKit.m in Sources */ = {isa = PBXBuildFile; fileRef = B3E7B5891CC2AC0600A0062D /* RCTARKit.m */; };
/* End PBXBuildFile section */
@@ -29,6 +30,15 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
+ B14C363A1F9504D70047CB67 /* CopyFiles */ = {
+ isa = PBXCopyFilesBuildPhase;
+ buildActionMask = 2147483647;
+ dstPath = "";
+ dstSubfolderSpec = 10;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
@@ -52,6 +62,10 @@
10FEF6121F774C9000EC21AE /* RCTARKitNodes.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RCTARKitNodes.m; sourceTree = ""; };
10FEF6131F774C9000EC21AE /* RCTARKitDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RCTARKitDelegate.h; sourceTree = ""; };
134814201AA4EA6300B7C361 /* libRCTARKit.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRCTARKit.a; sourceTree = BUILT_PRODUCTS_DIR; };
+ B14C36631F960C500047CB67 /* PocketSVG.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = PocketSVG.framework; sourceTree = BUILT_PRODUCTS_DIR; };
+ B14C36651F960C6E0047CB67 /* PocketSVG.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = PocketSVG.framework; sourceTree = BUILT_PRODUCTS_DIR; };
+ B1990B201FCEEBD60001AE2F /* color-grabber.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "color-grabber.h"; sourceTree = ""; };
+ B1990B211FCEEBD60001AE2F /* color-grabber.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "color-grabber.m"; sourceTree = ""; };
B3E7B5881CC2AC0600A0062D /* RCTARKit.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTARKit.h; sourceTree = ""; };
B3E7B5891CC2AC0600A0062D /* RCTARKit.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTARKit.m; sourceTree = ""; };
/* End PBXFileReference section */
@@ -91,6 +105,7 @@
58B511D21A9E6C8500147676 = {
isa = PBXGroup;
children = (
+ B1990B1F1FCEEBD60001AE2F /* color-grabber */,
106999E21F3EC2FB00032829 /* components */,
10ED47A51F38BC00004DF043 /* DeviceMotion.h */,
10ED47A61F38BC00004DF043 /* DeviceMotion.m */,
@@ -108,7 +123,26 @@
105F124C1F7C0718006D4BA3 /* RCTConvert+ARKit.h */,
105F124D1F7C0718006D4BA3 /* RCTConvert+ARKit.m */,
134814211AA4EA7D00B7C361 /* Products */,
+ B13FF7601F94F72400A6C92B /* Frameworks */,
+ );
+ sourceTree = "";
+ };
+ B13FF7601F94F72400A6C92B /* Frameworks */ = {
+ isa = PBXGroup;
+ children = (
+ B14C36651F960C6E0047CB67 /* PocketSVG.framework */,
+ B14C36631F960C500047CB67 /* PocketSVG.framework */,
+ );
+ name = Frameworks;
+ sourceTree = "";
+ };
+ B1990B1F1FCEEBD60001AE2F /* color-grabber */ = {
+ isa = PBXGroup;
+ children = (
+ B1990B201FCEEBD60001AE2F /* color-grabber.h */,
+ B1990B211FCEEBD60001AE2F /* color-grabber.m */,
);
+ path = "color-grabber";
sourceTree = "";
};
/* End PBXGroup section */
@@ -121,6 +155,7 @@
58B511D71A9E6C8500147676 /* Sources */,
58B511D81A9E6C8500147676 /* Frameworks */,
58B511D91A9E6C8500147676 /* CopyFiles */,
+ B14C363A1F9504D70047CB67 /* CopyFiles */,
);
buildRules = (
);
@@ -170,6 +205,7 @@
10FEF6141F774C9000EC21AE /* RCTARKitIO.m in Sources */,
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 */,
@@ -269,6 +305,8 @@
"$(SRCROOT)/../../react-native-arcl/ios",
../../../ios/Pods/Headers/Public/,
../../../ios/Pods/Headers/Public/React,
+ "$(SRCROOT)/../../_PocketSVG/**",
+ "../node_modules/_PocketSVG/**",
);
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
LIBRARY_SEARCH_PATHS = "$(inherited)";
@@ -290,6 +328,8 @@
"$(SRCROOT)/../../react-native-arcl/ios",
../../../ios/Pods/Headers/Public/,
../../../ios/Pods/Headers/Public/React,
+ "$(SRCROOT)/../../_PocketSVG/**",
+ "../node_modules/_PocketSVG/**",
);
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
LIBRARY_SEARCH_PATHS = "$(inherited)";
diff --git a/ios/RCTARKitManager.m b/ios/RCTARKitManager.m
index 94d83311..6d7ab838 100644
--- a/ios/RCTARKitManager.m
+++ b/ios/RCTARKitManager.m
@@ -11,6 +11,7 @@
#import "RCTARKitNodes.h"
#import
#import
+#import "color-grabber.h"
@implementation RCTARKitManager
@@ -37,6 +38,28 @@ - (NSDictionary *)constantsToExport
@"Phong": SCNLightingModelPhong,
@"PhysicallyBased": SCNLightingModelPhysicallyBased
},
+ @"LightType": @{
+ @"Ambient": SCNLightTypeAmbient,
+ @"Directional": SCNLightTypeDirectional,
+ @"Omni": SCNLightTypeOmni,
+ @"Probe": SCNLightTypeProbe,
+ @"Spot": SCNLightTypeSpot,
+ @"IES": SCNLightTypeIES
+ },
+ @"ShadowMode": @{
+ @"Forward": [@(SCNShadowModeForward) stringValue],
+ @"Deferred": [@(SCNShadowModeDeferred) stringValue],
+ @"ModeModulated": [@(SCNShadowModeModulated) stringValue],
+ },
+ @"ColorMask": @{
+ @"All": [@(SCNColorMaskAll) stringValue],
+ @"None": [@(SCNColorMaskNone) stringValue],
+ @"Alpha": [@(SCNColorMaskAlpha) stringValue],
+ @"Blue": [@(SCNColorMaskBlue) stringValue],
+ @"Red": [@(SCNColorMaskRed) stringValue],
+ @"Green": [@(SCNColorMaskGreen) stringValue],
+ },
+
@"ShaderModifierEntryPoint": @{
@"Geometry": SCNShaderModifierEntryPointGeometry,
@"Surface": SCNShaderModifierEntryPointSurface,
@@ -51,18 +74,36 @@ - (NSDictionary *)constantsToExport
@"Screen": [@(SCNBlendModeScreen) stringValue],
@"Replace": [@(SCNBlendModeReplace) stringValue],
+ },
+ @"ChamferMode": @{
+ @"Both": [@(SCNChamferModeBoth) stringValue],
+ @"Back": [@(SCNChamferModeBack) stringValue],
+ @"Front": [@(SCNChamferModeBack) stringValue],
+
+ },
+ @"ARWorldAlignment": @{
+ @"Gravity": @(ARWorldAlignmentGravity),
+ @"GravityAndHeading": @(ARWorldAlignmentGravityAndHeading),
+ @"Camera": @(ARWorldAlignmentCamera),
+ },
+ @"FillMode": @{
+ @"Fill": [@(SCNFillModeFill) stringValue],
+ @"Lines": [@(SCNFillModeLines) stringValue],
}
};
}
RCT_EXPORT_VIEW_PROPERTY(debug, BOOL)
RCT_EXPORT_VIEW_PROPERTY(planeDetection, BOOL)
-RCT_EXPORT_VIEW_PROPERTY(lightEstimation, BOOL)
+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(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(onEvent, RCTBubblingEventBlock)
@@ -114,7 +155,7 @@ - (NSString *)getAssetUrl:(NSString *)localID {
return assetURLStr;
}
-- (void)storeImageInPhotoAlbum:(UIImage *)image reject:(RCTPromiseRejectBlock)reject resolve:(RCTPromiseResolveBlock)resolve {
+- (void)storeImageInPhotoAlbum:(UIImage *)image cameraProperties:(NSDictionary *) cameraProperties reject:(RCTPromiseRejectBlock)reject resolve:(RCTPromiseResolveBlock)resolve {
__block PHObjectPlaceholder *placeholder;
[[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{
@@ -129,7 +170,7 @@ - (void)storeImageInPhotoAlbum:(UIImage *)image reject:(RCTPromiseRejectBlock)re
NSString * assetURLStr = [self getAssetUrl:localID];
- resolve(@{@"url": assetURLStr, @"width":@(image.size.width), @"height": @(image.size.height)});
+ resolve(@{@"url": assetURLStr, @"width":@(image.size.width), @"height": @(image.size.height), @"camera":cameraProperties});
}
else
{
@@ -139,7 +180,7 @@ - (void)storeImageInPhotoAlbum:(UIImage *)image reject:(RCTPromiseRejectBlock)re
}
-- (void)storeImageInDirectory:(UIImage *)image directory:(NSString *)directory format:(NSString *)format reject:(RCTPromiseRejectBlock)reject resolve:(RCTPromiseResolveBlock)resolve {
+- (void)storeImageInDirectory:(UIImage *)image directory:(NSString *)directory format:(NSString *)format cameraProperties:(NSDictionary *) cameraProperties reject:(RCTPromiseRejectBlock)reject resolve:(RCTPromiseResolveBlock)resolve {
NSData *data;
if([format isEqualToString:@"jpg"]) {
data = UIImageJPEGRepresentation(image, 0.9);
@@ -157,7 +198,7 @@ - (void)storeImageInDirectory:(UIImage *)image directory:(NSString *)directory f
NSString *filePath = [directory stringByAppendingPathComponent:uniqueFileName]; //Add the file name
bool success = [data writeToFile:filePath atomically:YES]; //Write the file
if(success) {
- resolve(@{@"url": filePath, @"width":@(image.size.width), @"height": @(image.size.height)});
+ resolve(@{@"url": filePath, @"width":@(image.size.width), @"height": @(image.size.height), @"camera":cameraProperties});
} else {
// TODO use NSError from writeToFile
reject(@"snapshot_error", [NSString stringWithFormat:@"could not save to '%@'", filePath], nil);
@@ -165,7 +206,7 @@ - (void)storeImageInDirectory:(UIImage *)image directory:(NSString *)directory f
}
-- (void)storeImage:(UIImage *)image options:(NSDictionary *)options reject:(RCTPromiseRejectBlock)reject resolve:(RCTPromiseResolveBlock)resolve {
+- (void)storeImage:(UIImage *)image options:(NSDictionary *)options reject:(RCTPromiseRejectBlock)reject resolve:(RCTPromiseResolveBlock)resolve cameraProperties:(NSDictionary *)cameraProperties {
NSString * target = @"cameraRoll";
NSString * format = @"png";
@@ -177,7 +218,7 @@ - (void)storeImage:(UIImage *)image options:(NSDictionary *)options reject:(RCTP
}
if([target isEqualToString:@"cameraRoll"]) {
// camera roll / photo album
- [self storeImageInPhotoAlbum:image reject:reject resolve:resolve];
+ [self storeImageInPhotoAlbum:image cameraProperties:cameraProperties reject:reject resolve:resolve ];
} else {
NSString * dir;
if([target isEqualToString:@"cache"]) {
@@ -188,21 +229,46 @@ - (void)storeImage:(UIImage *)image options:(NSDictionary *)options reject:(RCTP
} else {
dir = target;
}
- [self storeImageInDirectory:image directory:dir format:format reject:reject resolve:resolve];
+ [self storeImageInDirectory:image directory:dir format:format cameraProperties:cameraProperties reject:reject resolve:resolve ];
}
}
RCT_EXPORT_METHOD(snapshot:(NSDictionary *)options resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) {
- UIImage *image = [[ARKit sharedInstance] getSnaphshot];
- [self storeImage:image options:options reject:reject resolve:resolve];
+ dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
+ NSDictionary * selection = options[@"selection"];
+ NSDictionary * cameraProperties = [[ARKit sharedInstance] readCamera];
+ UIImage *image = [[ARKit sharedInstance] getSnapshot:selection];
+
+ [self storeImage:image options:options reject:reject resolve:resolve cameraProperties:cameraProperties ];
+ });
}
RCT_EXPORT_METHOD(snapshotCamera:(NSDictionary *)options resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) {
- UIImage *image = [[ARKit sharedInstance] getSnaphshotCamera];
- [self storeImage:image options:options reject:reject resolve:resolve];
+ dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
+ NSDictionary * selection = options[@"selection"];
+ NSDictionary * cameraProperties = [[ARKit sharedInstance] readCamera];
+ UIImage *image = [[ARKit sharedInstance] getSnapshotCamera:selection];
+ [self storeImage:image options:options reject:reject resolve:resolve cameraProperties:cameraProperties];
+ });
+}
+
+RCT_EXPORT_METHOD(pickColorsRaw:(NSDictionary *)options resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) {
+ dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
+
+ NSDictionary * selection = options[@"selection"];
+ UIImage *image = [[ARKit sharedInstance] getSnapshotCamera:selection];
+ resolve([[ColorGrabber sharedInstance] getColorsFromImage:image options:options]);
+ });
+}
+
+RCT_EXPORT_METHOD(pickColorsRawFromFile:(NSString * )filePath options:(NSDictionary *)options resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) {
+ dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
+ UIImage *image = [UIImage imageWithContentsOfFile:filePath];
+ resolve([[ColorGrabber sharedInstance] getColorsFromImage:image options:options]);
+ });
}
RCT_EXPORT_METHOD(getCamera:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) {
@@ -213,6 +279,14 @@ - (void)storeImage:(UIImage *)image options:(NSDictionary *)options reject:(RCTP
resolve([[ARKit sharedInstance] readCameraPosition]);
}
+RCT_EXPORT_METHOD(getCurrentLightEstimation:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) {
+ resolve([[ARKit sharedInstance] getCurrentLightEstimation]);
+}
+
+RCT_EXPORT_METHOD(getCurrentDetectedFeaturePoints:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) {
+ resolve([[ARKit sharedInstance] getCurrentDetectedFeaturePoints]);
+}
+
RCT_EXPORT_METHOD(projectPoint:
(NSDictionary *)pointDict
resolve:(RCTPromiseResolveBlock)resolve
@@ -240,4 +314,3 @@ - (void)storeImage:(UIImage *)image options:(NSDictionary *)options reject:(RCTP
}
@end
-
diff --git a/ios/RCTARKitNodes.h b/ios/RCTARKitNodes.h
index c67fa67f..c3136bcc 100644
--- a/ios/RCTARKitNodes.h
+++ b/ios/RCTARKitNodes.h
@@ -36,7 +36,8 @@ typedef NS_OPTIONS(NSUInteger, RFReferenceFrame) {
+ (instancetype)sharedInstance;
- (void)addNodeToScene:(SCNNode *)node inReferenceFrame:(NSString *)referenceFrame;
-- (void)updateNode:(NSString *)key properties:(NSDictionary *) properties;
+- (void)updateNode:(NSString *)nodeId properties:(NSDictionary *) properties;
+- (float)getCameraDistanceToPoint:(SCNVector3)point;
- (void)registerNode:(SCNNode *)node forKey:(NSString *)key;
- (SCNNode *)nodeForKey:(NSString *)key;
- (void)removeNodeForKey:(NSString *)key;
diff --git a/ios/RCTARKitNodes.m b/ios/RCTARKitNodes.m
index 27754820..a7f4a98c 100644
--- a/ios/RCTARKitNodes.m
+++ b/ios/RCTARKitNodes.m
@@ -107,16 +107,15 @@ - (void)clear {
- (void)addNodeToLocalFrame:(SCNNode *)node {
node.referenceFrame = RFReferenceFrameLocal;
- NSLog(@"[RCTARKitNodes] Add model %@ to Local frame at (%.2f, %.2f, %.2f)", node.name, node.position.x, node.position.y, node.position.z);
-
+ //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 model %@ to Camera frame at (%.2f, %.2f, %.2f)", node.name, node.position.x, node.position.y, node.position.z);
+ //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 +123,7 @@ - (void)addNodeToCameraFrame:(SCNNode *)node {
- (void)addNodeToFrontOfCameraFrame:(SCNNode *)node {
node.referenceFrame = RFReferenceFrameFrontOfCamera;
- NSLog(@"[RCTARKitNodes] Add model %@ to FrontOfCamera frame at (%.2f, %.2f, %.2f)", node.name, node.position.x, node.position.y, node.position.z);
+ //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];
}
@@ -132,12 +131,13 @@ - (void)addNodeToFrontOfCameraFrame:(SCNNode *)node {
- (NSDictionary *)getSceneObjectsHitResult:(const CGPoint)tapPoint {
NSDictionary *options = @{
- SCNHitTestRootNodeKey: self.localOrigin
+ SCNHitTestRootNodeKey: self.localOrigin,
+ SCNHitTestSortResultsKey: @(YES)
};
- NSArray *results = [_arView hitTest:tapPoint options:options];
+ NSArray *results = [_arView hitTest:tapPoint options:options];
NSMutableArray * resultsMapped = [self mapHitResultsWithSceneResults:results];
- NSDictionary *planeHitResult = getSceneObjectHitResult(resultsMapped, tapPoint);
- return planeHitResult;
+ NSDictionary *result = getSceneObjectHitResult(resultsMapped, tapPoint);
+ return result;
}
@@ -155,24 +155,37 @@ - (NSDictionary *)getSceneObjectsHitResult:(const CGPoint)tapPoint {
- (NSMutableArray *) mapHitResultsWithSceneResults: (NSArray *)results {
NSMutableArray *resultsMapped = [NSMutableArray arrayWithCapacity:[results count]];
+
[results enumerateObjectsUsingBlock:^(id obj, NSUInteger index, BOOL *stop) {
SCNHitTestResult *result = (SCNHitTestResult *) obj;
SCNNode * node = result.node;
- NSArray *keys = [self.nodes allKeysForObject: node];
- if([keys count]) {
-
- NSString * firstKey = [keys firstObject];
+
+ NSString * nodeId = [self findNodeId:node];
+ if(nodeId) {
+
+ SCNVector3 point = result.worldCoordinates;
+ SCNVector3 normal = result.worldNormal;
+ float distance = [self getCameraDistanceToPoint:point];
+
[resultsMapped addObject:(@{
- @"id": firstKey
+ @"id": nodeId,
+ @"distance": @(distance),
+ @"point": @{
+ @"x": @(point.x),
+ @"y": @(point.y),
+ @"z": @(point.z)
+ },
+ @"normal": @{
+ @"x": @(normal.x),
+ @"y": @(normal.y),
+ @"z": @(normal.z)
+
+ }
} )];
- } else {
- NSLog(@"no key found for node %@", node);
- NSLog(@"for results %@", results);
- NSLog(@"all nodes %@", self.nodes);
- NSLog(@"origin %@", self.localOrigin);
}
-
+
}];
+
return resultsMapped;
}
@@ -187,28 +200,64 @@ - (void)registerNode:(SCNNode *)node forKey:(NSString *)key {
}
}
+
+- (NSString *) findNodeId:(SCNNode *)nodeWithParents {
+
+ SCNNode* _node = nodeWithParents;
+ while(_node) {
+ if(_node.name && [self.nodes objectForKey:_node.name]) {
+ return _node.name;
+ }
+ _node = _node.parentNode;
+ }
+ return nil;
+
+}
+
+
- (SCNNode *)nodeForKey:(NSString *)key {
return [self.nodes objectForKey:key];
}
- (void)removeNodeForKey:(NSString *)key {
+
SCNNode *node = [self.nodes objectForKey:key];
if (node) {
- [node removeFromParentNode];
[self.nodes removeObjectForKey:key];
+ if(node.light) {
+ // see https://stackoverflow.com/questions/47270056/how-to-remove-a-light-with-shadowmode-deferred-in-scenekit-arkit?noredirect=1#comment81491270_47270056
+ node.hidden = YES;
+ [node removeFromParentNode];
+ } else {
+ [node removeFromParentNode];
+ }
}
}
-- (void)updateNode:(NSString *)key properties:(NSDictionary *) properties {
- SCNNode *node = [self.nodes objectForKey:key];
- // only basic properties like position and rotation can currently be updated this way
+- (void)updateNode:(NSString *)nodeId properties:(NSDictionary *) properties {
+ SCNNode *node = [self.nodes objectForKey:nodeId];
if(node) {
[RCTConvert setNodeProperties:node properties:properties];
+ if(node.geometry && properties[@"shape"]) {
+ [RCTConvert setShapeProperties:node.geometry properties:properties[@"shape"]];
+ }
+ if(properties[@"material"]) {
+ for (id material in node.geometry.materials) {
+ [RCTConvert setMaterialProperties:material properties:properties[@"material"]];
+ }
+ }
+ if(node.light) {
+ [RCTConvert setLightProperties:node.light properties:properties];
+ }
+
+
}
}
+
+
#pragma mark - RCTARKitSessionDelegate
- (void)session:(ARSession *)session didUpdateFrame:(ARFrame *)frame {
simd_float4 pos = frame.camera.transform.columns[3];
@@ -221,4 +270,21 @@ - (void)session:(ARSession *)session didUpdateFrame:(ARFrame *)frame {
}
+- (float)getCameraDistanceToPoint:(SCNVector3)point {
+ return getDistance(self.cameraOrigin.position, point);
+}
+
+static float getDistance(const SCNVector3 pointA, const SCNVector3 pointB) {
+ float xd = pointB.x - pointA.x;
+ float yd = pointB.y - pointA.y;
+ float zd = pointB.z - pointA.z;
+ float distance = sqrt(xd * xd + yd * yd + zd * zd);
+
+ if (distance < 0){
+ return (distance * -1);
+ } else {
+ return (distance);
+ }
+}
+
@end
diff --git a/ios/RCTConvert+ARKit.h b/ios/RCTConvert+ARKit.h
index 0576ed06..05cb6063 100644
--- a/ios/RCTConvert+ARKit.h
+++ b/ios/RCTConvert+ARKit.h
@@ -31,11 +31,15 @@
+ (SCNTorus *)SCNTorus:(id)json;
+ (SCNCapsule *)SCNCapsule:(id)json;
+ (SCNPlane *)SCNPlane:(id)json;
++ (SCNShape * )SCNShape:(id)json;
++ (SCNLight *)SCNLight:(id)json;
+ (SCNTextNode *)SCNTextNode:(id)json;
+ (void)setNodeProperties:(SCNNode *)node properties:(id)json;
+ (void)setMaterialProperties:(SCNMaterial *)material properties:(id)json;
++ (void)setShapeProperties:(SCNGeometry *)geometry properties:(id)json;
++ (void)setLightProperties:(SCNLight *)light properties:(id)json;
@end
diff --git a/ios/RCTConvert+ARKit.m b/ios/RCTConvert+ARKit.m
index 91a22c24..5e27d006 100644
--- a/ios/RCTConvert+ARKit.m
+++ b/ios/RCTConvert+ARKit.m
@@ -7,6 +7,7 @@
//
#import "RCTConvert+ARKit.h"
+#import "SVGBezierPath.h"
@implementation RCTConvert (ARKit)
@@ -37,35 +38,46 @@ + (SCNVector4)SCNVector4:(id)json {
+ (SCNNode *)SCNNode:(id)json {
SCNNode *node = [SCNNode new];
-
+
node.name = [NSString stringWithFormat:@"%@", json[@"id"]];
[self setNodeProperties:node properties:json];
-
+
return node;
}
+
++ (void)addMaterials:(SCNGeometry *)geometry json:(id)json sides:(int) sides {
+ SCNMaterial *material = [self SCNMaterial:json[@"material"]];
+
+ NSMutableArray *materials = [NSMutableArray array];
+ for (int i = 0; i < sides; i++)
+ [materials addObject: material];
+ geometry.materials = materials;
+}
+
+ (SCNBox *)SCNBox:(id)json {
NSDictionary *shape = json[@"shape"];
+
+
CGFloat width = [shape[@"width"] floatValue];
CGFloat height = [shape[@"height"] floatValue];
CGFloat length = [shape[@"length"] floatValue];
CGFloat chamfer = [shape[@"chamfer"] floatValue];
SCNBox *geometry = [SCNBox boxWithWidth:width height:height length:length chamferRadius:chamfer];
- SCNMaterial *material = [self SCNMaterial:json[@"material"]];
- geometry.materials = @[material, material, material, material, material, material];
-
+ [self addMaterials:geometry json:json sides:6];
+
return geometry;
}
+
+ (SCNSphere *)SCNSphere:(id)json {
NSDictionary* shape = json[@"shape"];
CGFloat radius = [shape[@"radius"] floatValue];
SCNSphere *geometry = [SCNSphere sphereWithRadius:radius];
- SCNMaterial *material = [self SCNMaterial:json[@"material"]];
- geometry.materials = @[material];
-
+ [self addMaterials:geometry json:json sides:1];
+
return geometry;
}
@@ -75,8 +87,7 @@ + (SCNCylinder *)SCNCylinder:(id)json {
CGFloat height = [shape[@"height"] floatValue];
SCNCylinder *geometry = [SCNCylinder cylinderWithRadius:radius height:height];
- SCNMaterial *material = [self SCNMaterial:json[@"material"]];
- geometry.materials = @[material, material, material];
+ [self addMaterials:geometry json:json sides:3];
return geometry;
}
@@ -88,8 +99,7 @@ + (SCNCone *)SCNCone:(id)json {
CGFloat height = [shape[@"height"] floatValue];
SCNCone *geometry = [SCNCone coneWithTopRadius:topR bottomRadius:bottomR height:height];
- SCNMaterial *material = [self SCNMaterial:json[@"material"]];
- geometry.materials = @[material, material];
+ [self addMaterials:geometry json:json sides:2];
return geometry;
}
@@ -101,8 +111,7 @@ + (SCNPyramid *)SCNPyramid:(id)json {
CGFloat height = [shape[@"height"] floatValue];
SCNPyramid *geometry = [SCNPyramid pyramidWithWidth:width height:height length:length];
- SCNMaterial *material = [self SCNMaterial:json[@"material"]];
- geometry.materials = @[material, material, material, material, material];
+ [self addMaterials:geometry json:json sides:5];
return geometry;
}
@@ -114,8 +123,7 @@ + (SCNTube *)SCNTube:(id)json {
CGFloat height = [shape[@"height"] floatValue];
SCNTube *geometry = [SCNTube tubeWithInnerRadius:innerR outerRadius:outerR height:height];
- SCNMaterial *material = [self SCNMaterial:json[@"material"]];
- geometry.materials = @[material, material, material, material];
+ [self addMaterials:geometry json:json sides:4];
return geometry;
}
@@ -126,20 +134,18 @@ + (SCNTorus *)SCNTorus:(id)json {
CGFloat pipeR = [shape[@"pipeR"] floatValue];
SCNTorus *geometry = [SCNTorus torusWithRingRadius:ringR pipeRadius:pipeR];
- SCNMaterial *material = [self SCNMaterial:json[@"material"]];
- geometry.materials = @[material];
+ [self addMaterials:geometry json:json sides:1];
return geometry;
}
-
+
+ (SCNCapsule *)SCNCapsule:(id)json {
NSDictionary* shape = json[@"shape"];
CGFloat capR = [shape[@"capR"] floatValue];
CGFloat height = [shape[@"height"] floatValue];
SCNCapsule *geometry = [SCNCapsule capsuleWithCapRadius:capR height:height];
- SCNMaterial *material = [self SCNMaterial:json[@"material"]];
- geometry.materials = @[material];
+ [self addMaterials:geometry json:json sides:1];
return geometry;
}
@@ -161,9 +167,75 @@ + (SCNPlane *)SCNPlane:(id)json {
if(shape[@"heightSegmentCount"]) {
geometry.heightSegmentCount = [shape[@"heightSegmentCount"] intValue];
}
- SCNMaterial *material = [self SCNMaterial:json[@"material"]];
- material.doubleSided = YES;
- geometry.materials = @[material];
+ [self addMaterials:geometry json:json sides:1];
+
+ return geometry;
+}
+
++ (SVGBezierPath *)svgStringToBezier:(NSString *)pathString {
+ NSArray * paths = [SVGBezierPath pathsFromSVGString:pathString];
+ SVGBezierPath * fullPath;
+ for(SVGBezierPath *path in paths) {
+ if(!fullPath) {
+ fullPath = path;
+ } else {
+ [fullPath appendPath:path];
+ }
+ }
+ return fullPath;
+}
+
++ (void)setChamferProfilePathSvg:(SCNShape *)geometry properties:(NSDictionary *)shape {
+ if (shape[@"chamferProfilePathSvg"]) {
+
+
+ SVGBezierPath * path = [self svgStringToBezier:shape[@"chamferProfilePathSvg"]];
+ if(shape[@"chamferProfilePathFlatness"]) {
+ path.flatness = [shape[@"chamferProfilePathFlatness"] floatValue];
+ }
+ // normalize path
+ CGRect boundingBox = path.bounds;
+ if(path.bounds.size.width !=0 && path.bounds.size.height != 0) {
+ CGFloat scaleX = 1/boundingBox.size.width;
+ CGFloat scaleY = scaleY = 1/boundingBox.size.height;
+
+ CGAffineTransform transform = CGAffineTransformMakeScale(scaleX, scaleY);
+ [path applyTransform:transform];
+ geometry.chamferProfile = path;
+ } else {
+ NSLog(@"Invalid chamferProfilePathFlatness");
+ }
+ }
+
+}
+
++ (SCNShape * )SCNShape:(id)json {
+ NSDictionary* shape = json[@"shape"];
+
+
+ NSString * pathString = shape[@"pathSvg"];
+
+ SVGBezierPath * path = [self svgStringToBezier:pathString];
+
+ if (shape[@"pathFlatness"]) {
+ path.flatness = [shape[@"pathFlatness"] floatValue];
+ }
+
+ CGFloat extrusion = [shape[@"extrusion"] floatValue];
+ SCNShape *geometry = [SCNShape shapeWithPath:path extrusionDepth:extrusion];
+
+ if (shape[@"chamferMode"]) {
+ geometry.chamferMode = (SCNChamferMode) [shape[@"chamferMode"] integerValue];
+ }
+ if (shape[@"chamferRadius"]) {
+ geometry.chamferRadius = [shape[@"chamferRadius"] floatValue];
+ }
+ if (shape[@"chamferProfilePathSvg"]) {
+ [self setChamferProfilePathSvg:geometry properties:shape];
+ }
+
+ [self addMaterials:geometry json:json sides:1];
+
return geometry;
}
@@ -183,7 +255,7 @@ + (SCNTextNode *)SCNTextNode:(id)json {
CGFloat fontSize = [font[@"size"] floatValue];
CGFloat size = fontSize / 12;
SCNText *scnText = [SCNText textWithString:text extrusionDepth:depth / size];
-
+
scnText.flatness = 0.1;
// font
@@ -203,19 +275,21 @@ + (SCNTextNode *)SCNTextNode:(id)json {
// material
// scnText.materials = @[face, face, border, border, border];
- SCNMaterial *material = [self SCNMaterial:json[@"material"]];
- scnText.materials = @[material, material, material, material, material];
+ [self addMaterials:scnText json:json sides:5];
// SCNTextNode
SCNTextNode *textNode = [SCNNode nodeWithGeometry:scnText];
+ textNode.name = [NSString stringWithFormat:@"%@", json[@"id"]];
+
+
textNode.scale = SCNVector3Make(size, size, size);
// position textNode
SCNVector3 min = SCNVector3Zero;
SCNVector3 max = SCNVector3Zero;
[textNode getBoundingBoxMin:&min max:&max];
-
+
textNode.position = SCNVector3Make(-(min.x + max.x) / 2 * size,
-(min.y + max.y) / 2 * size,
-(min.z + max.z) / 2 * size);
@@ -224,21 +298,64 @@ + (SCNTextNode *)SCNTextNode:(id)json {
}
++ (SCNLight *)SCNLight:(id)json {
+ SCNLight * light = [SCNLight light];
+ [self setLightProperties:light properties:json];
+ return light;
+}
+
+
++ (void)setMaterialPropertyContents:(id)property material:(SCNMaterialProperty *)material {
+ if (property[@"path"]) {
+ material.contents = property[@"path"];
+ } else if (property[@"color"]) {
+ material.contents = [self UIColor:property[@"color"]];
+ }
+ if (property[@"intensity"]) {
+ material.intensity = [property[@"intensity"] floatValue];
+ }
+}
+
+ (void)setMaterialProperties:(SCNMaterial *)material properties:(id)json {
+ if (json[@"doubleSided"]) {
+ material.doubleSided = [json[@"doubleSided"] boolValue];
+ } else {
+ material.doubleSided = YES;
+ }
+
if (json[@"blendMode"]) {
material.blendMode = (SCNBlendMode) [json[@"blendMode"] integerValue];
}
+
if (json[@"lightingModel"]) {
material.lightingModelName = json[@"lightingModel"];
}
+
if (json[@"diffuse"]) {
- material.diffuse.contents = [self UIColor:json[@"diffuse"]];
+ [self setMaterialPropertyContents:json[@"diffuse"] material:material.diffuse];
+ }
+
+ if (json[@"normal"]) {
+ [self setMaterialPropertyContents:json[@"normal"] material:material.normal];
+ }
+
+ if (json[@"displacement"]) {
+ [self setMaterialPropertyContents:json[@"displacement"] material:material.displacement];
+ }
+
+ if (json[@"specular"]) {
+ [self setMaterialPropertyContents:json[@"specular"] material:material.specular];
+ }
+
+ if (json[@"transparency"]) {
+ material.transparency = [json[@"transparency"] floatValue];
}
if (json[@"metalness"]) {
material.lightingModelName = SCNLightingModelPhysicallyBased;
material.metalness.contents = @([json[@"metalness"] floatValue]);
}
+
if (json[@"roughness"]) {
material.lightingModelName = SCNLightingModelPhysicallyBased;
material.roughness.contents = @([json[@"roughness"] floatValue]);
@@ -247,9 +364,39 @@ + (void)setMaterialProperties:(SCNMaterial *)material properties:(id)json {
if(json[@"shaders"] ) {
material.shaderModifiers = json[@"shaders"];
}
+
+ if(json[@"writesToDepthBuffer"] ) {
+ material.writesToDepthBuffer = [json[@"writesToDepthBuffer"] boolValue];
+ }
+
+ if(json[@"colorBufferWriteMask"] ) {
+ material.colorBufferWriteMask = [json[@"colorBufferWriteMask"] integerValue];
+ }
+
+ if(json[@"fillMode"] ) {
+ material.fillMode = [json[@"fillMode"] integerValue];
+ }
+
+ if(json[@"doubleSided"]) {
+ material.doubleSided = [json[@"doubleSided"] boolValue];
+ }
+
+ if(json[@"litPerPixel"]) {
+ material.litPerPixel = [json[@"litPerPixel"] boolValue];
+ }
}
+ (void)setNodeProperties:(SCNNode *)node properties:(id)json {
+
+ if (json[@"categoryBitMask"]) {
+ node.categoryBitMask = [json[@"categoryBitMask"] integerValue];
+ }
+ if (json[@"renderingOrder"]) {
+ node.renderingOrder = [json[@"renderingOrder"] integerValue];
+ }
+ if (json[@"castsShadow"]) {
+ node.castsShadow = [json[@"castsShadow"] boolValue];
+ }
if(json[@"transition"]) {
NSDictionary * transition =json[@"transition"];
if(transition[@"duration"]) {
@@ -257,7 +404,7 @@ + (void)setNodeProperties:(SCNNode *)node properties:(id)json {
} else {
[SCNTransaction setAnimationDuration:0.0];
}
-
+
} else {
[SCNTransaction setAnimationDuration:0.0];
}
@@ -265,6 +412,12 @@ + (void)setNodeProperties:(SCNNode *)node properties:(id)json {
node.position = [self SCNVector3:json[@"position"]];
}
+ if (json[@"scale"]) {
+ CGFloat scale = [json[@"scale"] floatValue];
+ node.scale = SCNVector3Make(scale, scale, scale);
+
+ }
+
if (json[@"eulerAngles"]) {
node.eulerAngles = [self SCNVector3:json[@"eulerAngles"]];
}
@@ -276,8 +429,118 @@ + (void)setNodeProperties:(SCNNode *)node properties:(id)json {
if (json[@"rotation"]) {
node.rotation = [self SCNVector4:json[@"rotation"]];
}
+
+ if (json[@"opacity"]) {
+ node.opacity = [json[@"opacity"] floatValue];
+ }
}
++ (NSSet *) specialShapeProperties {
+ return [[NSSet alloc] initWithArray:
+ @[@"pathSvg", @"chamferProfilePathSvg"]];
+}
+
+
++ (void)setShapeProperties:(SCNGeometry *)geometry properties:(id)shapeJson {
+
+ // most properties are strings
+ for (NSString* key in shapeJson) {
+ if(![self.specialShapeProperties containsObject:key]) {
+ id value = [NSNumber numberWithFloat:[shapeJson[key] floatValue]];
+ [geometry setValue:value forKey:key];
+ }
+ }
+
+ if([geometry isKindOfClass:[SCNShape class]]) {
+ SCNShape * shapeGeometry = (SCNShape * ) geometry;
+ if(shapeJson[@"pathSvg"]) {
+ NSString * pathString = shapeJson[@"pathSvg"];
+ SVGBezierPath * path = [self svgStringToBezier:pathString];
+ if (shapeJson[@"pathFlatness"]) {
+ path.flatness = [shapeJson[@"pathFlatness"] floatValue];
+ }
+ shapeGeometry.path = path;
+ }
+ if (shapeJson[@"chamferProfilePathSvg"]) {
+ [self setChamferProfilePathSvg: shapeGeometry properties:shapeJson];
+ }
+
+ }
+}
+
+
+
++ (void)setLightProperties:(SCNLight *)light properties:(id)json {
+ if (json[@"lightCategoryBitMask"]) {
+ light.categoryBitMask = [json[@"lightCategoryBitMask"] integerValue];
+ }
+ if(json[@"type"]) {
+ light.type = json[@"type"];
+ }
+ if(json[@"color"]) {
+ light.color = (__bridge id _Nonnull)([RCTConvert CGColor:json[@"color"]]);
+ }
+ if(json[@"temperature"]) {
+ light.temperature = [json[@"temperature"] floatValue];
+ }
+
+ if(json[@"intensity"]) {
+ light.intensity = [json[@"intensity"] floatValue];
+ }
+
+ if(json[@"attenuationStartDistance"]) {
+ light.attenuationStartDistance = [json[@"attenuationStartDistance"] floatValue];
+ }
+
+ if(json[@"attenuationEndDistance"]) {
+ light.attenuationEndDistance = [json[@"attenuationEndDistance"] floatValue];
+ }
+
+ if(json[@"spotInnerAngle"]) {
+ light.spotInnerAngle = [json[@"spotInnerAngle"] floatValue];
+ }
+
+ if(json[@"spotOuterAngle"]) {
+ light.spotOuterAngle = [json[@"spotOuterAngle"] floatValue];
+ }
+
+ if(json[@"castsShadow"]) {
+ light.castsShadow = [json[@"castsShadow"] boolValue];
+ }
+
+ if(json[@"shadowRadius"]) {
+ light.shadowRadius = [json[@"shadowRadius"] floatValue];
+ }
+
+ if(json[@"shadowColor"]) {
+ light.shadowColor = (__bridge id _Nonnull)([RCTConvert CGColor:json[@"shadowColor"]]);
+ }
+
+
+ if(json[@"shadowSampleCount"]) {
+ light.shadowSampleCount = [json[@"shadowSampleCount"] integerValue];
+ }
+
+ if(json[@"shadowBias"]) {
+ light.shadowBias = [json[@"shadowBias"] floatValue];
+ }
+
+ if(json[@"shadowMode"]) {
+ light.shadowMode = [json[@"shadowMode"] integerValue];
+ }
+ if(json[@"orthographicScale"]) {
+ light.orthographicScale = [json[@"orthographicScale"] floatValue];
+ }
+
+ if(json[@"zFar"]) {
+ light.zFar = [json[@"zFar"] floatValue];
+ }
+
+ if(json[@"zNear"]) {
+ light.zNear = [json[@"zNear"] floatValue];
+ }
+}
+
@end
diff --git a/ios/color-grabber/color-grabber.h b/ios/color-grabber/color-grabber.h
new file mode 100644
index 00000000..69cb1eaf
--- /dev/null
+++ b/ios/color-grabber/color-grabber.h
@@ -0,0 +1,13 @@
+//
+// color-grabber.h
+//
+
+#import
+#import
+#import
+
+@interface ColorGrabber : NSObject
+
++ (instancetype)sharedInstance;
+- (NSArray *)getColorsFromImage:(UIImage *)image options:(NSDictionary *)options;
+@end
diff --git a/ios/color-grabber/color-grabber.m b/ios/color-grabber/color-grabber.m
new file mode 100644
index 00000000..34b06fc9
--- /dev/null
+++ b/ios/color-grabber/color-grabber.m
@@ -0,0 +1,250 @@
+
+#import
+#import
+#import "color-grabber.h"
+
+#define CLAMP(x, low, high) ({\
+__typeof__(x) __x = (x); \
+__typeof__(low) __low = (low);\
+__typeof__(high) __high = (high);\
+__x > __high ? __high : (__x < __low ? __low : __x);\
+})
+
+@implementation ColorGrabber
+
+
+
+
+RCT_EXPORT_MODULE();
+
+- (NSArray *)getColorsFromImage:(UIImage *)image options:(NSDictionary *)options {
+
+ float dimension = [RCTConvert float:options[@"dimension"]]; // 4
+ float flexibility = [RCTConvert float:options[@"flexibility"]]; // 5;
+ float range = [RCTConvert float:options[@"range"]]; // 40;
+
+ NSMutableArray * colours = [NSMutableArray new];
+ CGImageRef imageRef = [image CGImage];
+ CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
+ unsigned char *rawData = (unsigned char*) calloc(dimension * dimension * 4, sizeof(unsigned char));
+ NSUInteger bytesPerPixel = 4;
+ NSUInteger bytesPerRow = bytesPerPixel * dimension;
+ NSUInteger bitsPerComponent = 8;
+ CGContextRef context = CGBitmapContextCreate(rawData, dimension, dimension, bitsPerComponent, bytesPerRow, colorSpace, kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big);
+ CGColorSpaceRelease(colorSpace);
+ CGContextDrawImage(context, CGRectMake(0, 0, dimension, dimension), imageRef);
+ CGContextRelease(context);
+
+ float x = 0;
+ float y = 0;
+ for (int n = 0; n<(dimension*dimension); n++){
+
+ int index = (bytesPerRow * y) + x * bytesPerPixel;
+ int red = rawData[index];
+ int green = rawData[index + 1];
+ int blue = rawData[index + 2];
+ int alpha = rawData[index + 3];
+ NSArray * a = [NSArray arrayWithObjects:[NSString stringWithFormat:@"%i",red],[NSString stringWithFormat:@"%i",green],[NSString stringWithFormat:@"%i",blue],[NSString stringWithFormat:@"%i",alpha], nil];
+ [colours addObject:a];
+
+ y++;
+ if (y==dimension){
+ y=0;
+ x++;
+ }
+ }
+ free(rawData);
+
+ // Add some colour flexibility (adds more colours either side of the colours in the image)
+ NSArray * copyColours = [NSArray arrayWithArray:colours];
+ NSMutableArray * flexibleColours = [NSMutableArray new];
+
+ float flexFactor = flexibility * 2 + 1;
+ float factor = flexFactor * flexFactor * 3; //(r,g,b) == *3
+ for (int n = 0; n<(dimension * dimension); n++){
+
+ NSArray * pixelColours = copyColours[n];
+ NSMutableArray * reds = [NSMutableArray new];
+ NSMutableArray * greens = [NSMutableArray new];
+ NSMutableArray * blues = [NSMutableArray new];
+
+ for (int p = 0; p<3; p++){
+
+ NSString * rgbStr = pixelColours[p];
+ int rgb = [rgbStr intValue];
+
+ for (int f = -flexibility; f= ranged_r-range && r<= ranged_r+range){
+ if (g>= ranged_g-range && g<= ranged_g+range){
+ if (b>= ranged_b-range && b<= ranged_b+range){
+ exclude = true;
+ }
+ }
+ }
+ }
+
+ if (!exclude){ [ranges addObject:key]; }
+ }
+
+
+
+ // If you want percentages to colours continue below
+ NSMutableDictionary * temp = [NSMutableDictionary new];
+ float totalCount = 0.0f;
+ for (NSString * rangeKey in ranges){
+ NSNumber * count = colourCounter[rangeKey];
+ totalCount += [count intValue];
+ temp[rangeKey]=count;
+ }
+
+ // Set percentages
+ NSMutableArray * colors = [NSMutableArray new];
+ for (NSString * key in temp){
+ float count = [temp[key] floatValue];
+ float percentage = count/totalCount;
+ NSLog(@"%f",percentage);
+ NSArray * rgb = [key componentsSeparatedByString:@","];
+ float r = [rgb[0] floatValue];
+ float g = [rgb[1] floatValue];
+ float b = [rgb[2] floatValue];
+
+
+
+ [colors addObject:@{
+ @"color": @{
+ @"r": @(r),
+ @"g": @(g),
+ @"b": @(b)
+ },
+ @"percentage":@(percentage)
+ }];
+
+ }
+ return colors;
+}
+
+RCT_EXPORT_METHOD(getColors:(NSString *)path options:(NSDictionary *)options resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject)
+{
+
+ NSURL* aURL = [NSURL URLWithString:path];
+
+ if([path hasPrefix: @"/"]) {
+ UIImage *image = [UIImage imageWithContentsOfFile:path];
+
+ NSArray * colors = [self getColorsFromImage:image options:options];
+ resolve(colors);
+ } else {
+ ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init];
+
+ [library assetForURL:aURL resultBlock:^(ALAsset *asset) {
+ UIImage *image = [UIImage imageWithCGImage:[[asset defaultRepresentation] fullScreenImage] scale:0.5 orientation:UIImageOrientationUp];
+ NSArray * colors = [self getColorsFromImage:image options:options];
+ resolve(colors);
+
+ }
+ failureBlock:^(NSError *error) {
+ reject(@"asset error", @"cant't get colors from asset", error );
+ }];
+ }
+
+
+
+
+}
+
+- (NSString *)hexStringFromColor:(UIColor *)color {
+ const CGFloat *components = CGColorGetComponents(color.CGColor);
+
+ CGFloat r = components[0];
+ CGFloat g = components[1];
+ CGFloat b = components[2];
+
+ return [NSString stringWithFormat:@"#%02lX%02lX%02lX",
+ lroundf(r * 255),
+ lroundf(g * 255),
+ lroundf(b * 255)];
+}
+
+
++ (instancetype)sharedInstance {
+ static ColorGrabber *instance = nil;
+ static dispatch_once_t onceToken;
+
+ dispatch_once(&onceToken, ^{
+ if (instance == nil) {
+ instance = [[self alloc] init];
+ }
+ });
+ return instance;
+}
+
+
+@end
+
+
+
diff --git a/ios/components/ARGeosManager.m b/ios/components/ARGeosManager.m
index 754cc4b8..c6a26757 100644
--- a/ios/components/ARGeosManager.m
+++ b/ios/components/ARGeosManager.m
@@ -18,6 +18,7 @@ @implementation ARGeosManager
[[RCTARKitNodes sharedInstance] addNodeToScene:node inReferenceFrame:frame];
}
+
RCT_EXPORT_METHOD(addSphere:(SCNSphere *)geometry node:(SCNNode *)node frame:(NSString *)frame) {
node.geometry = geometry;
[[RCTARKitNodes sharedInstance] addNodeToScene:node inReferenceFrame:frame];
@@ -58,11 +59,22 @@ @implementation ARGeosManager
[[RCTARKitNodes sharedInstance] addNodeToScene:node inReferenceFrame:frame];
}
+RCT_EXPORT_METHOD(addShape:(SCNShape *)geometry node:(SCNNode *)node frame:(NSString *)frame) {
+ node.geometry = geometry;
+ [[RCTARKitNodes sharedInstance] addNodeToScene:node inReferenceFrame:frame];
+}
+
+RCT_EXPORT_METHOD(addLight:(SCNLight *)light node:(SCNNode *)node frame:(NSString *)frame) {
+ node.light = light;
+ [[RCTARKitNodes sharedInstance] addNodeToScene:node inReferenceFrame:frame];
+}
+
+
RCT_EXPORT_METHOD(unmount:(NSString *)identifier) {
[[RCTARKitNodes sharedInstance] removeNodeForKey:identifier];
}
-RCT_EXPORT_METHOD(update:(NSString *)identifier properties:(NSDictionary *) properties) {
+RCT_EXPORT_METHOD(updateNode:(NSString *)identifier properties:(NSDictionary *) properties) {
[[RCTARKitNodes sharedInstance] updateNode:identifier properties:properties];
}
diff --git a/lib/colorUtils.js b/lib/colorUtils.js
new file mode 100644
index 00000000..d41e81e9
--- /dev/null
+++ b/lib/colorUtils.js
@@ -0,0 +1,43 @@
+// kudos to to https://github.com/jverhoelen/camanjs-whitebalance/blob/master/src/caman.whitebalance.js
+import { NativeModules } from 'react-native';
+
+const ARKitManager = NativeModules.ARKitManager;
+
+export const colorTemperatureToRgb = temperature => {
+ const m = global.Math;
+ const temp = temperature / 100;
+ let r;
+ let g;
+ let b;
+
+ if (temp <= 66) {
+ r = 255;
+ g = m.min(m.max(99.4708025861 * m.log(temp) - 161.1195681661, 0), 255);
+ } else {
+ r = m.min(m.max(329.698727446 * m.pow(temp - 60, -0.1332047592), 0), 255);
+ g = m.min(m.max(288.1221695283 * m.pow(temp - 60, -0.0755148492), 0), 255);
+ }
+
+ if (temp >= 66) {
+ b = 255;
+ } else if (temp <= 19) {
+ b = 0;
+ } else {
+ b = temp - 10;
+ b = m.min(m.max(138.5177312231 * m.log(b) - 305.0447927307, 0), 255);
+ }
+
+ return {
+ r,
+ g,
+ b,
+ };
+};
+export const whiteBalanceWithTemperature = ({ r, g, b }, temperature) => {
+ const temperatureRgb = colorTemperatureToRgb(temperature);
+ return {
+ r: r * 255 / temperatureRgb.r,
+ g: g * 255 / temperatureRgb.g,
+ b: b * 255 / temperatureRgb.b,
+ };
+};
diff --git a/lib/pickColors.js b/lib/pickColors.js
new file mode 100644
index 00000000..2d83a1fc
--- /dev/null
+++ b/lib/pickColors.js
@@ -0,0 +1,65 @@
+import { NativeModules } from 'react-native';
+
+import { whiteBalanceWithTemperature } from './colorUtils';
+
+const ARKitManager = NativeModules.ARKitManager;
+
+const doWhiteBalance = async (colors, { includeRawColors }) => {
+ const lightEstimation = await ARKitManager.getCurrentLightEstimation();
+
+ if (!lightEstimation) {
+ return colors;
+ }
+
+ return colors.map(({ color, ...p }) => ({
+ color: whiteBalanceWithTemperature(
+ color,
+ lightEstimation.ambientColorTemperature,
+ ),
+ ...p,
+ ...(includeRawColors ? { colorRaw: color } : {}),
+ }));
+};
+export const pickColorsFromFile = async (
+ filePath,
+ {
+ whiteBalance = true,
+ includeRawColors = false,
+ // color grabber options, currently undocumented
+ range = 40,
+ dimension = 4,
+ flexibility = 5,
+ } = {},
+) => {
+ const colors = await ARKitManager.pickColorsRawFromFile(filePath, {
+ range,
+ dimension,
+ flexibility,
+ });
+ if (!whiteBalance) {
+ return colors;
+ }
+ return doWhiteBalance(colors, { includeRawColors });
+};
+export const pickColors = async (
+ {
+ whiteBalance = true,
+ includeRawColors = false,
+ selection = null,
+ // color grabber options, currently undocumented
+ range = 40,
+ dimension = 4,
+ flexibility = 5,
+ } = {},
+) => {
+ const colors = await ARKitManager.pickColorsRaw({
+ selection,
+ range,
+ dimension,
+ flexibility,
+ });
+ if (!whiteBalance) {
+ return colors;
+ }
+ return doWhiteBalance(colors, { includeRawColors });
+};
diff --git a/package.json b/package.json
index a9bb2cf5..6d322f1b 100644
--- a/package.json
+++ b/package.json
@@ -4,7 +4,7 @@
"type": "git",
"url": "https://github.com/HippoAR/react-native-arkit.git"
},
- "version": "0.3.0",
+ "version": "0.6.1-beta.1",
"description": "React Native binding for iOS ARKit",
"author": "Zehao Li ",
"license": "MIT",
@@ -14,12 +14,17 @@
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
+ "peerDependencies": {
+ "react-native": ">=0.46.0",
+ "react": "*",
+ "prop-types": "*"
+ },
"dependencies": {
+ "@panter/react-animation-frame": "^0.3.7",
+ "_PocketSVG": "https://github.com/pocketsvg/PocketSVG",
"fast-deep-equal": "^1.0.0",
"lodash": "^4.17.4",
- "prop-types": "^15.5.7",
- "react": "16.0.0-alpha.12",
- "react-animation-frame": "^0.3.5"
+ "prop-types": "^15.5.7"
},
"devDependencies": {
"babel-eslint": "^7.2.3",
@@ -32,6 +37,8 @@
"eslint-plugin-jsx-a11y": "^5.0.3",
"eslint-plugin-prettier": "^2.1.1",
"eslint-plugin-react": "^7.0.1",
- "prettier": "^1.3.1"
+ "prettier": "^1.3.1",
+ "react": "16.0.0-alpha.12",
+ "prop-types": "^15.5.8"
}
}
diff --git a/startup.js b/startup.js
new file mode 100644
index 00000000..4899c34b
--- /dev/null
+++ b/startup.js
@@ -0,0 +1,10 @@
+import { NativeModules } from 'react-native';
+
+const ARKitManager = NativeModules.ARKitManager;
+
+export default () => {
+ // when reloading the app, the scene should be cleared.
+ // on prod, this usually does not happen, but you can reload the app in develop mode
+ // without clearing, this would result in inconsistency
+ ARKitManager.clearScene();
+};
diff --git a/yarn.lock b/yarn.lock
index 3655f102..c0b0f12a 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2,6 +2,16 @@
# yarn lockfile v1
+"@panter/react-animation-frame@^0.3.7":
+ version "0.3.7"
+ resolved "https://registry.yarnpkg.com/@panter/react-animation-frame/-/react-animation-frame-0.3.7.tgz#31a0d7683e4a8d2e1b29ff512cad36513bd43d00"
+ dependencies:
+ react "15.4.2"
+
+"_PocketSVG@https://github.com/pocketsvg/PocketSVG":
+ version "0.0.0"
+ resolved "https://github.com/pocketsvg/PocketSVG#e6d84ddeb11f99c4420e354ebb9fd34db2cf507a"
+
acorn-jsx@^3.0.0:
version "3.0.1"
resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-3.0.1.tgz#afdf9488fb1ecefc8348f6fb22f464e32a58b36b"
@@ -1417,12 +1427,6 @@ pseudomap@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3"
-react-animation-frame@^0.3.5:
- version "0.3.5"
- resolved "https://registry.yarnpkg.com/react-animation-frame/-/react-animation-frame-0.3.5.tgz#6afe77e8c8b8774c56183598ba40c6de4dce4588"
- dependencies:
- react "15.4.2"
-
react-deep-force-update@^1.0.0:
version "1.1.1"
resolved "https://registry.yarnpkg.com/react-deep-force-update/-/react-deep-force-update-1.1.1.tgz#bcd31478027b64b3339f108921ab520b4313dc2c"