UNPKG

expo-three

Version:

Utilities for using THREE.js on Expo

599 lines (420 loc) 17.7 kB
[![NPM](https://nodei.co/npm/expo-three.png)](https://nodei.co/npm/expo-three/) # expo-three Tools for using three.js to build native 3D experiences 💙 ### Installation ```bash yarn add three expo-three ``` ### Usage Import the library into your JavaScript file: ```js import ExpoTHREE from 'expo-three'; ``` Get a global instance of `three.js` from `expo-three`: ```js import { THREE } from 'expo-three'; ``` You can also import AR tools: ```js // This alias is useful cuz Expo.AR could collide. import { AR as ThreeAR } from 'expo-three'; ``` > `ExpoTHREE.AR` is not the same as `Expo.AR`. Think of `Expo.AR` as a data provider for `ExpoTHREE.AR` to visualize. ## Creating a Renderer ### `ExpoTHREE.Renderer({ gl: WebGLRenderingContext, width: number, height: number, pixelRatio: number, ...extras })` Given a `gl` from an [`Expo.GLView`](https://docs.expo.io/versions/latest/sdk/gl-view.html), return a [`THREE.WebGLRenderer`](https://threejs.org/docs/#api/renderers/WebGLRenderer) that draws into it. ```js const renderer = new ExpoTHREE.Renderer(props); or; /// A legacy alias for the extended renderer const renderer = ExpoTHREE.createRenderer(props); // Now just code some three.js stuff and add it to this! :D ``` ### `ExpoTHREE.loadAsync()` A function that will asynchronously load files based on their extension. > **Notice**: Remember to update your `app.json` to bundle obscure file types! ```json "packagerOpts": { "assetExts": [ "dae", "obj", "mtl" ] } ``` #### Props | Property | Type | Description | | ------------- | :-----------------------: | ---------------------------------------------------------------- | | resource | PossibleAsset | The asset that will be parsed asynchornously | | onProgress | (xhr) => void | A function that is called with an xhr event | | assetProvider | () => Promise<Expo.Asset> | A function that is called whenever an unknown asset is requested | ##### PossibleAsset Format export type PossibleAsset = Expo.Asset | number | string | AssetFormat; ```js type PossibleAsset = number | string | Expo.Asset; ``` - `number`: Static file reference `require('./model.*')` - `Expo.Asset`: [Expo.Asset](https://docs.expo.io/versions/latest/sdk/asset.html) - `string`: A uri path to an asset #### Returns This returns many different things, based on the input file. For a more predictable return value you should use one of the more specific model loaders. #### Example A list of supported formats can be found [here](/examples/loader) ```js const texture = await ExpoTHREE.loadAsync('https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_272x92dp.png'); ``` ## Loaders > Don't forget to add your extensions to `expo.packagerOpts.assetExts` in the `app.json` ### loadAsync(assetReference, onProgress, onAssetRequested) A universal loader that can be used to load images, models, scenes, and animations. Optionally more specific loaders are provided with less complexity. ```js // A THREE.Texture from a static resource. const texture = await ExpoTHREE.loadAsync(require('./icon.png')); const obj = await ExpoTHREE.loadAsync( [ require('./cartman.obj'), require('./cartman.mtl') ], null, (imageName) => resources[imageName] ); const { scene } = await ExpoTHREE.loadAsync( resources['./kenny.dae'], onProgress, resources ); ``` ### loadObjAsync({ asset, mtlAsset, materials, onAssetRequested, onMtlAssetRequested }) #### Props - `asset`: a `obj` model reference that will be evaluated using `AssetUtils.uriAsync` - `mtlAsset`: an optional prop that will be loaded using `loadMtlAsync()` - `onAssetRequested`: A callback that is used to evaluate urls found within the `asset` and optionally the `mtlAsset`. You can also just pass in a dictionary of key values if you know the assets required ahead of time. - `materials`: Optionally you can provide an array of materials returned from `loadMtlAsync()` - `onMtlAssetRequested`: If provided this will be used to request assets in `loadMtlAsync()` This function is used as a more direct method to loading a `.obj` model. You should use this function to debug when your model has a corrupted format. ```js const mesh = await loadObjAsync({ asset: 'https://www.members.com/chef.obj' }) ``` See: [MTL Loader Demo](/example/screens/Loaders/MtlLoaderExample.js) ### loadTextureAsync({ asset }) #### Props - `asset`: an `Expo.Asset` that could be evaluated using `AssetUtils.resolveAsync` if `localUri` is missing or the asset hasn't been downloaded yet. This function is used as a more direct method to loading an image into a texture. You should use this function to debug when your image is using an odd extension like `.bmp`. ```js const texture = await loadTextureAsync({ asset: require('./image.png') }) ``` ### loadMtlAsync({ asset, onAssetRequested }) #### Props - `asset`: a `mtl` material reference that will be evaluated using `AssetUtils.uriAsync` - `onAssetRequested`: A callback that is used to evaluate urls found within the `asset`, optionally you can just pass in a dictionary of key values if you know the assets required ahead of time. ```js const materials = await loadMtlAsync({ asset: require('chef.mtl'), onAssetRequested: modelAssets }) ``` See: [MTL Loader Demo](/example/screens/Loaders/MtlLoaderExample.js) ### loadDaeAsync({ asset, onAssetRequested, onProgress }) #### Props - `asset`: a reference to a `dae` scene that will be evaluated using `AssetUtils.uriAsync` - `onAssetRequested`: A callback that is used to evaluate urls found within the `asset`, optionally you can just pass in a dictionary of key values if you know the assets required ahead of time. - `onProgress`: An experimental callback used to track loading progress. ```js const { scene } = await loadDaeAsync({ asset: require('chef.dae'), onAssetRequested: modelAssets, onProgress: () => {} }) ``` See: [Collada Loader Demo](/example/screens/Loaders/DaeLoaderExample.js) ## AR Tools and utilites for working with ARKit in Expo. Here is an example of a basic AR enabled scene. [Also in snack form!](https://snack.expo.io/@bacon/basic-ar-scene) ```js import { AR } from 'expo'; import ExpoTHREE, { AR as ThreeAR, THREE } from 'expo-three'; import React from 'react'; import { View as GraphicsView } from 'expo-graphics'; export default class App extends React.Component { render() { // You need to add the `isArEnabled` & `arTrackingConfiguration` props. return ( <GraphicsView style={{ flex: 2 }} onContextCreate={this.onContextCreate} onRender={this.onRender} onResize={this.onResize} isArEnabled isArRunningStateEnabled isArCameraStateEnabled arTrackingConfiguration={AR.TrackingConfigurations.World} /> ); } onContextCreate = ({ gl, scale: pixelRatio, width, height }) => { AR.setPlaneDetection(AR.PlaneDetectionTypes.Horizontal); this.renderer = new ExpoTHREE.Renderer({ gl, pixelRatio, width, height, }); this.scene = new THREE.Scene(); this.scene.background = new ThreeAR.BackgroundTexture(this.renderer); this.camera = new ThreeAR.Camera(width, height, 0.01, 1000); }; onResize = ({ x, y, scale, width, height }) => { this.camera.aspect = width / height; this.camera.updateProjectionMatrix(); this.renderer.setPixelRatio(scale); this.renderer.setSize(width, height); }; onRender = () => { this.renderer.render(this.scene, this.camera); }; } ``` #### Enabling AR: - `Expo.GLView`: call `Expo.AR.startAsync(gl)` after `Expo.GLView.onContextCreate` has been called. - `expo-graphics`: you need to add the `isArEnabled` & `arTrackingConfiguration` props. ### `new ExpoTHREE.AR.BackgroundTexture(renderer: WebGLRenderingContext)` extends a [`THREE.Texture`](https://threejs.org/docs/#api/textures/Texture) that reflects the live video feed of the AR session. Usually this is set as the `.background` property of a [`THREE.Scene`](https://threejs.org/docs/#api/scenes/Scene) to render the video feed behind the scene's objects. ```js // viewport width/height & zNear/zFar scene.background = new ExpoTHREE.AR.BackgroundTexture(renderer); ``` See: [Basic Demo](/example/screens/AR/Basic.js) ### `new ExpoTHREE.AR.Camera(width: number, height: number, zNear: number, zFar: number)` extends a [`THREE.PerspectiveCamera`](https://threejs.org/docs/#api/cameras/PerspectiveCamera) that automatically updates its view and projection matrices to reflect the AR session camera. `width, height` specify the dimensions of the target viewport to render to and `near, far` specify the near and far clipping distances respectively. The `THREE.PerspectiveCamera` returned has its `updateMatrixWorld` and `updateProjectionMatrix` methods overriden to update to the AR session's state automatically. `THREE.PerspectiveCamera` that updates it's transform based on the device's orientation. ```js // viewport width/height & zNear/zFar const camera = new ExpoTHREE.AR.Camera(width, height, 0.01, 1000); ``` See: [Basic Demo](/example/screens/AR/Basic.js) ### `new ExpoTHREE.AR.Light()` `THREE.PointLight` that will update it's color and intensity based on ARKit's assumption of the room lighting. ```js renderer.physicallyCorrectLights = true; renderer.toneMapping = THREE.ReinhardToneMapping; const arPointLight = new ExpoTHREE.AR.Light(); arPointLight.position.y = 2; scene.add(arPointLight); // You should also add a Directional for shadows const shadowLight = new THREE.DirectionalLight(); scene.add(shadowLight); // If you would like to move the light (you would) then you will need to add the lights `target` to the scene. // The shadowLight.position adjusts one side of the light vector, and the target.position represents the other. scene.add(shadowLight.target); ... // Call this every frame: arPointLight.update() ``` See: [Model Demo](/example/screens/AR/Model.js) ### `new ExpoTHREE.AR.MagneticObject()` A `THREE.Mesh` that sticks to surfaces. Use this as a parent to models that you want to attach to surfaces. ```js const magneticObject = new ExpoTHREE.AR.MagneticObject(); magneticObject.maintainScale = false; // This will scale the mesh up/down to preserve it's size regardless of distance. magneticObject.maintainRotation = true; // When true the mesh will orient itself to face the camera. // screenCenter is a normalized value = { 0.5, 0.5 } const screenCenter = new THREE.Vector2(0.5, 0.5); ... // Call this every frame to update the position. magneticObject.update(camera, screenCenter); ``` See: [Model Demo](/example/screens/AR/Model.js) ### `new ExpoTHREE.AR.ShadowFloor()` A transparent plane that extends `THREE.Mesh` and receives shadows from other meshes. This is used to render shadows on real world surfaces. ```js renderer.gammaInput = true; renderer.gammaOutput = true; renderer.shadowMap.enabled = true; const shadowFloor = new ExpoTHREE.AR.ShadowFloor({ width: 1, height: 1, opacity: 0.6 }); // The opacity of the shadow ``` See: [Model Demo](/example/screens/AR/Model.js) ### `new ExpoTHREE.AR.CubeTexture()` Used to load in a texture cube or skybox. - `assetForDirection`: This function will be called for each of the 6 directions. - `({ direction })`: A direction string will be passed back looking for the corresponding image. You can send back: `static resource`, `localUri`, `Expo.Asset`, `remote image url` - `directions`: The order that image will be requested in. The default value is: `['px', 'nx', 'py', 'ny', 'pz', 'nz']` Example: ```js const skybox = { nx: require('./nx.jpg'), ny: require('./ny.jpg'), nz: require('./nz.jpg'), px: require('./px.jpg'), py: require('./py.jpg'), pz: require('./pz.jpg') } const cubeTexture = new CubeTexture() await cubeTexture.loadAsync({assetForDirection: ({ direction }) => skybox[direction]}) scene.background = cubeTexture ``` ### `new ExpoTHREE.AR.Points()` A utility object that renders all the raw feature points. ```js const points = new ExpoTHREE.AR.Points(); // Then call this each frame... points.update(); ``` See: [Points Demo](/example/screens/AR/Points.js) ### `new ExpoTHREE.AR.Planes()` A utility object that renders all the ARPlaneAnchors ```js const planes = new ExpoTHREE.AR.Planes(); // Then call this each frame... planes.update(); ``` See: [Planes Demo](/example/screens/AR/Planes.js) ## AR Functions Three.js calculation utilites for working in ARKit. Most of these functions are used for calculating the surfaces. You should see if `ExpoTHREE.AR.MagneticObject()` has what you need before digging into these. [You can also check out this example provided by Apple](https://developer.apple.com/sample-code/wwdc/2017/PlacingObjects.zip) ### hitTestWithFeatures(camera: THREE.Camera, point: THREE.Vector2, coneOpeningAngleInDegrees: number, minDistance: number, maxDistance: number, rawFeaturePoints: Array<any>) #### Props - camera: THREE.Camera - point: THREE.Vector2 - coneOpeningAngleInDegrees: number - minDistance: number - maxDistance: number - rawFeaturePoints: Array<any> ### hitTestWithPoint(camera: THREE.Camera, point: THREE.Vector2) #### Props - camera: THREE.Camera - point: THREE.Vector2 ### unprojectPoint(camera: THREE.Camera, point: THREE.Vector2) #### Props - camera: THREE.Camera - point: THREE.Vector2 ### hitTestRayFromScreenPos(camera: THREE.Camera, point: THREE.Vector2) #### Props - camera: THREE.Camera - point: THREE.Vector2 ### hitTestFromOrigin(origin: THREE.Vector3, direction: THREE.Vector3, rawFeaturePoints: ?Array<any>) #### Props - origin: THREE.Vector3 - direction: THREE.Vector3 - rawFeaturePoints: ?Array<any> ### hitTestWithInfiniteHorizontalPlane(camera: THREE.Camera, point: Point, pointOnPlane: THREE.Vector3) #### Props - camera: THREE.Camera - point: THREE.Vector2 - pointOnPlane: THREE.Vector3 ### rayIntersectionWithHorizontalPlane(rayOrigin: THREE.Vector3, direction: THREE.Vector3, planeY: number) #### Props - rayOrigin: THREE.Vector3 - direction: THREE.Vector3 - planeY: number ### convertTransformArray(transform: Array<number>): THREE.Matrix4 #### Props - transform: Array<number> ### positionFromTransform(transform: THREE.Matrix4): THREE.Vector3 #### Props - transform: THREE.Matrix4 ### worldPositionFromScreenPosition(camera: THREE.Camera, position: THREE.Vector2, objectPos: THREE.Vector3, infinitePlane = false, dragOnInfinitePlanesEnabled = false, rawFeaturePoints = null): { worldPosition: THREE.Vector3, planeAnchor: ARPlaneAnchor, hitAPlane: boolean } #### Props - camera: THREE.Camera - position: THREE.Vector2 - objectPos: THREE.Vector3 - infinitePlane = false - dragOnInfinitePlanesEnabled = false - rawFeaturePoints = null ### positionFromAnchor(anchor: ARAnchor): THREE.Vector3 #### Props - anchor: { worldTransform: Matrix4 } ### improviseHitTest(point, camera: THREE.Camera): ?THREE.Vector3 #### Props - point: THREE.Vector2 - camera: THREE.Camera --- ## `ExpoTHREE.utils` ### `ExpoTHREE.utils.alignMesh()` #### Props ```js type Axis = { x?: number, y?: number, z?: number, }; ``` | Property | Type | Description | | -------- | :---------: | --------------------------------- | | mesh | &THREE.Mesh | The mesh that will be manipulated | | axis | ?Axis | Set the relative center axis | #### Example ```js ExpoTHREE.utils.alignMesh(mesh, { x: 0.0, y: 0.5 }); ``` --- ### `ExpoTHREE.utils.scaleLongestSideToSize()` #### Props | Property | Type | Description | | -------- | :---------: | ------------------------------------------------------------ | | mesh | &THREE.Mesh | The mesh that will be manipulated | | size | number | The size that the longest side of the mesh will be scaled to | #### Example ```js ExpoTHREE.utils.scaleLongestSideToSize(mesh, 3.2); ``` --- ### `ExpoTHREE.utils.computeMeshNormals()` Used for smoothing imported geometry, specifically when imported from `.obj` models. #### Props | Property | Type | Description | | -------- | :---------: | ------------------------------------------------- | | mesh | &THREE.Mesh | The mutable (inout) mesh that will be manipulated | #### Example ````js ExpoTHREE.utils.computeMeshNormals(mesh); ``` --- ## THREE Extensions ### `suppressExpoWarnings` A function that suppresses EXGL compatibility warnings and logs them instead. You will need to import the `ExpoTHREE.THREE` global instance to use this. By default this function will be activated on import. * `shouldSuppress`: boolean ```js import { THREE } from 'expo-three'; THREE.suppressExpoWarnings(); ```` --- ## Links Somewhat out of date - [Loading Text](https://github.com/EvanBacon/expo-three-text) - [ARKit Tutorial](https://blog.expo.io/introducing-expo-ar-mobile-augmented-reality-with-javascript-powered-by-arkit-b0d5a02ff23) - [ARKit Example](https://snack.expo.io/@bacon/arkit-example) - [three.js docs](https://threejs.org/docs/) - [Random Demos](https://github.com/EvanBacon/expo-three-demo) - [Game: Expo Sunset Cyberspace](https://github.com/EvanBacon/Sunset-Cyberspace) - [Game: Crossy Road](https://github.com/EvanBacon/Expo-Crossy-Road) - [Game: Nitro Roll](https://github.com/EvanBacon/Expo-Nitro-Roll) - [Game: Pillar Valley](https://github.com/EvanBacon/Expo-Pillar-Valley) - [Voxel Terrain](https://github.com/EvanBacon/Expo-Voxel)