From 448ba231caeb22c6ecafccc984427980ecf189e3 Mon Sep 17 00:00:00 2001 From: Mike Armstrong Date: Tue, 6 Jun 2017 10:34:24 -0700 Subject: [PATCH] Environment facing camera displayed full screen Summary: A simple component that when dropped into the scene displaces the environment facing camera Reviewed By: amberroy Differential Revision: D5144803 fbshipit-source-id: 2175809 --- Libraries/Camera/LiveEnvCamera.js | 72 +++++++++++++ Libraries/react-vr.js | 3 + ReactVR/js/Modules/UIManager.js | 4 + ReactVR/js/Views/LiveEnvCamera.js | 161 ++++++++++++++++++++++++++++++ website/server/docsList.js | 1 + 5 files changed, 241 insertions(+) create mode 100755 Libraries/Camera/LiveEnvCamera.js create mode 100755 ReactVR/js/Views/LiveEnvCamera.js diff --git a/Libraries/Camera/LiveEnvCamera.js b/Libraries/Camera/LiveEnvCamera.js new file mode 100755 index 000000000..021668cb4 --- /dev/null +++ b/Libraries/Camera/LiveEnvCamera.js @@ -0,0 +1,72 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @providesModule LiveEnvCamera + */ +'use strict'; + +const NativeMethodsMixin = require('NativeMethodsMixin'); +const React = require('React'); +const ReactNativeViewAttributes = require('ReactNativeViewAttributes'); +const View = require('View'); +const requireNativeComponent = require('requireNativeComponent'); +const StyleSheetPropType = require('StyleSheetPropType'); +const LayoutAndTransformPropTypes = require('LayoutAndTransformPropTypes'); + +/** + * Displays the environment facing camera. By default the camera is position:'absolute' + * `` + * The camera image is displayed on geometry that is 1000m away from the viewer + */ +const LiveEnvCamera = React.createClass({ + mixins: [NativeMethodsMixin], + + propTypes: { + ...View.propTypes, + style: StyleSheetPropType(LayoutAndTransformPropTypes), + }, + + viewConfig: { + uiViewClassName: 'LiveEnvCamera', + validAttributes: { + ...ReactNativeViewAttributes.RCTView, + }, + }, + + getDefaultProps: function() { + return {}; + }, + + render: function() { + const props = {...this.props} || {}; + props.style = props.style || {}; + if (!props.style.position) { + props.style.position = 'absolute'; + } + // default panos to being a render group + if (!props.style.renderGroup) { + props.style.renderGroup = true; + } + + return ( + true} + onResponderTerminationRequest={() => false}> + {this.props.children} + + ); + }, +}); + +const RKLiveEnvCamera = requireNativeComponent('LiveEnvCamera', LiveEnvCamera, { + nativeOnly: {}, +}); + +module.exports = LiveEnvCamera; diff --git a/Libraries/react-vr.js b/Libraries/react-vr.js index bebf23e53..a16a927a3 100644 --- a/Libraries/react-vr.js +++ b/Libraries/react-vr.js @@ -28,6 +28,9 @@ const ReactVR = { get Box() { return require('Box'); }, + get LiveEnvCamera() { + return require('LiveEnvCamera'); + }, get Cylinder() { return require('Cylinder'); }, diff --git a/ReactVR/js/Modules/UIManager.js b/ReactVR/js/Modules/UIManager.js index 53c936dbf..6d820d0fc 100755 --- a/ReactVR/js/Modules/UIManager.js +++ b/ReactVR/js/Modules/UIManager.js @@ -16,6 +16,7 @@ import RCTSphere from '../Views/Sphere'; import RCTImage from '../Views/Image'; import RCTView from '../Views/View'; import RCTPano from '../Views/Pano'; +import RCTLiveEnvCamera from '../Views/LiveEnvCamera'; import RCTModel from '../Views/Model'; import RCTScene from '../Views/Scene'; import RCTSound from '../Views/Sound'; @@ -177,6 +178,9 @@ export default class UIManager extends Module { this.registerViewType('RCTView', RCTView.describe(), function() { return new RCTView(guiSys); }); + this.registerViewType('LiveEnvCamera', RCTLiveEnvCamera.describe(), function() { + return new RCTLiveEnvCamera(guiSys); + }); this.registerViewType('RCTImageView', RCTImage.describe(), function() { return new RCTImage(guiSys, rnctx); }); diff --git a/ReactVR/js/Views/LiveEnvCamera.js b/ReactVR/js/Views/LiveEnvCamera.js new file mode 100755 index 000000000..be5d7f125 --- /dev/null +++ b/ReactVR/js/Views/LiveEnvCamera.js @@ -0,0 +1,161 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +/** + * RCTLiveEnvCamera + * Displays the environment facing camera on a sphere + * @class RCTLiveEnvCamera + * @extends RCTBaseView + */ + +import RCTBaseView from './BaseView'; +import merge from '../Utils/merge'; +import * as OVRUI from 'ovrui'; +import * as THREE from 'three'; +import * as Yoga from '../Utils/Yoga.bundle'; + +// display texture always infront of the camera +const basic_vert = ` + varying highp vec4 vUv; + void main() + { + vUv = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); + vUv.xy = (vUv.xy + vec2(vUv.w)) * 0.5; + gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); + } + `; + +const basic_frag = ` + uniform sampler2D map; + varying highp vec4 vUv; + void main() + { + gl_FragColor = texture2DProj( map, vUv ); + } + `; + +const SPHERE_RADIUS = 1000; + +const sphereRayCast = (function() { + // avoid create temp objects; + const inverseMatrix = new THREE.Matrix4(); + const ray = new THREE.Ray(); + const sphere = new THREE.Sphere(new THREE.Vector3(0, 0, 0), SPHERE_RADIUS); + const intersectionPoint = new THREE.Vector3(); + const intersectionPointWorld = new THREE.Vector3(); + return function(raycaster, intersects) { + // transform the ray into the space of the sphere + inverseMatrix.getInverse(this.matrixWorld); + ray.copy(raycaster.ray).applyMatrix4(inverseMatrix); + const intersect = ray.intersectSphere(sphere, intersectionPoint); + if (intersect === null) { + return; + } + + // determine hit location in world space + intersectionPointWorld.copy(intersectionPoint); + intersectionPointWorld.applyMatrix4(this.matrixWorld); + + const distance = raycaster.ray.origin.distanceTo(intersectionPointWorld); + if (distance < raycaster.near || distance > raycaster.far) { + return; + } + + intersects.push({ + distance: distance, + point: intersectionPointWorld.clone(), + object: this, + }); + }; +})(); + +export default class RCTLiveEnvCamera extends RCTBaseView { + /** + * constructor: allocates the required resources and sets defaults + */ + constructor(guiSys, rnctx) { + super(); + + navigator.getUserMedia = + navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia; + + const constraints = { + video: {facingMode: {exact: 'environment'}}, + }; + const video = document.createElement('video'); + const videoTexture = new THREE.Texture(video); + videoTexture.minFilter = THREE.LinearFilter; + this._video = video; + this._videoTexture = videoTexture; + navigator.getUserMedia( + constraints, + stream => { + video.src = window.URL.createObjectURL(stream); + }, + error => { + console.log('navigator.getUserMedia error: ', error); + } + ); + + this._sphereGeometry = new THREE.SphereGeometry(SPHERE_RADIUS, 5, 5); + this._material = new THREE.ShaderMaterial({ + uniforms: { + map: { + value: videoTexture, + type: 't', + }, + }, + vertexShader: basic_vert, + fragmentShader: basic_frag, + side: THREE.DoubleSide, + }); + + this._onUpdate = this._onUpdate.bind(this); + + this._globe = new THREE.Mesh(this._sphereGeometry, this._material); + this._globe.raycast = sphereRayCast.bind(this._globe); + this._globe.rotation.y = -Math.PI / 2; + this._globe.onUpdate = this._onUpdate; + + this.view = new OVRUI.UIView(guiSys); + this.view.add(this._globe); + } + + _onUpdate(scene, camera) { + if (this._video.readyState === this._video.HAVE_ENOUGH_DATA) { + this._videoTexture.needsUpdate = true; + } + } + + presentLayout() { + super.presentLayout(); + this._globe.visible = this.YGNode.getDisplay() !== Yoga.DISPLAY_NONE; + } + + /** + * Dispose of any associated resources + */ + dispose() { + if (this._localResource) { + this._localResource.dispose(); + } + super.dispose(); + } + + /** + * Describes the properties representable by this view type and merges + * with super type + */ + static describe() { + return merge(super.describe(), { + // declare the native props sent from react to runtime + NativeProps: {}, + }); + } +} diff --git a/website/server/docsList.js b/website/server/docsList.js index 8267bb24d..ed487094c 100644 --- a/website/server/docsList.js +++ b/website/server/docsList.js @@ -21,6 +21,7 @@ path.join(rn, 'Libraries/Text/Text.js'), const components = [ '../Libraries/Lights/AmbientLight.js', '../Libraries/Mesh/Box.js', +'../Libraries/Camera/LiveEnvCamera.js', '../Libraries/Mesh/Cylinder.js', '../Libraries/VRLayers/CylindricalPanel.js', '../Libraries/Lights/DirectionalLight.js',