├── .gitignore ├── example └── hello │ ├── flint.json │ ├── shaders │ ├── cursor.frag │ └── cursor.vert │ ├── README.md │ ├── webpack.config.js │ ├── package.json │ └── hello.js ├── package.json ├── README.md ├── lib ├── ReactFlintIDOperations.js ├── ReactFlintInjection.js ├── ReactFlintReconcileTransaction.js ├── render.js └── ReactFlintComponent.js └── LICENSE.txt /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | build 3 | npm-debug.log -------------------------------------------------------------------------------- /example/hello/flint.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Hello", 3 | "files": [ 4 | "build/hello.js" 5 | ], 6 | "entrypoint": "build/hello.js" 7 | } -------------------------------------------------------------------------------- /example/hello/shaders/cursor.frag: -------------------------------------------------------------------------------- 1 | #version 300 es 2 | 3 | in lowp vec4 fragmentColor; 4 | out lowp vec4 outColor; 5 | 6 | void main() 7 | { 8 | outColor = vec4(0, 0, 0, 1); 9 | } -------------------------------------------------------------------------------- /example/hello/shaders/cursor.vert: -------------------------------------------------------------------------------- 1 | #version 300 es 2 | 3 | in vec3 Position; 4 | in vec4 VertexColor; 5 | uniform mat4 Modelm; 6 | uniform mat4 Viewm; 7 | uniform mat4 Projectionm; 8 | out vec4 fragmentColor; 9 | 10 | void main() 11 | { 12 | gl_Position = Projectionm * (Viewm * (Modelm * vec4(Position, 1.0))); 13 | fragmentColor = VertexColor; 14 | } -------------------------------------------------------------------------------- /example/hello/README.md: -------------------------------------------------------------------------------- 1 | FlintVR React Hello Example 2 | --------------------------- 3 | 4 | This is a simple app for Flint VR that puts a model on the screen, and turns 5 | it into a cursor that follows where you look. 6 | 7 | 8 | Getting Started 9 | =============== 10 | 11 | * npm install 12 | * npm run build 13 | 14 | 15 | Running 16 | ======= 17 | 18 | * npm run serve -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "flint-react", 3 | "version": "0.0.1", 4 | "description": "React bindings for FlintVR", 5 | "main": "./lib/render.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "directories": { 10 | "lib": "./lib" 11 | }, 12 | "dependencies": { 13 | "react": "~0.14.7" 14 | }, 15 | "author": "Eric Florenzano", 16 | "license": "BSD-3-Clause" 17 | } 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | flint-react 2 | ----------- 3 | 4 | This is a small shim library to wrap FlintVR with React.js bindings. 5 | 6 | 7 | How to Use 8 | ---------- 9 | 10 | Please see the example project. Basically this is just regular react 11 | but you can use 'scene' and 'model' components from FlintVR. 12 | 13 | ```javascript 14 | import { render } from 'flint-react'; 15 | 16 | global.vrmain = function(env) { 17 | render( 18 | 19 | 20 | 21 | ); 22 | } 23 | ``` -------------------------------------------------------------------------------- /example/hello/webpack.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | context: __dirname, 3 | entry: './hello.js', 4 | output: { 5 | path: __dirname + '/build', 6 | filename: 'hello.js' 7 | }, 8 | module: { 9 | loaders: [ 10 | { 11 | test: /\.jsx?$/, 12 | exclude: /(node_modules|bower_components)/, 13 | loader: 'babel', 14 | query: { 15 | presets: ['es2015', 'react'] 16 | } 17 | }, 18 | { 19 | test: /\.(vert|frag)$/, 20 | loader: 'raw' 21 | } 22 | ] 23 | } 24 | }; -------------------------------------------------------------------------------- /lib/ReactFlintIDOperations.js: -------------------------------------------------------------------------------- 1 | const blessedNodes = {}; 2 | 3 | class ReactBlessedIDOperations { 4 | add(ID, node) { 5 | blessedNodes[ID] = node; 6 | return this; 7 | } 8 | 9 | get(ID) { 10 | const numPeriods = ID.match(/\./g).length; 11 | 12 | // If the node is root, we return the scene 13 | if (numPeriods === 0) { 14 | return Flint.scene; 15 | } 16 | 17 | return blessedNodes[ID]; 18 | } 19 | 20 | getParent(ID) { 21 | const numPeriods = ID.match(/\./g).length; 22 | 23 | // If the node is root, we return null 24 | if (numPeriods === 1) { 25 | return Flint.scene; 26 | } 27 | 28 | const parentID = ID.split('.').slice(0, -1).join('.'); 29 | return this.get(parentID); 30 | } 31 | 32 | drop(ID) { 33 | delete blessedNodes[ID]; 34 | return this; 35 | } 36 | } 37 | 38 | export default new ReactBlessedIDOperations(); -------------------------------------------------------------------------------- /lib/ReactFlintInjection.js: -------------------------------------------------------------------------------- 1 | import ReactInjection from 'react/lib/ReactInjection'; 2 | import ReactComponentEnvironment from 'react/lib/ReactComponentEnvironment'; 3 | import ReactFlintReconcileTransaction from './ReactFlintReconcileTransaction'; 4 | import ReactFlintComponent from './ReactFlintComponent'; 5 | 6 | export default function inject() { 7 | 8 | ReactInjection.NativeComponent.injectGenericComponentClass( 9 | ReactFlintComponent 10 | ); 11 | 12 | ReactInjection.Updates.injectReconcileTransaction( 13 | ReactFlintReconcileTransaction 14 | ); 15 | 16 | ReactInjection.EmptyComponent.injectEmptyComponent('element'); 17 | 18 | // NOTE: we're monkeypatching ReactComponentEnvironment because 19 | // ReactInjection.Component.injectEnvironment() currently throws, 20 | // as it's already injected by ReactDOM for backward compat in 0.14 betas. 21 | // Read more: https://github.com/Yomguithereal/react-blessed/issues/5 22 | ReactComponentEnvironment.processChildrenUpdates = function () {}; 23 | ReactComponentEnvironment.replaceNodeWithMarkupByID = function () {}; 24 | } -------------------------------------------------------------------------------- /example/hello/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "flint-react-example-hello", 3 | "version": "0.0.0", 4 | "description": "Hello world for Flint VR", 5 | "main": "build/hello.js", 6 | "scripts": { 7 | "build": "webpack --config webpack.config.js", 8 | "serve": "python -m SimpleHTTPServer 8000" 9 | }, 10 | "author": "Eric Florenzano", 11 | "license": "ISC", 12 | "devDependencies": { 13 | "babel-cli": "~6.1.4", 14 | "babel-preset-es2015": "~6.1.4", 15 | "babel-preset-react": "~6.1.4", 16 | "babel-preset-stage-0": "~6.1.2", 17 | "babel-preset-stage-2": "~6.1.2", 18 | "babel-preset-stage-3": "~6.1.2", 19 | "babel-preset-stage-1": "~6.1.2", 20 | "babel-core": "~6.1.4", 21 | "babel-loader": "~6.1.0", 22 | "webpack": "~1.12.4", 23 | "raw-loader": "~0.5.1", 24 | "react": "~0.14.7", 25 | "invariant": "~2.2.0", 26 | "lodash": "~3.10.1" 27 | }, 28 | "babel": { 29 | "presets": [ 30 | "es2015", 31 | "react", 32 | "stage-0", 33 | "stage-1", 34 | "stage-2", 35 | "stage-3" 36 | ] 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /lib/ReactFlintReconcileTransaction.js: -------------------------------------------------------------------------------- 1 | import CallbackQueue from 'react/lib/CallbackQueue'; 2 | import PooledClass from 'react/lib/PooledClass'; 3 | import Transaction from 'react/lib/Transaction'; 4 | import { extend } from 'lodash'; 5 | 6 | const ON_FLINT_READY_QUEUEING = { 7 | initialize: function () { 8 | this.reactMountReady.reset(); 9 | }, 10 | close: function () { 11 | this.reactMountReady.notifyAll(); 12 | } 13 | }; 14 | 15 | function ReactFlintReconcileTransaction() { 16 | this.reinitializeTransaction(); 17 | this.reactMountReady = CallbackQueue.getPooled(null); 18 | } 19 | 20 | const Mixin = { 21 | getTransactionWrappers: function() { 22 | return [ON_FLINT_READY_QUEUEING]; 23 | }, 24 | getReactMountReady: function() { 25 | return this.reactMountReady; 26 | }, 27 | destructor: function() { 28 | CallbackQueue.release(this.reactMountReady); 29 | this.reactMountReady = null; 30 | } 31 | }; 32 | 33 | extend( 34 | ReactFlintReconcileTransaction.prototype, 35 | Transaction.Mixin, 36 | Mixin 37 | ); 38 | 39 | PooledClass.addPoolingTo(ReactFlintReconcileTransaction); 40 | 41 | export default ReactFlintReconcileTransaction; -------------------------------------------------------------------------------- /lib/render.js: -------------------------------------------------------------------------------- 1 | import ReactInstanceHandles from 'react/lib/ReactInstanceHandles'; 2 | import ReactElement from 'react/lib/ReactElement'; 3 | import ReactUpdates from 'react/lib/ReactUpdates'; 4 | import ReactFlintIDOperations from './ReactFlintIDOperations'; 5 | import invariant from 'invariant'; 6 | import instantiateReactComponent from 'react/lib/instantiateReactComponent'; 7 | import inject from './ReactFlintInjection'; 8 | 9 | inject(); 10 | 11 | function render(element) { 12 | 13 | // Is the given element valid? 14 | invariant( 15 | ReactElement.isValidElement(element), 16 | 'render(): You must pass a valid ReactElement.' 17 | ); 18 | 19 | // Creating a root id & creating the screen 20 | const id = ReactInstanceHandles.createReactRootID(); 21 | 22 | // Mounting the app 23 | const component = instantiateReactComponent(element); 24 | 25 | // The initial render is synchronous but any updates that happen during 26 | // rendering, in componentWillMount or componentDidMount, will be batched 27 | // according to the current batching strategy. 28 | ReactUpdates.batchedUpdates(() => { 29 | // Batched mount component 30 | const transaction = ReactUpdates.ReactReconcileTransaction.getPooled(); 31 | transaction.perform(() => { 32 | component.mountComponent(id, transaction, {}); 33 | }); 34 | ReactUpdates.ReactReconcileTransaction.release(transaction); 35 | }); 36 | 37 | // Returning the scene so the user can attach listeners etc. 38 | return component._instance; 39 | } 40 | 41 | export { render }; -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016, Eric Florenzano 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are 6 | met: 7 | 8 | * Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | * Redistributions in binary form must reproduce the above 11 | copyright notice, this list of conditions and the following 12 | disclaimer in the documentation and/or other materials provided 13 | with the distribution. 14 | * Neither the name of the author nor the names of other 15 | contributors may be used to endorse or promote products derived 16 | from this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /example/hello/hello.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render } from '../../lib/render'; 3 | import cursorVertexShader from './shaders/cursor.vert' 4 | import cursorFragmentShader from './shaders/cursor.frag' 5 | 6 | const { VERTEX_POSITION, VERTEX_COLOR, Geometry, Program, Vector3f, Vector4f } = Flint.Core; 7 | 8 | function getCubeGeometry() { 9 | const cubeVertices = [ 10 | VERTEX_POSITION, VERTEX_COLOR, 11 | Vector3f(-1, 1, -1), Vector4f(1, 0, 1, 1), // top 12 | Vector3f( 1, 1, -1), Vector4f(0, 1, 0, 1), 13 | Vector3f( 1, 1, 1), Vector4f(0, 0, 1, 1), 14 | Vector3f(-1, 1, 1), Vector4f(1, 0, 0, 1), 15 | Vector3f(-1, -1, -1), Vector4f(0, 0, 1, 1), // bottom 16 | Vector3f(-1, -1, 1), Vector4f(0, 1, 0, 1), 17 | Vector3f( 1, -1, 1), Vector4f(1, 0, 1, 1), 18 | Vector3f( 1, -1, -1), Vector4f(1, 0, 0, 1) 19 | ]; 20 | const cubeIndices = [ 21 | 0, 1, 2, 2, 3, 0, // top 22 | 4, 5, 6, 6, 7, 4, // bottom 23 | 2, 6, 7, 7, 1, 2, // right 24 | 0, 4, 5, 5, 3, 0, // left 25 | 3, 5, 6, 6, 2, 3, // front 26 | 0, 1, 7, 7, 4, 0 // back 27 | ]; 28 | return Geometry({ 29 | vertices: cubeVertices, 30 | indices: cubeIndices 31 | }); 32 | } 33 | 34 | const Cursor = React.createClass({ 35 | getDefaultProps: function() { 36 | return { 37 | geometry: getCubeGeometry(), 38 | program: Program(cursorVertexShader, cursorFragmentShader) 39 | }; 40 | }, 41 | 42 | handleFrame: function(ev) { 43 | const pos = ev.viewPos.add(ev.viewFwd.multiply(5)); 44 | this.refs.model.position.x = pos.x; 45 | this.refs.model.position.y = pos.y; 46 | this.refs.model.position.z = pos.z; 47 | }, 48 | 49 | render: function() { 50 | return ( 51 | 56 | 57 | ); 58 | } 59 | }); 60 | 61 | global.vrmain = function(env) { 62 | render( 63 | 64 | 65 | 66 | ); 67 | } -------------------------------------------------------------------------------- /lib/ReactFlintComponent.js: -------------------------------------------------------------------------------- 1 | import ReactMultiChild from 'react/lib/ReactMultiChild'; 2 | import ReactFlintIDOperations from './ReactFlintIDOperations'; 3 | import invariant from 'invariant'; 4 | import {extend, groupBy, startCase, filter, each} from 'lodash'; 5 | 6 | const FLINT_TYPES = { 7 | model: true, 8 | scene: true 9 | }; 10 | 11 | export default class ReactFlintComponent { 12 | constructor(tag) { 13 | this._tag = tag.toLowerCase(); 14 | this._renderedChildren = null; 15 | this._previousStyle = null; 16 | this._previousStyleCopy = null; 17 | this._rootNodeID = null; 18 | this._wrapperState = null; 19 | this._topLevelWrapper = null; 20 | this._nodeWithLegacyProperties = null; 21 | } 22 | 23 | construct(element) { 24 | this._currentElement = element; 25 | } 26 | 27 | mountComponent(rootID, transaction, context) { 28 | this._rootNodeID = rootID; 29 | 30 | const node = this.mountNode( 31 | ReactFlintIDOperations.getParent(rootID), 32 | this._currentElement 33 | ); 34 | 35 | ReactFlintIDOperations.add(rootID, node); 36 | 37 | // Get the children 38 | let childrenToUse = this._currentElement.props.children; 39 | // Make sure it's an array 40 | childrenToUse = childrenToUse === null ? [] : [].concat(childrenToUse); 41 | /* 42 | // Filter out non-Flint types for now 43 | childrenToUse = filter(childrenToUse, function(child) { 44 | print('asdf ' + child + ' ' + (typeof child)); 45 | return FLINT_TYPES[typeof child]; 46 | }); 47 | */ 48 | 49 | if (childrenToUse.length > 0) { 50 | this.mountChildren(childrenToUse, transaction, context); 51 | } 52 | } 53 | 54 | mountNode(parent, element) { 55 | const {props, type} = element, 56 | {children, ...options} = props, 57 | flintElementExists = FLINT_TYPES[type.toLowerCase()]; 58 | 59 | invariant( 60 | flintElementExists, 61 | `Invalid Flint element "${type}".` 62 | ); 63 | 64 | let node = null; 65 | if (type.toLowerCase() === 'scene') { 66 | print('scene: ' + parent); 67 | node = Flint.scene; 68 | each(options, function(value, key) { 69 | node[key] = value; 70 | }); 71 | } else { 72 | print('parent: ' + parent); 73 | node = Flint.Core[startCase(type)](options); 74 | parent.add(node); 75 | } 76 | 77 | return node; 78 | } 79 | 80 | receiveComponent(nextElement, transaction, context) { 81 | const {props: {children, ...options}} = nextElement, 82 | node = ReactFlintIDOperations.get(this._rootNodeID); 83 | 84 | // Update the properties on the node 85 | this._updating = true; 86 | each(options, function(value, key) { 87 | // TODO: Bind 'this' to the component such that FlintVR won't override it 88 | node[key] = value; 89 | }); 90 | this._updating = false; 91 | 92 | // Updating children 93 | let childrenToUse = children === null ? [] : [].concat(children); 94 | /* 95 | // Filter out non-Flint types for now 96 | childrenToUse = filter(childrenToUse, function(child) { 97 | return FLINT_TYPES[typeof child] || false; 98 | }); 99 | */ 100 | this.updateChildren(childrenToUse, transaction, context); 101 | } 102 | 103 | unmountComponent() { 104 | this.unmountChildren(); 105 | 106 | const node = ReactFlintIDOperations.get(this._rootNodeID); 107 | const parent = ReactFlintIDOperations.getParent(this._rootNodeID); 108 | parent.remove(node); 109 | 110 | ReactFlintIDOperations.drop(this._rootNodeID); 111 | 112 | this._rootNodeID = null; 113 | } 114 | 115 | getPublicInstance() { 116 | return ReactFlintIDOperations.get(this._rootNodeID); 117 | } 118 | } 119 | 120 | extend( 121 | ReactFlintComponent.prototype, 122 | ReactMultiChild.Mixin 123 | ); --------------------------------------------------------------------------------