├── .gitignore ├── .jshintrc ├── .npmignore ├── .travis.yml ├── LICENSE ├── NOTICE ├── README.md ├── RELEASE-NOTES.md ├── build-clojars.js ├── docs ├── react-three-devshot.png └── react-three-interactiveexample.png ├── examples ├── assets │ ├── cherry.png │ ├── creamPink.png │ ├── cupCake.png │ ├── lollipopGreen.png │ └── spherePanorama.jpg ├── camera-up │ ├── camera-up.html │ └── camera-up.js ├── cupcake │ ├── cupcake.html │ └── cupcake.js ├── helpers │ ├── helpers.html │ └── helpers.js ├── interactive │ ├── interactive.html │ └── interactive.js ├── jsxtransform │ ├── jsxtransform.html │ └── jsxtransform.jsx ├── orbitcontrols │ ├── orbitcontrols.html │ └── orbitcontrols.js ├── shader │ ├── fragment_shader.glsl │ ├── shader.html │ ├── shader.jsx │ └── vertex_shader.glsl └── spherical_panorama │ ├── spherical_panorama.html │ └── spherical_panorama.js ├── index.html ├── karma.conf-withrender.js ├── karma.conf.js ├── package.json ├── src ├── Constants.js ├── ReactTHREE.js ├── ReactTHREEMonkeyPatch.js ├── Utils.js ├── components │ ├── THREERenderer.js │ ├── THREEScene.js │ ├── cameras │ │ ├── THREEOrthographicCamera.js │ │ └── THREEPerspectiveCamera.js │ ├── extras │ │ └── THREEDecoratorHelper.js │ ├── lights │ │ ├── CommonShadowmapProps.js │ │ ├── THREEAmbientLight.js │ │ ├── THREEAreaLight.js │ │ ├── THREEDirectionalLight.js │ │ ├── THREEHemisphereLight.js │ │ ├── THREEPointLight.js │ │ └── THREESpotLight.js │ └── objects │ │ ├── THREEAxisHelper.js │ │ ├── THREELine.js │ │ ├── THREELineSegments.js │ │ ├── THREEMesh.js │ │ ├── THREEObject3D.js │ │ ├── THREEPointCloud.js │ │ ├── THREESkinnedMesh.js │ │ └── THREESprite.js ├── deps.cljs ├── mixins │ ├── LightObjectMixin.js │ ├── THREEContainerMixin.js │ └── THREEObject3DMixin.js ├── project_template.clj └── react-three-exposeglobals.js ├── test ├── basics │ └── tests.js ├── components │ ├── composite.js │ ├── mesh.js │ ├── object3d.js │ └── scene.js ├── createTestFixtureMountPoint.js └── pixels │ ├── generatetestrender.html │ ├── generatetestrender.js │ ├── pixelTests.js │ ├── testimage.png │ ├── testrender0.png │ ├── testrender1.png │ ├── testrender2.png │ ├── testrender3.png │ ├── testrender4.png │ ├── testrender5.png │ └── testrender6.png ├── vendor ├── OrbitControls.js ├── lodash.min.js └── phantomjs-shims.js ├── webpack-commonjs.config.js ├── webpack-examples.config.js └── webpack.config.js /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | *.swp 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | *~ 11 | 12 | # Directory for instrumented libs generated by jscoverage/JSCover 13 | lib-cov 14 | 15 | # Coverage directory used by tools like istanbul 16 | coverage 17 | 18 | # don't checkin files generated from jsx 19 | 20 | build 21 | dist 22 | dist-clojars/ 23 | es5 24 | 25 | examples/jsxtransform/jsxtransform.js 26 | 27 | # Dependency directory 28 | # Deployed apps should consider commenting this line out: 29 | # see https://npmjs.org/doc/faq.html#Should-I-check-my-node_modules-folder-into-git 30 | node_modules 31 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "unused": true, 3 | "undef" : true, 4 | "globalstrict": true, 5 | "browser":true, 6 | "globals" : { 7 | "module" : false, 8 | "require" : false 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | test/ 2 | examples/ 3 | docs/ 4 | build/ 5 | gulpfile.js 6 | index.html 7 | bower.json 8 | bower_components/ 9 | .jshintrc 10 | .travis.yml 11 | 12 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | node_js: 4 | - "0.12" 5 | before_script: 6 | - export DISPLAY=:99.0 7 | - sh -e /etc/init.d/xvfb start 8 | 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Unless noted otherwise in the file NOTICE, files in react-three are 2 | Copyright (c) 2014 Gary Haussmann and covered by the Apache License, Version 2.0 3 | 4 | Apache License 5 | Version 2.0, January 2004 6 | http://www.apache.org/licenses/ 7 | 8 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 9 | 10 | 1. Definitions. 11 | 12 | "License" shall mean the terms and conditions for use, reproduction, 13 | and distribution as defined by Sections 1 through 9 of this document. 14 | 15 | "Licensor" shall mean the copyright owner or entity authorized by 16 | the copyright owner that is granting the License. 17 | 18 | "Legal Entity" shall mean the union of the acting entity and all 19 | other entities that control, are controlled by, or are under common 20 | control with that entity. For the purposes of this definition, 21 | "control" means (i) the power, direct or indirect, to cause the 22 | direction or management of such entity, whether by contract or 23 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 24 | outstanding shares, or (iii) beneficial ownership of such entity. 25 | 26 | "You" (or "Your") shall mean an individual or Legal Entity 27 | exercising permissions granted by this License. 28 | 29 | "Source" form shall mean the preferred form for making modifications, 30 | including but not limited to software source code, documentation 31 | source, and configuration files. 32 | 33 | "Object" form shall mean any form resulting from mechanical 34 | transformation or translation of a Source form, including but 35 | not limited to compiled object code, generated documentation, 36 | and conversions to other media types. 37 | 38 | "Work" shall mean the work of authorship, whether in Source or 39 | Object form, made available under the License, as indicated by a 40 | copyright notice that is included in or attached to the work 41 | (an example is provided in the Appendix below). 42 | 43 | "Derivative Works" shall mean any work, whether in Source or Object 44 | form, that is based on (or derived from) the Work and for which the 45 | editorial revisions, annotations, elaborations, or other modifications 46 | represent, as a whole, an original work of authorship. For the purposes 47 | of this License, Derivative Works shall not include works that remain 48 | separable from, or merely link (or bind by name) to the interfaces of, 49 | the Work and Derivative Works thereof. 50 | 51 | "Contribution" shall mean any work of authorship, including 52 | the original version of the Work and any modifications or additions 53 | to that Work or Derivative Works thereof, that is intentionally 54 | submitted to Licensor for inclusion in the Work by the copyright owner 55 | or by an individual or Legal Entity authorized to submit on behalf of 56 | the copyright owner. For the purposes of this definition, "submitted" 57 | means any form of electronic, verbal, or written communication sent 58 | to the Licensor or its representatives, including but not limited to 59 | communication on electronic mailing lists, source code control systems, 60 | and issue tracking systems that are managed by, or on behalf of, the 61 | Licensor for the purpose of discussing and improving the Work, but 62 | excluding communication that is conspicuously marked or otherwise 63 | designated in writing by the copyright owner as "Not a Contribution." 64 | 65 | "Contributor" shall mean Licensor and any individual or Legal Entity 66 | on behalf of whom a Contribution has been received by Licensor and 67 | subsequently incorporated within the Work. 68 | 69 | 2. Grant of Copyright License. Subject to the terms and conditions of 70 | this License, each Contributor hereby grants to You a perpetual, 71 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 72 | copyright license to reproduce, prepare Derivative Works of, 73 | publicly display, publicly perform, sublicense, and distribute the 74 | Work and such Derivative Works in Source or Object form. 75 | 76 | 3. Grant of Patent License. Subject to the terms and conditions of 77 | this License, each Contributor hereby grants to You a perpetual, 78 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 79 | (except as stated in this section) patent license to make, have made, 80 | use, offer to sell, sell, import, and otherwise transfer the Work, 81 | where such license applies only to those patent claims licensable 82 | by such Contributor that are necessarily infringed by their 83 | Contribution(s) alone or by combination of their Contribution(s) 84 | with the Work to which such Contribution(s) was submitted. If You 85 | institute patent litigation against any entity (including a 86 | cross-claim or counterclaim in a lawsuit) alleging that the Work 87 | or a Contribution incorporated within the Work constitutes direct 88 | or contributory patent infringement, then any patent licenses 89 | granted to You under this License for that Work shall terminate 90 | as of the date such litigation is filed. 91 | 92 | 4. Redistribution. You may reproduce and distribute copies of the 93 | Work or Derivative Works thereof in any medium, with or without 94 | modifications, and in Source or Object form, provided that You 95 | meet the following conditions: 96 | 97 | (a) You must give any other recipients of the Work or 98 | Derivative Works a copy of this License; and 99 | 100 | (b) You must cause any modified files to carry prominent notices 101 | stating that You changed the files; and 102 | 103 | (c) You must retain, in the Source form of any Derivative Works 104 | that You distribute, all copyright, patent, trademark, and 105 | attribution notices from the Source form of the Work, 106 | excluding those notices that do not pertain to any part of 107 | the Derivative Works; and 108 | 109 | (d) If the Work includes a "NOTICE" text file as part of its 110 | distribution, then any Derivative Works that You distribute must 111 | include a readable copy of the attribution notices contained 112 | within such NOTICE file, excluding those notices that do not 113 | pertain to any part of the Derivative Works, in at least one 114 | of the following places: within a NOTICE text file distributed 115 | as part of the Derivative Works; within the Source form or 116 | documentation, if provided along with the Derivative Works; or, 117 | within a display generated by the Derivative Works, if and 118 | wherever such third-party notices normally appear. The contents 119 | of the NOTICE file are for informational purposes only and 120 | do not modify the License. You may add Your own attribution 121 | notices within Derivative Works that You distribute, alongside 122 | or as an addendum to the NOTICE text from the Work, provided 123 | that such additional attribution notices cannot be construed 124 | as modifying the License. 125 | 126 | You may add Your own copyright statement to Your modifications and 127 | may provide additional or different license terms and conditions 128 | for use, reproduction, or distribution of Your modifications, or 129 | for any such Derivative Works as a whole, provided Your use, 130 | reproduction, and distribution of the Work otherwise complies with 131 | the conditions stated in this License. 132 | 133 | 5. Submission of Contributions. Unless You explicitly state otherwise, 134 | any Contribution intentionally submitted for inclusion in the Work 135 | by You to the Licensor shall be under the terms and conditions of 136 | this License, without any additional terms or conditions. 137 | Notwithstanding the above, nothing herein shall supersede or modify 138 | the terms of any separate license agreement you may have executed 139 | with Licensor regarding such Contributions. 140 | 141 | 6. Trademarks. This License does not grant permission to use the trade 142 | names, trademarks, service marks, or product names of the Licensor, 143 | except as required for reasonable and customary use in describing the 144 | origin of the Work and reproducing the content of the NOTICE file. 145 | 146 | 7. Disclaimer of Warranty. Unless required by applicable law or 147 | agreed to in writing, Licensor provides the Work (and each 148 | Contributor provides its Contributions) on an "AS IS" BASIS, 149 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 150 | implied, including, without limitation, any warranties or conditions 151 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 152 | PARTICULAR PURPOSE. You are solely responsible for determining the 153 | appropriateness of using or redistributing the Work and assume any 154 | risks associated with Your exercise of permissions under this License. 155 | 156 | 8. Limitation of Liability. In no event and under no legal theory, 157 | whether in tort (including negligence), contract, or otherwise, 158 | unless required by applicable law (such as deliberate and grossly 159 | negligent acts) or agreed to in writing, shall any Contributor be 160 | liable to You for damages, including any direct, indirect, special, 161 | incidental, or consequential damages of any character arising as a 162 | result of this License or out of the use or inability to use the 163 | Work (including but not limited to damages for loss of goodwill, 164 | work stoppage, computer failure or malfunction, or any and all 165 | other commercial damages or losses), even if such Contributor 166 | has been advised of the possibility of such damages. 167 | 168 | 9. Accepting Warranty or Additional Liability. While redistributing 169 | the Work or Derivative Works thereof, You may choose to offer, 170 | and charge a fee for, acceptance of support, warranty, indemnity, 171 | or other liability obligations and/or rights consistent with this 172 | License. However, in accepting such obligations, You may act only 173 | on Your own behalf and on Your sole responsibility, not on behalf 174 | of any other Contributor, and only if You agree to indemnify, 175 | defend, and hold each Contributor harmless for any liability 176 | incurred by, or claims asserted against, such Contributor by reason 177 | of your accepting any such warranty or additional liability. 178 | 179 | END OF TERMS AND CONDITIONS 180 | 181 | APPENDIX: How to apply the Apache License to your work. 182 | 183 | To apply the Apache License to your work, attach the following 184 | boilerplate notice, with the fields enclosed by brackets "{}" 185 | replaced with your own identifying information. (Don't include 186 | the brackets!) The text should be enclosed in the appropriate 187 | comment syntax for the file format. We also recommend that a 188 | file or class name and description of purpose be included on the 189 | same "printed page" as the copyright notice for easier 190 | identification within third-party archives. 191 | 192 | Copyright {yyyy} {name of copyright owner} 193 | 194 | Licensed under the Apache License, Version 2.0 (the "License"); 195 | you may not use this file except in compliance with the License. 196 | You may obtain a copy of the License at 197 | 198 | http://www.apache.org/licenses/LICENSE-2.0 199 | 200 | Unless required by applicable law or agreed to in writing, software 201 | distributed under the License is distributed on an "AS IS" BASIS, 202 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 203 | See the License for the specific language governing permissions and 204 | limitations under the License. 205 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | The Comic Neue font is public domain create by Craig Rozynski (comicneue.Comic) 2 | 3 | The following bitmap font files derived from Comic Neue are public domain: 4 | - examples/assets/comic_neue_angular_bold.fnt 5 | - examples/assets/comic_neue_angular_bold_0.png 6 | 7 | The following graphics files in examples/assets are CC0 (public domain), created by Kenney Vleugels (www.kenney.nl): 8 | - bg_castle.png 9 | - cupCake.png 10 | - cherry.png 11 | - creamChoco.png 12 | - creamMocca.png 13 | - creamPink.png 14 | - creamVanilla.png 15 | 16 | The following files are Copyright 2013-2014 Facebook, Inc. and included under the Apache License v2.0: 17 | - vendor/phantomjs-shims.phantomjs 18 | 19 | The following files are Copyright 2010-2014 three.js authors and included under the MIT License: 20 | - vendor/three.js 21 | - vendor/three.min.js 22 | 23 | The following file is Copyright 2012-2013 The Dojo Foundation and included under the MIT License: 24 | - vendor/lodash.min.js 25 | 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | react-three-legacy 2 | ================== 3 | 4 | Create/control a [three.js](http://threejs.org/) canvas using [React](https://github.com/facebook/react). 5 | 6 | To use React for drawing 2D using WebGL, try [react-pixi](https://github.com/Izzimach/react-pixi). 7 | 8 | This react-three is deprecated 9 | ============================== 10 | 11 | For your React 3D needs this legacy package has been replaced by another package (react-three-fiber) which 12 | has replaced the old react-three packages on npm. I strongly suggest you update your code to work with react-three-fiber. 13 | If not, you can still use this library via github links or as the npm package ```react-three-legacy``` 14 | -------------------------------------------------------------------------------- /RELEASE-NOTES.md: -------------------------------------------------------------------------------- 1 | 0.8.0 2 | ===== 3 | Now use separate renderer and scene components - gilbox 4 | Replaced 'dev' build/webserver with 'examples' 5 | Add opengl shader examples - oveddan 6 | 7 | 0.7.5 8 | ===== 9 | 10 | * dependency and doc fixes - masonicboom 11 | * copy `fog` prop over - agrande 12 | 13 | 0.7.4 14 | ===== 15 | 16 | * make the React version dependency less strict. I hope this doesn't cause problems later! 17 | -------------------------------------------------------------------------------- /build-clojars.js: -------------------------------------------------------------------------------- 1 | // 2 | // generate directory tree so that the js code can be 3 | // deployed to clojars.org for use as a clojurescript library 4 | // 5 | // after running this script you need to cd into dist-clojars/ and 6 | // run 'lein deploy' 7 | 8 | var fs = require('fs'); 9 | var rimraf = require('rimraf'); 10 | var _ = require('lodash'); 11 | var pkg = require('./package.json'); 12 | 13 | // 14 | // for deploy to clojars we need: 15 | // - project.clj with the version set properly 16 | // - src/deps.cljs 17 | // - react-three code in react_three 18 | // 19 | // we should really make minimized versions and an extern file, but for 20 | // now we'll just use the default js file 21 | 22 | var copystuff = function(frompath, topath) { 23 | fs.createReadStream(frompath).pipe(fs.createWriteStream(topath)); 24 | }; 25 | 26 | // first nuke the current dist-clojars and rebuild directories 27 | rimraf('dist-clojars', function (err) { 28 | if (err) { console.log('Error removing old dist-clojars'); } 29 | else { 30 | fs.mkdirSync('dist-clojars'); 31 | fs.mkdirSync('dist-clojars/src'); 32 | fs.mkdirSync('dist-clojars/src/react_three'); 33 | 34 | // copy over deps.cljs 35 | copystuff('src/deps.cljs', 'dist-clojars/src/deps.cljs'); 36 | 37 | // copy over react-three code 38 | copystuff('build/react-three.js', 'dist-clojars/src/react_three/react-three.js'); 39 | 40 | // copy project.clj and insert version 41 | var projectfile_template = _.template(fs.readFileSync('src/project_template.clj')); 42 | var projectfile_formatted = projectfile_template(pkg); 43 | fs.writeFileSync('dist-clojars/project.clj', projectfile_formatted); 44 | } 45 | }); 46 | 47 | 48 | -------------------------------------------------------------------------------- /docs/react-three-devshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Izzimach/react-three-legacy/3ede927e66db1261794402acafc52968f4791d01/docs/react-three-devshot.png -------------------------------------------------------------------------------- /docs/react-three-interactiveexample.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Izzimach/react-three-legacy/3ede927e66db1261794402acafc52968f4791d01/docs/react-three-interactiveexample.png -------------------------------------------------------------------------------- /examples/assets/cherry.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Izzimach/react-three-legacy/3ede927e66db1261794402acafc52968f4791d01/examples/assets/cherry.png -------------------------------------------------------------------------------- /examples/assets/creamPink.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Izzimach/react-three-legacy/3ede927e66db1261794402acafc52968f4791d01/examples/assets/creamPink.png -------------------------------------------------------------------------------- /examples/assets/cupCake.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Izzimach/react-three-legacy/3ede927e66db1261794402acafc52968f4791d01/examples/assets/cupCake.png -------------------------------------------------------------------------------- /examples/assets/lollipopGreen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Izzimach/react-three-legacy/3ede927e66db1261794402acafc52968f4791d01/examples/assets/lollipopGreen.png -------------------------------------------------------------------------------- /examples/assets/spherePanorama.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Izzimach/react-three-legacy/3ede927e66db1261794402acafc52968f4791d01/examples/assets/spherePanorama.jpg -------------------------------------------------------------------------------- /examples/camera-up/camera-up.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Interactive THREE test 7 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /examples/camera-up/camera-up.js: -------------------------------------------------------------------------------- 1 | // 2 | // Basic ReactTHREE example using events to add/remove sprites. 3 | 4 | // eslint hints 5 | /* global _ */ 6 | /* global React */ 7 | /* global ReactTHREE */ 8 | /* global THREE */ 9 | 10 | var g_assetpath = function(filename) { return '../assets/' + filename; }; 11 | 12 | 13 | // 14 | // This 'application' tracks a bunch of cubes. 15 | // You can do two things: 16 | // 1. add a new randomly-placed cube to the application state 17 | // 2. remove a specific cube, specified by the cube id 18 | // 19 | 20 | 21 | // the DOM element where the canvas is mounted 22 | var g_renderelement; 23 | 24 | // This basically the 'application state': 25 | // a list of all the current sprites 26 | var g_applicationstate = {}; 27 | 28 | var g_nextcubeid = 1; 29 | 30 | // if the application state is modified call this to update the GUI 31 | 32 | function updateApp() { 33 | ReactTHREE.render(React.createElement(CubeApp, g_applicationstate), g_renderelement); 34 | } 35 | 36 | // 37 | // callback which adds a randomly placed cube to the application state 38 | // 39 | 40 | function randomradian() { 41 | return Math.random() * Math.PI; 42 | } 43 | 44 | function addRandomCube() { 45 | // give each sprite a unique ID 46 | var refnumber = g_nextcubeid++; 47 | var cubeid = 'cube' + refnumber.toString(); 48 | 49 | var newcube = { 50 | position: new THREE.Vector3( 51 | (Math.random() - 0.5) * g_applicationstate.xsize, 52 | (Math.random() - 0.5) * g_applicationstate.ysize, 53 | (Math.random() - 0.5) * g_applicationstate.zsize 54 | ), 55 | quaternion: new THREE.Quaternion().setFromEuler(new THREE.Euler(randomradian(), randomradian(), randomradian(),'XYZ')), 56 | materialname: g_assetpath('lollipopGreen.png'), 57 | key: cubeid, 58 | name: cubeid 59 | }; 60 | 61 | g_applicationstate.cubes.push(newcube); 62 | 63 | // update and re-render 64 | updateApp(); 65 | } 66 | 67 | // 68 | // callback to remove the dynamic cube that was clicked on 69 | // 70 | 71 | function removeCubeById(cubeid) { 72 | var isthecube = function(cube) { return cube.key === cubeid; }; 73 | _.remove(g_applicationstate.cubes, isthecube); 74 | 75 | updateApp(); 76 | } 77 | 78 | 79 | 80 | 81 | // 82 | // React Components follow 83 | // 84 | 85 | 86 | // 87 | // Component to represent a clickable cube with a given texture 88 | // the box geometry is shared! 89 | // materials are generated and cached here. Normally you would want to 90 | // come up with a more general purpose asset manager... 91 | // 92 | 93 | var boxgeometry = new THREE.BoxGeometry( 200,200,200); 94 | 95 | var boxmaterialcache = []; 96 | function lookupmaterial(materialname) { 97 | var material = _.find(boxmaterialcache, function(x) { return x.name === materialname;}); 98 | if (typeof material !== "undefined") { return material; } 99 | 100 | // not found. create a new material for the given texture 101 | var texturemap = THREE.ImageUtils.loadTexture( g_assetpath(materialname) ); 102 | var newmaterial = new THREE.MeshBasicMaterial( { map: texturemap } ); 103 | newmaterial.name = materialname; 104 | 105 | boxmaterialcache.push(newmaterial); 106 | return newmaterial; 107 | } 108 | 109 | var ClickableCube = React.createClass({ 110 | displayName: 'ClickableCube', 111 | propTypes: { 112 | position: React.PropTypes.instanceOf(THREE.Vector3), 113 | quaternion: React.PropTypes.instanceOf(THREE.Quaternion), 114 | materialname: React.PropTypes.string.isRequired, 115 | shared: React.PropTypes.bool 116 | }, 117 | render: function() { 118 | var boxmaterial = lookupmaterial(this.props.materialname); 119 | var cubeprops = _.clone(this.props); 120 | cubeprops.geometry = boxgeometry; 121 | cubeprops.material = boxmaterial; 122 | return React.createElement(ReactTHREE.Mesh, cubeprops); 123 | } 124 | }); 125 | 126 | // 127 | // A cube that, when clicked, removes itself from the application state 128 | // 129 | 130 | var ClickToRemoveCube = React.createClass({ 131 | displayName: 'ClickToRemoveCube', 132 | removeThisCube: function(event, intersection) { 133 | var cubeid = intersection.object.name; 134 | removeCubeById(cubeid); 135 | }, 136 | render: function() { 137 | var cubeprops = _.clone(this.props); 138 | cubeprops.materialname = 'lollipopGreen.png'; 139 | cubeprops.onClick3D = this.removeThisCube; 140 | return React.createElement(ClickableCube, cubeprops); 141 | } 142 | }); 143 | 144 | 145 | // 146 | // Component that represents an add button. click on this 'button' (really a cube) to add a cube to the scene 147 | // 148 | 149 | var CubeAppButtons = React.createClass({ 150 | displayName:'CubeAppButtons', 151 | propTypes: { 152 | }, 153 | handlePick: function(/*event, intersection*/) { 154 | addRandomCube(); 155 | }, 156 | render: function() { 157 | return React.createElement( 158 | ReactTHREE.Object3D, 159 | {}, 160 | React.createElement(ClickableCube,{position: new THREE.Vector3(0,0,0), materialname:'cherry.png', name:'addbutton', onClick3D:this.handlePick}) 161 | ); 162 | } 163 | }); 164 | 165 | // 166 | // Component to display all the dynamically added cubes. All we do is 167 | // generate a ClickableCube component for each entry in the 'cubes' property. 168 | // 169 | 170 | var RemovableCubes = React.createClass({ 171 | displayName:'RemoveableCubes', 172 | propTypes: { 173 | cubes: React.PropTypes.arrayOf(React.PropTypes.object) 174 | }, 175 | render: function() { 176 | // props for the Object3D containing the cubes. You could change these 177 | // props to translate/rotate/scale the whole group of cubes at once 178 | var containerprops = {}; 179 | var children = []; 180 | _.forEach(this.props.cubes, function(cube) { children.push(React.createElement(ClickToRemoveCube,cube));}); 181 | return React.createElement(ReactTHREE.Object3D, 182 | containerprops, 183 | children); 184 | } 185 | }); 186 | 187 | // 188 | // A camera that orbits the origin. Specify orbit distance and angle (azimuth) 189 | // 190 | 191 | var OrbitCamera = React.createClass({ 192 | displayName:'OrbitCamera', 193 | propTypes: { 194 | distance: React.PropTypes.number.isRequired, 195 | azimuth: React.PropTypes.number.isRequired, 196 | tilt: React.PropTypes.number.isRequired, 197 | aspectratio: React.PropTypes.number.isRequired 198 | }, 199 | render: function() { 200 | // could use sin/cos here but a quat allows for more generic rotation 201 | var orbitquaternion = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0,1,0), this.props.azimuth); 202 | var cameraposition = new THREE.Vector3(this.props.distance,0,0); // camera position at azimuth 0 203 | var cameraup = new THREE.Vector3(0, Math.cos(this.props.tilt), Math.sin(this.props.tilt)); 204 | cameraposition.applyQuaternion(orbitquaternion); 205 | cameraup.applyQuaternion(orbitquaternion); 206 | 207 | return React.createElement(ReactTHREE.PerspectiveCamera, 208 | { 209 | name:'maincamera', 210 | fov:75, 211 | aspect:this.props.aspectratio, 212 | position: cameraposition, 213 | lookat: new THREE.Vector3(0,0,0), 214 | up: cameraup, 215 | near:1, 216 | far:5000 217 | }); 218 | } 219 | }); 220 | 221 | // 222 | // The top level component 223 | // props: 224 | // - width,height : size of the overall render canvas in pixels 225 | // - sprites: a list of objects describing all the current sprites containing x,y and image fields 226 | // 227 | 228 | var CubeApp = React.createClass({ 229 | displayName: 'CubeApp', 230 | propTypes: { 231 | borderpx: React.PropTypes.number.isRequired 232 | }, 233 | getInitialState: function() { 234 | // base initial size on window size minus border size 235 | var width = window.innerWidth - this.props.borderpx; 236 | var height = window.innerHeight - this.props.borderpx; 237 | 238 | return {width:width, height:height, cameraazimuth:0, cameratilt: 0}; 239 | }, 240 | componentDidMount: function() { 241 | var componentinstance = this; 242 | var animationcallback = function(/*t*/) { 243 | var newazimuth = componentinstance.state.cameraazimuth + 0.01; 244 | var newtilt = 0.1 * Math.sin(newazimuth); 245 | 246 | var newstate = { 247 | cameraazimuth:newazimuth, 248 | cameratilt:newtilt, 249 | spincameracallback:requestAnimationFrame(animationcallback) 250 | }; 251 | componentinstance.setState(newstate); 252 | }; 253 | // add an interval timer function to rotate the camera 254 | componentinstance.setState({spincameracallback:requestAnimationFrame(animationcallback)}); 255 | 256 | // handle resize events - should prob. be a mixin 257 | var resizecallback = function() { 258 | var newwidth = window.innerWidth - componentinstance.props.borderpx; 259 | var newheight = window.innerHeight - componentinstance.props.borderpx; 260 | componentinstance.setState({width:newwidth, height:newheight}); 261 | }; 262 | window.addEventListener('resize',resizecallback, false); 263 | componentinstance.setState({resizecallback:resizecallback}); 264 | }, 265 | componentWillUnmount: function() { 266 | if (this.state.spincameracallback !== null) { 267 | cancelAnimationFrame(this.state.spincameracallback); 268 | } 269 | window.removeEventListener('resize',this.state.resizecallback); 270 | }, 271 | render: function() { 272 | return React.createElement( 273 | ReactTHREE.Renderer, 274 | {width:this.state.width, height:this.state.height}, 275 | React.createElement( 276 | ReactTHREE.Scene, 277 | {width: this.state.width, height: this.state.height, pointerEvents: ['onClick'], background:0x202020, camera:'maincamera'}, 278 | [ 279 | React.createElement(OrbitCamera, {key:'camera', distance:600, azimuth:this.state.cameraazimuth, tilt:this.state.cameratilt, aspectratio:this.state.width / this.state.height}), 280 | React.createElement(RemovableCubes, {key:'cubes', cubes:this.props.cubes}), 281 | React.createElement(CubeAppButtons, {key:'gui'}) 282 | ] 283 | ) 284 | ); 285 | } 286 | }); 287 | 288 | function interactiveexamplestart() { // eslint-disable-line no-unused-vars 289 | 290 | g_renderelement = document.getElementById("three-box"); 291 | 292 | g_applicationstate = {borderpx:6, cubes:[], xsize:500, ysize:500, zsize:500 }; 293 | 294 | updateApp(); 295 | } 296 | -------------------------------------------------------------------------------- /examples/cupcake/cupcake.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | React-Three test 7 | 20 | 21 | 22 | 23 | 24 |
25 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /examples/cupcake/cupcake.js: -------------------------------------------------------------------------------- 1 | 2 | // 3 | // Basic React-THREE example using a custom 'Cupcake' Component which consists of two cubes 4 | // 5 | 6 | /* global React */ 7 | /* global ReactTHREE */ 8 | /* global THREE */ 9 | 10 | var assetpath = function(filename) { return '../assets/' + filename; }; 11 | 12 | var MeshFactory = React.createFactory(ReactTHREE.Mesh); 13 | 14 | // 15 | // Cupcake component is two cube meshes textured with cupcake textures 16 | // 17 | 18 | var boxgeometry = new THREE.BoxGeometry(200,200,200); 19 | 20 | var textureLoader = new THREE.TextureLoader(); 21 | var cupcaketexture = THREE.ImageUtils.loadTexture( assetpath('cupCake.png') ); 22 | var cupcakematerial = new THREE.MeshBasicMaterial( { map: cupcaketexture } ); 23 | 24 | var creamtexture = THREE.ImageUtils.loadTexture( assetpath('creamPink.png') ); 25 | var creammaterial = new THREE.MeshBasicMaterial( { map: creamtexture }); 26 | 27 | var Cupcake = React.createClass({ 28 | displayName: 'Cupcake', 29 | propTypes: { 30 | position: React.PropTypes.instanceOf(THREE.Vector3), 31 | quaternion: React.PropTypes.instanceOf(THREE.Quaternion).isRequired 32 | }, 33 | render: function() { 34 | return React.createElement( 35 | ReactTHREE.Object3D, 36 | {quaternion:this.props.quaternion, position:this.props.position || new THREE.Vector3(0,0,0)}, 37 | MeshFactory({position:new THREE.Vector3(0,-100,0), geometry:boxgeometry, material:cupcakematerial}), 38 | MeshFactory({position:new THREE.Vector3(0, 100,0), geometry:boxgeometry, material:creammaterial}) 39 | ); 40 | } 41 | }); 42 | 43 | 44 | // 45 | // The top level component 46 | // props: 47 | // - width,height : size of the overall render canvas in pixels 48 | // - xposition: x position in pixels that governs where the elements are placed 49 | // 50 | 51 | var ExampleScene = React.createClass({ 52 | displayName: 'ExampleScene', 53 | render: function() { 54 | var MainCameraElement = React.createElement( 55 | ReactTHREE.PerspectiveCamera, 56 | {name:'maincamera', fov:'75', aspect:this.props.width/this.props.height, near:1, far:5000, position:new THREE.Vector3(0,0,600), lookat:new THREE.Vector3(0,0,0)}); 57 | 58 | return ( 59 | React.createElement(ReactTHREE.Renderer, { width:this.props.width, height:this.props.height }, 60 | React.createElement(ReactTHREE.Scene, 61 | {width:this.props.width, height:this.props.height, camera:'maincamera'} 62 | ,MainCameraElement 63 | ,React.createElement(Cupcake, this.props.cupcakedata) 64 | ), 65 | React.createElement(ReactTHREE.Scene, 66 | {width:this.props.width, height:this.props.height, camera:'maincamera'} 67 | ,MainCameraElement 68 | ,React.createElement(Cupcake, this.props.cupcakedata2) 69 | ) 70 | ) 71 | ); 72 | } 73 | }); 74 | 75 | var cupcakestart = function() { // eslint-disable-line no-unused-vars 76 | var renderelement = document.getElementById("three-box"); 77 | 78 | var w = window.innerWidth-6; 79 | var h = window.innerHeight-6; 80 | 81 | var sceneprops = {width:w, height:h, 82 | cupcakedata:{position:new THREE.Vector3(0,0,0), quaternion:new THREE.Quaternion()}, 83 | cupcakedata2:{position:new THREE.Vector3(0,0,0), quaternion:new THREE.Quaternion()} 84 | }; 85 | var cupcakeprops = sceneprops.cupcakedata; 86 | var cupcakeprops2 = sceneprops.cupcakedata2; 87 | var rotationangle = 0; 88 | 89 | ReactTHREE.render(React.createElement(ExampleScene,sceneprops), renderelement); 90 | 91 | function spincupcake(t) { 92 | rotationangle = t * 0.001; 93 | cupcakeprops.quaternion.setFromEuler(new THREE.Euler(rotationangle,rotationangle*3,0)); 94 | cupcakeprops.position.x = 300 * Math.sin(rotationangle); 95 | cupcakeprops2.quaternion.setFromEuler(new THREE.Euler(rotationangle,rotationangle*3,0)); 96 | cupcakeprops2.position.x = 300 * Math.sin(-rotationangle); 97 | 98 | ReactTHREE.render(React.createElement(ExampleScene,sceneprops), renderelement); 99 | 100 | requestAnimationFrame(spincupcake); 101 | } 102 | 103 | spincupcake(); 104 | } 105 | -------------------------------------------------------------------------------- /examples/helpers/helpers.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | React-Three Helpers Example 7 | 20 | 21 | 22 | 23 | 24 |
25 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /examples/helpers/helpers.js: -------------------------------------------------------------------------------- 1 | // 2 | // Basic React-THREE example applying threejs helpers to a component 3 | // 4 | 5 | /* global React */ 6 | /* global ReactTHREE */ 7 | /* global THREE */ 8 | 9 | var assetpath = function(filename) { return '../assets/' + filename; }; 10 | 11 | var MeshFactory = React.createFactory(ReactTHREE.Mesh); 12 | 13 | // 14 | // Cupcake component is two cube meshes textured with cupcake textures 15 | // 16 | 17 | var boxgeometry = new THREE.BoxGeometry( 200,200,200); 18 | 19 | var cupcaketexture = THREE.ImageUtils.loadTexture( assetpath('cupCake.png') ); 20 | var cupcakematerial = new THREE.MeshBasicMaterial( { map: cupcaketexture } ); 21 | 22 | var creamtexture = THREE.ImageUtils.loadTexture( assetpath('creamPink.png') ); 23 | var creammaterial = new THREE.MeshBasicMaterial( { map: creamtexture } ); 24 | 25 | var Cupcake = React.createClass({ 26 | displayName: 'Cupcake', 27 | propTypes: { 28 | position: React.PropTypes.instanceOf(THREE.Vector3), 29 | quaternion: React.PropTypes.instanceOf(THREE.Quaternion).isRequired 30 | }, 31 | render: function() { 32 | return React.createElement( 33 | ReactTHREE.Object3D, 34 | {quaternion:this.props.quaternion, position:this.props.position || new THREE.Vector3(0,0,0)}, 35 | MeshFactory({position:new THREE.Vector3(0,-100,0), geometry:boxgeometry, material:cupcakematerial}), 36 | MeshFactory({position:new THREE.Vector3(0, 100,0), geometry:boxgeometry, material:creammaterial}) 37 | ); 38 | } 39 | }); 40 | 41 | // 42 | // The top level component 43 | // props: 44 | // - width,height : size of the overall render canvas in pixels 45 | // - xposition: x position in pixels that governs where the elements are placed 46 | // 47 | 48 | var ExampleScene = React.createClass({ 49 | displayName: 'ExampleScene', 50 | render: function() { 51 | var MainCameraElement = React.createElement( 52 | ReactTHREE.PerspectiveCamera, 53 | {name:'maincamera', fov:'75', aspect:this.props.width/this.props.height, near:1, far:5000, position:new THREE.Vector3(0,0,600), lookat:new THREE.Vector3(0,0,0)}); 54 | 55 | return ( 56 | React.createElement( 57 | ReactTHREE.Renderer, 58 | { width:this.props.width, height:this.props.height }, 59 | React.createElement( 60 | ReactTHREE.Scene, 61 | {width:this.props.width, height:this.props.height, camera:'maincamera'}, 62 | MainCameraElement, 63 | React.createElement(ReactTHREE.Helper, 64 | {helpers:[THREE.BoxHelper]}, 65 | React.createElement(Cupcake, this.props.cupcakedata)) 66 | ) 67 | ) 68 | ); 69 | } 70 | }); 71 | 72 | var helpersstart = function() { // eslint-disable-line no-unused-vars 73 | var renderelement = document.getElementById("three-box"); 74 | 75 | var w = window.innerWidth-6; 76 | var h = window.innerHeight-6; 77 | 78 | var sceneprops = {width:w, height:h, 79 | cupcakedata:{position:new THREE.Vector3(0,0,0), quaternion:new THREE.Quaternion()}, 80 | cupcakedata2:{position:new THREE.Vector3(0,0,0), quaternion:new THREE.Quaternion()} 81 | }; 82 | var cupcakeprops = sceneprops.cupcakedata; 83 | var cupcakeprops2 = sceneprops.cupcakedata2; 84 | var rotationangle = 0; 85 | 86 | ReactTHREE.render(React.createElement(ExampleScene,sceneprops), renderelement); 87 | 88 | function spincupcake(t) { 89 | rotationangle = t * 0.001; 90 | cupcakeprops.quaternion.setFromEuler(new THREE.Euler(rotationangle,rotationangle*3,0)); 91 | cupcakeprops.position.x = 300 * Math.sin(rotationangle); 92 | cupcakeprops2.quaternion.setFromEuler(new THREE.Euler(rotationangle,rotationangle*3,0)); 93 | cupcakeprops2.position.x = 300 * Math.sin(-rotationangle); 94 | 95 | ReactTHREE.render(React.createElement(ExampleScene,sceneprops), renderelement); 96 | 97 | requestAnimationFrame(spincupcake); 98 | } 99 | 100 | spincupcake(); 101 | } 102 | -------------------------------------------------------------------------------- /examples/interactive/interactive.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Interactive THREE test 7 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /examples/interactive/interactive.js: -------------------------------------------------------------------------------- 1 | // 2 | // Basic ReactTHREE example using events to add/remove sprites. 3 | 4 | /* global _*/ 5 | /* global React */ 6 | /* global ReactTHREE */ 7 | /* global THREE */ 8 | 9 | var g_assetpath = function(filename) { return '../assets/' + filename; }; 10 | 11 | 12 | // 13 | // This 'application' tracks a bunch of cubes. 14 | // You can do two things: 15 | // 1. add a new randomly-placed cube to the application state 16 | // 2. remove a specific cube, specified by the cube id 17 | // 18 | 19 | 20 | // DOM node upon which to mount the WebGL canvas 21 | var g_renderelement; 22 | 23 | // This basically the 'application state': 24 | // a list of all the current sprites 25 | var g_applicationstate = {}; 26 | 27 | var g_nextcubeid = 1; 28 | 29 | // if the application state is modified call this to update the GUI 30 | 31 | function updateApp() { 32 | ReactTHREE.render(React.createElement(CubeApp, g_applicationstate), g_renderelement); 33 | } 34 | 35 | // 36 | // callback which adds a randomly placed cube to the application state 37 | // 38 | 39 | function randomradian() { 40 | return Math.random() * Math.PI; 41 | } 42 | 43 | function addRandomCube() { 44 | // give each sprite a unique ID 45 | var refnumber = g_nextcubeid++; 46 | var cubeid = 'cube' + refnumber.toString(); 47 | 48 | var newcube = { 49 | position: new THREE.Vector3( 50 | (Math.random() - 0.5) * g_applicationstate.xsize, 51 | (Math.random() - 0.5) * g_applicationstate.ysize, 52 | (Math.random() - 0.5) * g_applicationstate.zsize 53 | ), 54 | quaternion: new THREE.Quaternion().setFromEuler(new THREE.Euler(randomradian(), randomradian(), randomradian(),'XYZ')), 55 | materialname: g_assetpath('lollipopGreen.png'), 56 | key: cubeid, 57 | name: cubeid 58 | }; 59 | 60 | g_applicationstate.cubes.push(newcube); 61 | 62 | // update and re-render 63 | updateApp(); 64 | } 65 | 66 | // 67 | // callback to remove the dynamic cube that was clicked on 68 | // 69 | 70 | function removeCubeById(cubeid) { 71 | var isthecube = function(cube) { return cube.key === cubeid; }; 72 | _.remove(g_applicationstate.cubes, isthecube); 73 | 74 | updateApp(); 75 | } 76 | 77 | 78 | 79 | 80 | // 81 | // React Components follow 82 | // 83 | 84 | 85 | // 86 | // Component to represent a clickable cube with a given texture 87 | // the box geometry is shared! 88 | // materials are generated and cached here. Normally you would want to 89 | // come up with a more general purpose asset manager... 90 | // 91 | 92 | var boxgeometry = new THREE.BoxGeometry( 200,200,200); 93 | 94 | var boxmaterialcache = []; 95 | function lookupmaterial(materialname) { 96 | var material = _.find(boxmaterialcache, function(x) { return x.name === materialname;}); 97 | if (typeof material !== "undefined") { return material; } 98 | 99 | // not found. create a new material for the given texture 100 | var texturemap = THREE.ImageUtils.loadTexture( g_assetpath(materialname) ); 101 | var newmaterial = new THREE.MeshBasicMaterial( { map: texturemap } ); 102 | newmaterial.name = materialname; 103 | 104 | boxmaterialcache.push(newmaterial); 105 | return newmaterial; 106 | } 107 | 108 | var ClickableCube = React.createClass({ 109 | displayName: 'ClickableCube', 110 | propTypes: { 111 | position: React.PropTypes.instanceOf(THREE.Vector3), 112 | quaternion: React.PropTypes.instanceOf(THREE.Quaternion), 113 | materialname: React.PropTypes.string.isRequired, 114 | shared: React.PropTypes.bool 115 | }, 116 | render: function() { 117 | var boxmaterial = lookupmaterial(this.props.materialname); 118 | var cubeprops = _.clone(this.props); 119 | cubeprops.geometry = boxgeometry; 120 | cubeprops.material = boxmaterial; 121 | return React.createElement(ReactTHREE.Mesh, cubeprops); 122 | } 123 | }); 124 | 125 | // 126 | // A cube that, when clicked, removes itself from the application state 127 | // 128 | 129 | var ClickToRemoveCube = React.createClass({ 130 | displayName: 'ClickToRemoveCube', 131 | removeThisCube: function(event, intersection) { 132 | var cubeid = intersection.object.name; 133 | removeCubeById(cubeid); 134 | }, 135 | render: function() { 136 | var cubeprops = _.clone(this.props); 137 | cubeprops.materialname = 'lollipopGreen.png'; 138 | cubeprops.onMouseMove3D = this.removeThisCube; 139 | return React.createElement(ClickableCube, cubeprops); 140 | } 141 | }); 142 | 143 | 144 | // 145 | // Component that represents an add button. click on this 'button' (really a cube) to add a cube to the scene 146 | // 147 | 148 | var CubeAppButtons = React.createClass({ 149 | displayName:'CubeAppButtons', 150 | propTypes: { 151 | }, 152 | handlePick: function(/*event, intersection*/) { 153 | addRandomCube(); 154 | }, 155 | render: function() { 156 | return React.createElement( 157 | ReactTHREE.Object3D, 158 | {}, 159 | React.createElement(ClickableCube,{position: new THREE.Vector3(0,0,0), materialname:'cherry.png', name:'addbutton', onClick3D:this.handlePick}) 160 | ); 161 | } 162 | }); 163 | 164 | // 165 | // Component to display all the dynamically added cubes. All we do is 166 | // generate a ClickableCube component for each entry in the 'cubes' property. 167 | // 168 | 169 | var RemovableCubes = React.createClass({ 170 | displayName:'RemoveableCubes', 171 | propTypes: { 172 | cubes: React.PropTypes.arrayOf(React.PropTypes.object) 173 | }, 174 | render: function() { 175 | // props for the Object3D containing the cubes. You could change these 176 | // props to translate/rotate/scale the whole group of cubes at once 177 | var containerprops = {}; 178 | var children = []; 179 | _.forEach(this.props.cubes, function(cube) { children.push(React.createElement(ClickToRemoveCube,cube));}); 180 | return React.createElement(ReactTHREE.Object3D, 181 | containerprops, 182 | children); 183 | } 184 | }); 185 | 186 | // 187 | // A camera that orbits the origin. Specify orbit distance and angle (azimuth) 188 | // 189 | 190 | var OrbitCamera = React.createClass({ 191 | displayName:'OrbitCamera', 192 | propTypes: { 193 | distance: React.PropTypes.number.isRequired, 194 | azimuth: React.PropTypes.number.isRequired, 195 | aspectratio: React.PropTypes.number.isRequired 196 | }, 197 | render: function() { 198 | // could use sin/cos here but a quat allows for more generic rotation 199 | var orbitquaternion = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0,1,0), this.props.azimuth); 200 | var cameraposition = new THREE.Vector3(this.props.distance,0,0); // camera position at azimuth 0 201 | cameraposition.applyQuaternion(orbitquaternion); 202 | 203 | return React.createElement(ReactTHREE.PerspectiveCamera, 204 | { 205 | name:'maincamera', 206 | fov:75, 207 | aspect:this.props.aspectratio, 208 | position: cameraposition, 209 | lookat: new THREE.Vector3(0,0,0), 210 | near:1, 211 | far:5000 212 | }); 213 | } 214 | }); 215 | 216 | // 217 | // The top level component 218 | // props: 219 | // - width,height : size of the overall render canvas in pixels 220 | // - sprites: a list of objects describing all the current sprites containing x,y and image fields 221 | // 222 | 223 | var CubeApp = React.createClass({ 224 | displayName: 'CubeApp', 225 | propTypes: { 226 | borderpx: React.PropTypes.number.isRequired 227 | }, 228 | getInitialState: function() { 229 | // base initial size on window size minus border size 230 | var width = window.innerWidth - this.props.borderpx; 231 | var height = window.innerHeight - this.props.borderpx; 232 | 233 | return {width:width, height:height, cameraazimuth:0}; 234 | }, 235 | componentDidMount: function() { 236 | var componentinstance = this; 237 | var animationcallback = function(/*t*/) { 238 | var newazimuth = componentinstance.state.cameraazimuth + 0.01; 239 | 240 | var newstate = { 241 | cameraazimuth:newazimuth, 242 | spincameracallback:requestAnimationFrame(animationcallback) 243 | }; 244 | componentinstance.setState(newstate); 245 | }; 246 | // add an interval timer function to rotate the camera 247 | componentinstance.setState({spincameracallback:requestAnimationFrame(animationcallback)}); 248 | 249 | // handle resize events - should prob. be a mixin 250 | var resizecallback = function() { 251 | var newwidth = window.innerWidth - componentinstance.props.borderpx; 252 | var newheight = window.innerHeight - componentinstance.props.borderpx; 253 | componentinstance.setState({width:newwidth, height:newheight}); 254 | }; 255 | window.addEventListener('resize',resizecallback, false); 256 | componentinstance.setState({resizecallback:resizecallback}); 257 | }, 258 | componentWillUnmount: function() { 259 | if (this.state.spincameracallback !== null) { 260 | cancelAnimationFrame(this.state.spincameracallback); 261 | } 262 | window.removeEventListener('resize',this.state.resizecallback); 263 | }, 264 | render: function() { 265 | return React.createElement( 266 | ReactTHREE.Renderer, 267 | {width: this.state.width, height: this.state.height, background:0x202020}, 268 | React.createElement(ReactTHREE.Scene, 269 | {pointerEvents: ['onClick', 'onMouseMove'], background:0x202020, camera:'maincamera'}, 270 | [ 271 | React.createElement(OrbitCamera, {key:'camera', distance:600, azimuth:this.state.cameraazimuth, aspectratio:this.state.width / this.state.height}), 272 | React.createElement(RemovableCubes, {key:'cubes', cubes:this.props.cubes}), 273 | React.createElement(CubeAppButtons, {key:'gui'}) 274 | ] 275 | )); 276 | } 277 | }); 278 | 279 | 280 | 281 | function interactiveexamplestart() { // eslint-disable-line no-unused-vars 282 | 283 | g_renderelement = document.getElementById("three-box"); 284 | 285 | g_applicationstate = {borderpx:6, cubes:[], xsize:500, ysize:500, zsize:500 }; 286 | 287 | updateApp(); 288 | } 289 | -------------------------------------------------------------------------------- /examples/jsxtransform/jsxtransform.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | React-three test 7 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | -------------------------------------------------------------------------------- /examples/jsxtransform/jsxtransform.jsx: -------------------------------------------------------------------------------- 1 | // 2 | // The cupcake example done using JSX instead of javascript. 3 | // 4 | 5 | var assetpath = function(filename) { return '../assets/' + filename; }; 6 | var boxgeometry = new THREE.BoxGeometry( 200,200,200); 7 | 8 | // 9 | // Supposedly you can skip these declares and just write jsx elements like 10 | // but I haven't actually tried that 11 | // 12 | 13 | var Renderer = ReactTHREE.Renderer; 14 | var Scene = ReactTHREE.Scene; 15 | var Mesh = ReactTHREE.Mesh; 16 | var Object3D = ReactTHREE.Object3D; 17 | var PerspectiveCamera = ReactTHREE.PerspectiveCamera; 18 | 19 | // 20 | // Cupcake component is two cube meshes textured with cupcake textures 21 | // 22 | 23 | var cupcaketexture = THREE.ImageUtils.loadTexture( assetpath('cupCake.png') ); 24 | var cupcakematerial = new THREE.MeshBasicMaterial( { map: cupcaketexture } ); 25 | 26 | var creamtexture = THREE.ImageUtils.loadTexture( assetpath('creamPink.png') ); 27 | var creammaterial = new THREE.MeshBasicMaterial( { map: creamtexture } ); 28 | 29 | var Cupcake = React.createClass({ 30 | displayName: 'Cupcake', 31 | propTypes: { 32 | position: React.PropTypes.instanceOf(THREE.Vector3), 33 | quaternion: React.PropTypes.instanceOf(THREE.Quaternion).isRequired 34 | }, 35 | render: function() { 36 | return 37 | 38 | 39 | ; 40 | } 41 | }); 42 | 43 | // 44 | // The top level component 45 | // props: 46 | // - width,height : size of the overall render canvas in pixels 47 | // - xposition: x position in pixels that governs where the elements are placed 48 | // 49 | 50 | var ExampleScene = React.createClass({ 51 | displayName: 'ExampleScene', 52 | render: function() { 53 | var aspectratio = this.props.width / this.props.height; 54 | var cameraprops = {fov:75, aspect:aspectratio, near:1, far:5000, position:new THREE.Vector3(0,0,600), lookat:new THREE.Vector3(0,0,0)}; 55 | 56 | return 57 | 58 | 59 | 60 | 61 | ; 62 | } 63 | }); 64 | 65 | 66 | function jsxtransformstart() { // eslint-disable-line no-unused-vars 67 | var renderelement = document.getElementById("three-box"); 68 | 69 | var w = window.innerWidth-6; 70 | var h = window.innerHeight-6; 71 | 72 | var sceneprops = {width:w, height:h, cupcakedata:{position:new THREE.Vector3(0,0,0), quaternion:new THREE.Quaternion()}}; 73 | var cupcakeprops = sceneprops.cupcakedata; 74 | var rotationangle = 0; 75 | 76 | ReactTHREE.render(, renderelement); 77 | 78 | function spincupcake(t) { 79 | rotationangle = t * 0.001; 80 | cupcakeprops.quaternion.setFromEuler(new THREE.Euler(rotationangle,rotationangle*3,0)); 81 | cupcakeprops.position.x = 300 * Math.sin(rotationangle); 82 | ReactTHREE.render(, renderelement); 83 | 84 | requestAnimationFrame(spincupcake); 85 | } 86 | 87 | spincupcake(); 88 | } 89 | 90 | window.onload = jsxtransformstart; 91 | -------------------------------------------------------------------------------- /examples/orbitcontrols/orbitcontrols.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | React-Three test 7 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /examples/orbitcontrols/orbitcontrols.js: -------------------------------------------------------------------------------- 1 | // 2 | // Basic React-THREE example using a custom 'Cupcake' Component which consists of two cubes 3 | // 4 | 5 | /* global React */ 6 | /* global ReactTHREE*/ 7 | /* global THREE */ 8 | 9 | var assetpath = function(filename) { return '../assets/' + filename; }; 10 | 11 | var MeshFactory = React.createFactory(ReactTHREE.Mesh); 12 | 13 | // 14 | // Cupcake component is two cube meshes textured with cupcake textures 15 | // 16 | 17 | var boxgeometry = new THREE.BoxGeometry( 200,200,200); 18 | 19 | var cupcaketexture = THREE.ImageUtils.loadTexture( assetpath('cupCake.png') ); 20 | var cupcakematerial = new THREE.MeshBasicMaterial( { map: cupcaketexture } ); 21 | 22 | var creamtexture = THREE.ImageUtils.loadTexture( assetpath('creamPink.png') ); 23 | var creammaterial = new THREE.MeshBasicMaterial( { map: creamtexture } ); 24 | 25 | var Cupcake = React.createClass({ 26 | displayName: 'Cupcake', 27 | propTypes: { 28 | position: React.PropTypes.instanceOf(THREE.Vector3), 29 | quaternion: React.PropTypes.instanceOf(THREE.Quaternion).isRequired 30 | }, 31 | render: function() { 32 | return React.createElement( 33 | ReactTHREE.Object3D, 34 | {quaternion:this.props.quaternion, position:this.props.position || new THREE.Vector3(0,0,0)}, 35 | MeshFactory({position:new THREE.Vector3(0,-100,0), geometry:boxgeometry, material:cupcakematerial}), 36 | MeshFactory({position:new THREE.Vector3(0, 100,0), geometry:boxgeometry, material:creammaterial}) 37 | ); 38 | } 39 | }); 40 | 41 | // 42 | // The top level component 43 | // props: 44 | // - width,height : size of the overall render canvas in pixels 45 | // - xposition: x position in pixels that governs where the elements are placed 46 | // 47 | 48 | var ExampleScene = React.createClass({ 49 | displayName: 'ExampleScene', 50 | render: function() { 51 | var MainCameraElement = React.createElement( 52 | ReactTHREE.PerspectiveCamera, 53 | {name:'maincamera', fov:'75', aspect:this.props.width/this.props.height, near:1, far:5000, position:new THREE.Vector3(0,0,600), lookat:new THREE.Vector3(0,0,0)}); 54 | 55 | return React.createElement( 56 | ReactTHREE.Renderer, 57 | this.props, 58 | React.createElement( 59 | ReactTHREE.Scene, 60 | {width:this.props.width, height:this.props.height, camera:'maincamera', orbitControls:THREE.OrbitControls}, 61 | MainCameraElement, 62 | React.createElement(Cupcake, this.props.cupcakedata) 63 | ) 64 | ); 65 | } 66 | }); 67 | 68 | function orbitcontrolsstart() { // eslint-disable-line no-unused-vars 69 | var renderelement = document.getElementById("three-box"); 70 | 71 | var w = window.innerWidth-6; 72 | var h = window.innerHeight-6; 73 | 74 | var sceneprops = {width:w, height:h, cupcakedata:{position:new THREE.Vector3(0,0,0), quaternion:new THREE.Quaternion()}}; 75 | 76 | ReactTHREE.render(React.createElement(ExampleScene,sceneprops), renderelement); 77 | } 78 | -------------------------------------------------------------------------------- /examples/shader/fragment_shader.glsl: -------------------------------------------------------------------------------- 1 | uniform vec2 resolution; 2 | uniform float time; 3 | 4 | void main() { 5 | vec2 p = -1.0 + 2.0 * gl_FragCoord.xy / resolution.xy; 6 | float a = time*40.0; 7 | float d,e,f,g=1.0/40.0,h,i,r,q; 8 | e=400.0*(p.x*0.5+0.5); 9 | f=400.0*(p.y*0.5+0.5); 10 | i=200.0+sin(e*g+a/150.0)*20.0; 11 | d=200.0+cos(f*g/2.0)*18.0+cos(e*g)*7.0; 12 | r=sqrt(pow(abs(i-e),2.0)+pow(abs(d-f),2.0)); 13 | q=f/r; 14 | e=(r*cos(q))-a/2.0;f=(r*sin(q))-a/2.0; 15 | d=sin(e*g)*176.0+sin(e*g)*164.0+r; 16 | h=((f+d)+a/2.0)*g; 17 | i=cos(h+r*p.x/1.3)*(e+e+a)+cos(q*g*6.0)*(r+h/3.0); 18 | h=sin(f*g)*144.0-sin(e*g)*212.0*p.x; 19 | h=(h+(f-e)*q+sin(r-(a+h)/7.0)*10.0+i/4.0)*g; 20 | i+=cos(h*2.3*sin(a/350.0-q))*184.0*sin(q-(r*4.3+a/12.0)*g)+tan(r*g+h)*184.0*cos(r*g+h); 21 | i=mod(i/5.6,256.0)/64.0; 22 | if(i<0.0) i+=4.0; 23 | if(i>=2.0) i=4.0-i; 24 | d=r/350.0; 25 | d+=sin(d*d*8.0)*0.52; 26 | f=(sin(a*g)+1.0)/2.0; 27 | gl_FragColor=vec4(vec3(f*i/1.6,i/2.0+d/13.0,i)*d*p.x+vec3(i/1.3+d/8.0,i/2.0+d/18.0,i)*d*(1.0-p.x),1.0); 28 | } 29 | 30 | -------------------------------------------------------------------------------- /examples/shader/shader.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | React-three test 7 | 33 | 34 | 35 | 36 | 37 | 38 |
39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /examples/shader/shader.jsx: -------------------------------------------------------------------------------- 1 | // 2 | // The shader example uses a webgl shader. 3 | // It is based off the code at http://threejs.org/examples/#webgl_shader, but 4 | // converted to React.THREE 5 | // 6 | 7 | var geometry = new THREE.PlaneBufferGeometry( 2, 2 ); 8 | 9 | var vertexShader = require('raw!./vertex_shader.glsl'); 10 | var fragmentShader = require('raw!./fragment_shader.glsl'); 11 | const { Renderer, Scene, Mesh, Object3d, PerspectiveCamera } = ReactTHREE; 12 | 13 | class Wavey extends React.Component { 14 | constructor(props) { 15 | super(props) 16 | 17 | this.uniforms = { 18 | time: { type: "f", value: props.time }, 19 | resolution: { type: "v2", value: new THREE.Vector2(props.width, props.height) } 20 | }; 21 | 22 | this.material = new THREE.ShaderMaterial({ 23 | uniforms: this.uniforms, 24 | vertexShader: vertexShader, 25 | fragmentShader: fragmentShader 26 | }); 27 | } 28 | componentWillReceiveProps(nextProps) { 29 | this.uniforms.time.value = nextProps.time 30 | 31 | if(nextProps.width !== this.props.width) 32 | this.uniforms.resolution.value.x = nextProps.width; 33 | 34 | if(nextProps.height !== this.props.height) 35 | this.uniforms.resolution.value.y = nextProps.height; 36 | } 37 | render() { 38 | return 39 | } 40 | } 41 | 42 | // 43 | // The top level component 44 | // props: 45 | // - width,height : size of the overall render canvas in pixels 46 | // - xposition: x position in pixels that governs where the elements are placed 47 | // 48 | 49 | class ExampleScene extends React.Component { 50 | constructor(props) { 51 | super(props); 52 | 53 | this.state = { 54 | time: 1.0, 55 | width: window.innerWidth, 56 | height: window.innerHeight 57 | }; 58 | 59 | this.animate = () => { 60 | this.setState({ 61 | time: this.state.time + 0.05 62 | }) 63 | 64 | this.frameId = requestAnimationFrame(this.animate) 65 | } 66 | } 67 | 68 | componentDidMount() { 69 | this.animate() 70 | 71 | window.addEventListener( 'resize', this.onWindowResize.bind(this), false ) 72 | } 73 | 74 | componentWillUnmount() { 75 | cancelAnimationFrame(this.frameId) 76 | window.removeEventListener('resize', this.onWindowResize) 77 | } 78 | 79 | onWindowResize() { 80 | this.setState({ 81 | width: window.innerWidth, 82 | height: window.innerHeight 83 | }) 84 | } 85 | 86 | render() { 87 | var cameraprops = {position:{z: 1}}; 88 | 89 | return 90 | 91 | 92 | 93 | 94 | 95 | } 96 | } 97 | 98 | var time = 1.0; 99 | 100 | function shaderstart() { // eslint-disable-line no-unused-vars 101 | var renderelement = document.getElementById("three-box"); 102 | 103 | ReactTHREE.render(, renderelement); 104 | } 105 | 106 | window.onload = shaderstart; 107 | 108 | -------------------------------------------------------------------------------- /examples/shader/vertex_shader.glsl: -------------------------------------------------------------------------------- 1 | void main() { 2 | gl_Position = vec4( position, 1.0 ); 3 | } 4 | -------------------------------------------------------------------------------- /examples/spherical_panorama/spherical_panorama.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Spherical Panorama THREE test 7 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /examples/spherical_panorama/spherical_panorama.js: -------------------------------------------------------------------------------- 1 | // A simple SphericalPanorama viewer 2 | 3 | /* global React */ 4 | /* global ReactTHREE */ 5 | /* global THREE */ 6 | 7 | var sphereGeometry = new THREE.SphereGeometry(100, 32, 32); 8 | 9 | var imageMaterial = new THREE.MeshBasicMaterial({ 10 | map: THREE.ImageUtils.loadTexture('/examples/assets/spherePanorama.jpg') 11 | }); 12 | 13 | var SpherePanoramaScene = React.createClass({ 14 | displayName: 'SpherePanoramaScene', 15 | render: function() { 16 | var sceneProps = { 17 | width: this.props.width, 18 | height: this.props.height, 19 | camera: 'maincamera', 20 | listenToClick: true 21 | }; 22 | 23 | var cameraAngle = this.props.cameraAngle || 0; 24 | var cameraLookAt = new THREE.Vector3( 25 | Math.cos(cameraAngle), 26 | 0, 27 | Math.sin(cameraAngle) 28 | ); 29 | 30 | var mainCamera = React.createElement( 31 | ReactTHREE.PerspectiveCamera, 32 | { 33 | name: 'maincamera', 34 | aspect: this.props.width / this.props.height, 35 | near: 1, 36 | far: 5000, 37 | position: new THREE.Vector3(0, 0, 0), 38 | lookat: cameraLookAt 39 | } 40 | ); 41 | 42 | var sphere = React.createElement( 43 | ReactTHREE.Mesh, 44 | { 45 | geometry: sphereGeometry, 46 | material: imageMaterial, 47 | position: new THREE.Vector3(0, 0, 0), 48 | scale: new THREE.Vector3(1, 1, -1), 49 | quaternion: new THREE.Quaternion() 50 | } 51 | ); 52 | 53 | return React.createElement( 54 | ReactTHREE.Renderer, 55 | {width: this.props.width, height: this.props.height, background:0x202020}, 56 | React.createElement( 57 | ReactTHREE.Scene, 58 | sceneProps, 59 | mainCamera, 60 | sphere 61 | ) 62 | ); 63 | } 64 | }); 65 | 66 | 67 | function spherepanoramaexamplestart() { // eslint-disable-line no-unused-vars 68 | var renderelement = document.getElementById("three-box"); 69 | var appState = { 70 | width: window.innerWidth, 71 | height: window.innerHeight, 72 | cameraAngle: 0 73 | }; 74 | 75 | ReactTHREE.render( 76 | React.createElement(SpherePanoramaScene, appState), 77 | renderelement 78 | ); 79 | 80 | var start = Date.now(); 81 | var PERIOD = 30; 82 | 83 | function animate(time) { 84 | var dt = time - start; 85 | var newAngle = (2 * dt / (PERIOD * 1000)) * Math.PI; 86 | appState.cameraAngle = newAngle; 87 | 88 | ReactTHREE.render( 89 | React.createElement(SpherePanoramaScene, appState), 90 | renderelement 91 | ); 92 | 93 | requestAnimationFrame(animate); 94 | } 95 | 96 | animate(start); 97 | } 98 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |

React-THREE Examples

4 |

A cupcake

5 |

Orbit control demo

6 |

Interactive

7 |

Interactive with OrbitCamera

8 |

JSX transformed code

9 |

Using an opengl shader

10 |

Using threejs helpers

11 | 12 | 13 | -------------------------------------------------------------------------------- /karma.conf-withrender.js: -------------------------------------------------------------------------------- 1 | var confbasic = require('./karma.conf.js'); 2 | 3 | var test_render_files = [ 4 | 'vendor/lodash.min.js', 5 | 'build/react-three.js', 6 | 'test/createTestFixtureMountPoint.js', // why did I make this filename so long/ 7 | 'test/basics/*.js', 8 | 'test/components/*.js', 9 | 'node_modules/resemblejs/resemble.js', 10 | 'test/pixels/pixelTests.js', 11 | {pattern:'test/pixels/*.png',included:false, served:true} // for render tests 12 | ]; 13 | 14 | module.exports = function(config) { 15 | // re-use the basic configuration, and just add some more files to test 16 | confbasic(config); 17 | 18 | config.set({ 19 | files: test_render_files 20 | }); 21 | }; 22 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | var test_basic_files = [ 2 | 'vendor/lodash.min.js', 3 | 'build/react-three.js', 4 | 'test/createTestFixtureMountPoint.js', // why did I make this filename so long/ 5 | 'test/basics/*.js' 6 | ]; 7 | 8 | module.exports = function(config) { 9 | config.set({ 10 | browsers: ['Firefox'], 11 | frameworks:['jasmine'], 12 | files: test_basic_files, 13 | singleRun:true 14 | }); 15 | }; 16 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-three", 3 | "version": "0.9.7", 4 | "description": "Construct three.js scenes using React", 5 | "keywords": [ 6 | "react", 7 | "reactjs", 8 | "threejs", 9 | "webGL", 10 | "canvas" 11 | ], 12 | "author": { 13 | "name": "Gary Haussmann", 14 | "email": "gjhaussmann@gmail.com" 15 | }, 16 | "repository": { 17 | "type": "git", 18 | "url": "https://github.com/Izzimach/react-three" 19 | }, 20 | "license": "Apache-2.0", 21 | "files": [ 22 | "es5", 23 | "src" 24 | ], 25 | "main": "es5/react-three-commonjs.js", 26 | "es6": "src/ReactTHREE.js", 27 | "jsnext:main": "src/ReactTHREE.js", 28 | "dependencies": { 29 | "fbjs": "^0.8.0", 30 | "object-assign": "^4.0.1" 31 | }, 32 | "peerDependencies": { 33 | "react": "^15.4.1", 34 | "react-dom": "^15.4.1", 35 | "three": "^0.83.0" 36 | }, 37 | "devDependencies": { 38 | "babel-core": "^6.0.0", 39 | "babel-eslint": "~5.0.0", 40 | "babel-loader": "^6.0.0", 41 | "babel-plugin-transform-runtime": "^6.6.0", 42 | "babel-preset-es2015": "^6.0.0", 43 | "babel-preset-react": "^6.0.0", 44 | "babel-preset-stage-2": "^6.0.0", 45 | "babel-runtime": "^6.6.1", 46 | "eslint": "2.2.0", 47 | "expose-loader": "^0.7.0", 48 | "jasmine-core": "^2.3.4", 49 | "karma": "~0.13.0", 50 | "karma-chrome-launcher": "~0.2.2", 51 | "karma-firefox-launcher": "~0.1.3", 52 | "karma-jasmine": "~0.3.2", 53 | "lodash": "~4.17.11", 54 | "raw-loader": "^0.5.1", 55 | "react": "^15.4.1", 56 | "react-dom": "^15.4.1", 57 | "resemblejs": "^2.2.0", 58 | "rimraf": "~2.5.0", 59 | "three": "^0.83.0", 60 | "webpack": "^1.12.1", 61 | "webpack-dev-server": "~1.14.1" 62 | }, 63 | "scripts": { 64 | "build": "webpack", 65 | "dev": "webpack-dev-server --progress-colors", 66 | "examples": "webpack-dev-server --progress --colors --config webpack-examples.config.js", 67 | "build-commonjs": "webpack --config webpack-commonjs.config.js", 68 | "lint": "eslint src examples", 69 | "test": "karma start", 70 | "rendertest": "karma start karma.conf-withrender.js", 71 | "pixelrefs": "slimerjs test/pixels/generatetestrender.js", 72 | "pretest": "npm run build", 73 | "prerendertest": "npm run build", 74 | "prepublish": "npm run build-commonjs", 75 | "preexamples": "npm run build", 76 | "dist-clojars": "node build-clojars.js" 77 | }, 78 | "eslintConfig": { 79 | "extends": "eslint:recommended", 80 | "parser": "babel-eslint", 81 | "env": { 82 | "browser": true, 83 | "es6": true, 84 | "node": true 85 | }, 86 | "ecmaFeatures": { 87 | "spread": true 88 | }, 89 | "rules": { 90 | "strict": [ 91 | 0 92 | ], 93 | "eqeqeq": 2 94 | } 95 | }, 96 | "engines": { 97 | "node": ">=0.12.0" 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/Constants.js: -------------------------------------------------------------------------------- 1 | import * as THREE from 'three'; 2 | 3 | export default { 4 | 5 | X_AXIS: new THREE.Vector3(1,0,0), 6 | Y_AXIS: new THREE.Vector3(0,1,0), 7 | Z_AXIS: new THREE.Vector3(0,0,1) 8 | 9 | }; 10 | -------------------------------------------------------------------------------- /src/ReactTHREE.js: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * Copyright (c) 2014 Gary Haussmann 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | // 19 | // Lots of code here is based on react-art: https://github.com/facebook/react-art 20 | // 21 | 22 | import ReactDOM from 'react-dom'; 23 | 24 | // monkey patch to workaround some assumptions that we're working with the DOM 25 | import monkeypatch from './ReactTHREEMonkeyPatch'; 26 | monkeypatch(); 27 | 28 | import Renderer from './components/THREERenderer'; 29 | import Scene from './components/THREEScene'; 30 | import PerspectiveCamera from './components/cameras/THREEPerspectiveCamera'; 31 | import OrthographicCamera from './components/cameras/THREEOrthographicCamera'; 32 | import AxisHelper from './components/objects/THREEAxisHelper'; 33 | import Line from './components/objects/THREELine'; 34 | import LineSegments from './components/objects/THREELineSegments'; 35 | import PointCloud from './components/objects/THREEPointCloud'; 36 | import Object3D from './components/objects/THREEObject3D'; 37 | import Mesh from './components/objects/THREEMesh'; 38 | import SkinnedMesh from './components/objects/THREESkinnedMesh'; 39 | import Sprite from './components/objects/THREESprite'; 40 | import AmbientLight from './components/lights/THREEAmbientLight'; 41 | import PointLight from './components/lights/THREEPointLight'; 42 | import AreaLight from './components/lights/THREEAreaLight'; 43 | import DirectionalLight from './components/lights/THREEDirectionalLight'; 44 | import HemisphereLight from './components/lights/THREEHemisphereLight'; 45 | import SpotLight from './components/lights/THREESpotLight'; 46 | import Helper from './components/extras/THREEDecoratorHelper'; 47 | import Constants from './Constants'; 48 | 49 | module.exports = { 50 | Renderer, 51 | Scene, 52 | PerspectiveCamera, 53 | OrthographicCamera, 54 | AxisHelper, 55 | Line, 56 | LineSegments, 57 | PointCloud, 58 | Object3D, 59 | Mesh, 60 | SkinnedMesh, 61 | Sprite, 62 | AmbientLight, 63 | PointLight, 64 | AreaLight, 65 | DirectionalLight, 66 | HemisphereLight, 67 | SpotLight, 68 | Helper, 69 | Constants, 70 | render: ReactDOM.render, 71 | unmountComponentAtNode: ReactDOM.unmountComponentAtNode 72 | }; 73 | -------------------------------------------------------------------------------- /src/ReactTHREEMonkeyPatch.js: -------------------------------------------------------------------------------- 1 | // 2 | // time to monkey-patch React! 3 | // 4 | // a subtle bug happens when ReactCompositeComponent updates something in-place by 5 | // modifying HTML markup; since THREE objects don't exist as markup the whole thing bombs. 6 | // we try to fix this by monkey-patching ReactCompositeComponent 7 | // 8 | 9 | "use strict"; 10 | 11 | import ReactCompositeComponent from 'react-dom/lib/ReactCompositeComponent'; 12 | import ReactReconciler from 'react-dom/lib/ReactReconciler'; 13 | 14 | import shouldUpdateReactComponent from 'react-dom/lib/shouldUpdateReactComponent'; 15 | import warning from 'fbjs/lib/warning'; 16 | 17 | // 18 | // Composite components don't have an Object3D. So we have to do some work to find 19 | // the proper Object3D sometimes. 20 | // 21 | 22 | 23 | function findObject3DChild(componentinstance) { 24 | // walk downwards via _renderedComponent to find something with a displayObject 25 | var componentwalker = componentinstance; 26 | while (typeof componentwalker !== 'undefined') { 27 | // no displayObject? then fail 28 | if (typeof componentwalker._THREEObject3D !== 'undefined') { 29 | return componentwalker._THREEObject3D; 30 | } 31 | componentwalker = componentwalker._renderedComponent; 32 | } 33 | 34 | // we walked all the way down and found no Object3D 35 | return undefined; 36 | 37 | } 38 | 39 | // 40 | // This modified version of updateRenderedComponent will 41 | // manage Object3D nodes instead of HTML markup 42 | // 43 | var old_updateRenderedComponent = ReactCompositeComponent._updateRenderedComponent; 44 | 45 | var ReactTHREE_updateRenderedComponent = function(transaction, context) { 46 | var prevComponentInstance = this._renderedComponent; 47 | 48 | // Find the first actual rendered (non-Composite) component. 49 | // If that component is a THREE node we use the special code here. 50 | // If not, we call back to the original version of updateComponent 51 | // which should handle all non-THREE nodes. 52 | 53 | var prevObject3D = findObject3DChild(prevComponentInstance); 54 | if (!prevObject3D) { 55 | // not a THREE node, use the original DOM-style version 56 | old_updateRenderedComponent.call(this,transaction, context); 57 | return; 58 | } 59 | 60 | // This is a THREE node, do a special THREE version of updateComponent 61 | var prevRenderedElement = prevComponentInstance._currentElement; 62 | var nextRenderedElement = this._renderValidatedComponent(); 63 | 64 | if (shouldUpdateReactComponent(prevRenderedElement, nextRenderedElement)) { 65 | ReactReconciler.receiveComponent( 66 | prevComponentInstance, 67 | nextRenderedElement, 68 | transaction, 69 | this._processChildContext(context) 70 | ); 71 | } else { 72 | // We can't just update the current component. 73 | // So we nuke the current instantiated component and put a new component in 74 | // the same place based on the new props. 75 | var thisID = this._rootNodeID; 76 | 77 | var object3DParent = prevObject3D.parent; 78 | 79 | // unmounting doesn't disconnect the child from the parent node, 80 | // but later on we'll simply overwrite the proper element in the 'children' data member 81 | var object3DIndex = object3DParent.children.indexOf(prevObject3D); 82 | ReactReconciler.unmountComponent(prevComponentInstance); 83 | 84 | // create the new object and stuff it into the place vacated by the old object 85 | this._renderedComponent = this._instantiateReactComponent( 86 | nextRenderedElement, 87 | this._currentElement.type); 88 | var nextObject3D = ReactReconciler.mountComponent( 89 | this._renderedComponent, 90 | thisID, 91 | transaction, 92 | this._processChildContext(context) 93 | ); 94 | this._renderedComponent._THREEObject3D = nextObject3D; 95 | 96 | // fixup _mountImage as well 97 | this._mountImage = nextObject3D; 98 | 99 | // overwrite the old child 100 | object3DParent.children[object3DIndex] = nextObject3D; 101 | } 102 | }; 103 | 104 | // 105 | // This generates a patched version of ReactReconciler.receiveComponent to check the type of the 106 | // component and patch it if it's an unpatched version of ReactCompositeComponentWrapper 107 | // 108 | 109 | var buildPatchedReceiveComponent = function(oldReceiveComponent) { 110 | var newReceiveComponent = function( 111 | internalInstance, nextElement, transaction, context 112 | ) { 113 | // if the instance is a ReactCompositeComponentWrapper, fixed it if needed 114 | var ComponentPrototype = Object.getPrototypeOf(internalInstance); 115 | 116 | // if this is a composite component it wil have _updateRenderedComponent defined 117 | if (typeof ComponentPrototype._updateRenderedComponent !== 'undefined') { 118 | // check first to make sure we don't patch it twice 119 | if (ComponentPrototype._updateRenderedComponent !== ReactTHREE_updateRenderedComponent) { 120 | ComponentPrototype._updateRenderedComponent = ReactTHREE_updateRenderedComponent; 121 | } 122 | } 123 | 124 | oldReceiveComponent.call(this,internalInstance, nextElement, transaction, context); 125 | }; 126 | 127 | return newReceiveComponent; 128 | }; 129 | 130 | 131 | var ReactTHREEMonkeyPatch = function() { 132 | 133 | // in order version we patched ReactCompositeComponentMixin, but in 0.13 the 134 | // prototype is wrapped in a ReactCompositeComponentWrapper so monkey-patching 135 | // ReactCompositeComponentMixin won't actually have any effect. 136 | // 137 | // Really we want to patch ReactCompositeComponentWrapper but it's hidden inside 138 | // the instantiateReactComponent module. urgh. 139 | // 140 | // So what we have to do is patch ReactReconciler to detect the first time an 141 | // instance of ReactCompositeComponentWrapper is used, and patch it THEN 142 | // 143 | // urk. 144 | 145 | var old_ReactReconciler_receiveComponent = ReactReconciler.receiveComponent; 146 | 147 | // check to see if we already patched it, so we don't patch again 148 | if (typeof old_ReactReconciler_receiveComponent._ReactTHREEPatched === 'undefined') { 149 | warning(false,"patching react to work with react-three"); 150 | 151 | ReactReconciler.receiveComponent = buildPatchedReceiveComponent(old_ReactReconciler_receiveComponent); 152 | ReactReconciler.receiveComponent._ReactTHREEPatched = true; 153 | } 154 | }; 155 | 156 | export default ReactTHREEMonkeyPatch; 157 | 158 | -------------------------------------------------------------------------------- /src/Utils.js: -------------------------------------------------------------------------------- 1 | import * as THREE from 'three'; 2 | import assign from 'object-assign'; 3 | import warning from 'fbjs/lib/warning'; 4 | 5 | export function createTHREEComponent(name, ...mixins) { 6 | let ReactTHREEComponent = function(element) { 7 | this.node = null; 8 | this._mountImage = null; 9 | this._nativeParent = null; 10 | this._nativeContainerInfo = null; 11 | this._renderedChildren = null; 12 | this._THREEObject3D = null; 13 | this._THREEMetaData = null; 14 | this._currentElement = element; 15 | }; 16 | ReactTHREEComponent.displayName = name; 17 | 18 | for (var m of mixins) { 19 | assign(ReactTHREEComponent.prototype, m); 20 | } 21 | 22 | return ReactTHREEComponent; 23 | } 24 | 25 | 26 | export function setNewLightColor(targetColor, sourceValue) { 27 | // function to set a light color. The sourcevalue 28 | // can be either a number (usually in hex: 0xff0000) 29 | // or a THREE.Color 30 | 31 | // is the prop a hex number or a THREE.Color? 32 | if (typeof sourceValue === 'number') { 33 | targetColor.setHex(sourceValue); 34 | } else if (typeof sourceValue === 'object' && 35 | sourceValue !== null && 36 | sourceValue instanceof THREE.Color) { 37 | targetColor.copy(sourceValue); 38 | } else { 39 | warning(false, "Light color must be a number or an instance of THREE.Color"); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/components/THREERenderer.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import ReactDOM from 'react-dom'; 4 | import ReactUpdates from 'react-dom/lib/ReactUpdates'; 5 | import warning from 'fbjs/lib/warning'; 6 | import * as THREE from 'three'; 7 | import THREEContainerMixin from '../mixins/THREEContainerMixin'; 8 | 9 | import EventPluginHub from 'react-dom/lib/EventPluginHub'; 10 | 11 | // 12 | // The 'Scene' component includes both the three.js scene and 13 | // the canvas DOM element that three.js renders onto. 14 | // 15 | 16 | const THREERenderer = React.createClass({ 17 | displayName: 'THREERenderer', 18 | mixins: [THREEContainerMixin], 19 | 20 | propTypes: { 21 | enableRapidRender: React.PropTypes.bool, 22 | pixelRatio: React.PropTypes.number, 23 | pointerEvents: React.PropTypes.arrayOf(React.PropTypes.string), 24 | transparent: React.PropTypes.bool, 25 | disableHotLoader: React.PropTypes.bool, 26 | customRender: React.PropTypes.func, 27 | style: React.PropTypes.object 28 | }, 29 | 30 | getDefaultProps() { 31 | return { 32 | enableRapidRender: true, 33 | pixelRatio: 1, 34 | transparent: false, 35 | disableHotLoader: false, 36 | style: {}, 37 | rendererProps: {} 38 | }; 39 | }, 40 | 41 | componentDidMount() { 42 | const renderelement = this.props.canvas || ReactDOM.findDOMNode(this); 43 | const props = this.props; 44 | const context = this._reactInternalInstance._context; 45 | 46 | // manually mounting things in a 'createClass' component messes up react internals 47 | // need to fix up some fields 48 | this._rootNodeID = ""; 49 | 50 | this._customRender = this.props.customRender; 51 | this._THREErenderer = new THREE.WebGLRenderer({ 52 | alpha: this.props.transparent, 53 | canvas: renderelement, 54 | antialias: props.antialias === undefined ? true : props.antialias, 55 | ...this.props.rendererProps 56 | }); 57 | this._THREErenderer.shadowMap.enabled = props.shadowMapEnabled !== undefined ? props.shadowMapEnabled : false; 58 | if (props.shadowMapType !== undefined) { 59 | this._THREErenderer.shadowMap.type = props.shadowMapType; 60 | } 61 | this._THREErenderer.setPixelRatio(props.pixelRatio); 62 | this._THREErenderer.setSize(+props.width, +props.height); 63 | 64 | this._debugID = this._reactInternalInstance._debugID; 65 | 66 | const transaction = ReactUpdates.ReactReconcileTransaction.getPooled(); 67 | transaction.perform( 68 | this.mountAndAddChildren, 69 | this, 70 | props.children, 71 | transaction, 72 | context 73 | ); 74 | ReactUpdates.ReactReconcileTransaction.release(transaction); 75 | 76 | // THREEScene binds the pointer events and orbit camera but needs a canvas/DOM element to bind to. 77 | // The canvas is stored in the renderer, though, so we have to get the canvas here in the renderer and 78 | // then bind pointer events/orbit controls in child scenes 79 | const renderedComponent = this._reactInternalInstance._renderedComponent; 80 | const renderedChildren = this._renderedChildren; 81 | if (renderedChildren) { 82 | for (var childkey in renderedChildren) { 83 | if (renderedChildren.hasOwnProperty(childkey)) { 84 | let child = renderedChildren[childkey]; 85 | child.bindOrbitControls(renderedComponent, renderelement, child._currentElement.props); 86 | child.bindPointerEvents(renderedComponent, renderelement, child._currentElement.props); 87 | } 88 | } 89 | } 90 | 91 | // hack for react-hot-loader 92 | if (!this.props.disableHotLoader && 93 | renderedComponent._currentElement !== null) { 94 | renderedComponent._renderedChildren = this._renderedChildren; 95 | } 96 | 97 | const backgroundtype = typeof props.background; 98 | if (backgroundtype !== 'undefined') { 99 | // background color should be a number, check it 100 | warning(backgroundtype === 'number', "The background property of "+ 101 | "the Renderer component must be a number, not " + backgroundtype); 102 | this._THREErenderer.setClearColor(props.background, this.props.transparent ? 0 : 1); 103 | } 104 | 105 | this.renderScene(); 106 | 107 | // The canvas gets re-rendered every frame even if no props/state changed. 108 | // This is because some three.js items like skinned meshes need redrawing 109 | // every frame even if nothing changed in React props/state. 110 | // 111 | // See https://github.com/Izzimach/react-three/issues/28 112 | 113 | if (this.props.enableRapidRender) { 114 | const rapidrender = (timestamp) => { 115 | 116 | this._timestamp = timestamp; 117 | if (typeof this._rAFID !== 'undefined') { 118 | this._rAFID = window.requestAnimationFrame( rapidrender ); 119 | } 120 | 121 | // render the stage 122 | this.renderScene(); 123 | } 124 | 125 | this._rAFID = window.requestAnimationFrame( rapidrender ); 126 | } 127 | 128 | // warn users of the old listenToClick prop 129 | warning(typeof props.listenToClick === 'undefined', "the `listenToClick` prop has been replaced with `pointerEvents`"); 130 | 131 | renderelement.onselectstart = () => false; 132 | }, 133 | 134 | componentDidUpdate(oldProps) { 135 | const props = this.props; 136 | const context = this._reactInternalInstance._context; 137 | 138 | if (props.pixelRatio !== oldProps.pixelRatio) { 139 | this._THREErenderer.setPixelRatio(props.pixelRatio); 140 | } 141 | 142 | if (props.width !== oldProps.width || 143 | props.height !== oldProps.height || 144 | props.pixelRatio !== oldProps.pixelRatio) { 145 | this._THREErenderer.setSize(+props.width, +props.height); 146 | } 147 | 148 | const backgroundtype = typeof props.background; 149 | if (backgroundtype !== 'undefined') { 150 | // background color should be a number, check it 151 | warning(backgroundtype === 'number', "The background property of "+ 152 | "the scene component must be a number, not " + backgroundtype); 153 | this._THREErenderer.setClearColor(props.background, this.props.transparent ? 0 : 1); 154 | } 155 | 156 | const transaction = ReactUpdates.ReactReconcileTransaction.getPooled(); 157 | transaction.perform( 158 | this.updateChildren, 159 | this, 160 | this.props.children, 161 | transaction, 162 | context 163 | ); 164 | ReactUpdates.ReactReconcileTransaction.release(transaction); 165 | 166 | // hack for react-hot-loader 167 | const renderedComponent = this._reactInternalInstance._renderedComponent; 168 | if (!this.props.disableHotLoader && 169 | renderedComponent._currentElement !== null) { 170 | renderedComponent._renderedChildren = this._renderedChildren; 171 | } 172 | 173 | this.renderScene(); 174 | }, 175 | 176 | componentWillUnmount() { 177 | // hack for react-hot-loader 178 | const renderedComponent = this._reactInternalInstance._renderedComponent; 179 | if (!this.props.disableHotLoader && 180 | renderedComponent._currentElement !== null) { 181 | renderedComponent._renderedChildren = null; 182 | } 183 | EventPluginHub.deleteAllListeners(this._reactInternalInstance); 184 | if (typeof this._rAFID !== 'undefined') { 185 | window.cancelAnimationFrame(this._rAFID); 186 | delete this._rAFID; 187 | } 188 | }, 189 | 190 | renderScene() { 191 | if (!this.props.children) return; 192 | 193 | const children = this._renderedChildren; 194 | 195 | this._THREErenderer.autoClear = false; 196 | this._THREErenderer.clear(); 197 | 198 | Object.keys(children).forEach(key => { 199 | const scene = children[key]; 200 | if (scene._THREEObject3D && 201 | scene._THREEMetaData.camera !== null) { 202 | if (this._customRender) { 203 | this._customRender( 204 | this._THREErenderer, 205 | scene._THREEObject3D, 206 | scene._THREEMetaData.camera 207 | ); 208 | } 209 | else { 210 | this._THREErenderer.render( 211 | scene._THREEObject3D, 212 | scene._THREEMetaData.camera 213 | ); 214 | } 215 | } 216 | }); 217 | 218 | }, 219 | 220 | render() { 221 | if (this.props.canvas) return null; 222 | 223 | // the three.js renderer will get applied to this canvas element 224 | return React.createElement("canvas", {style: this.props.style}); 225 | } 226 | }); 227 | 228 | export default THREERenderer; 229 | -------------------------------------------------------------------------------- /src/components/THREEScene.js: -------------------------------------------------------------------------------- 1 | import ReactMount from 'react-dom/lib/ReactMount'; 2 | import { listenTo } from 'react-dom/lib/ReactBrowserEventEmitter'; 3 | import EventPluginHub from 'react-dom/lib/EventPluginHub'; 4 | 5 | import * as THREE from 'three'; 6 | import THREEObject3DMixin from '../mixins/THREEObject3DMixin'; 7 | import {createTHREEComponent} from '../Utils'; 8 | 9 | import warning from 'fbjs/lib/warning'; 10 | 11 | const ELEMENT_TYPE_NODE = 1; // can't access this in ReactDOMComponent 12 | 13 | // 14 | // The 'Scene' component includes the three.js scene 15 | // 16 | 17 | var THREEScene = createTHREEComponent( 18 | 'THREEScene', 19 | THREEObject3DMixin, 20 | { 21 | createTHREEObject: function() { 22 | return new THREE.Scene(); 23 | }, 24 | 25 | applySpecificTHREEProps: function (oldProps, newProps) { // eslint-disable-line no-unused-vars 26 | // can't bind the camera here since children may not be mounted yet 27 | }, 28 | 29 | mountComponent: function (transaction, nativeParent, nativeContainerInfo, context) { 30 | let props = this._currentElement.props; 31 | THREEObject3DMixin.mountComponent.call(this, transaction, nativeParent, nativeContainerInfo, context); 32 | this._THREEMetaData = { 33 | camera: null, 34 | raycaster : new THREE.Raycaster() 35 | }; 36 | this.bindCamera(props); 37 | 38 | if (props.projectPointerEventRef) { 39 | props.projectPointerEventRef(this.projectPointerEvent.bind(this)); 40 | } 41 | 42 | // this now gets called by the renderer so that canvas is provided 43 | //this.bindPointerEvents(rootID, props); 44 | return this._THREEObject3D; 45 | }, 46 | 47 | receiveComponent: function (nextElement, transaction, context) { 48 | THREEObject3DMixin.receiveComponent.call(this, nextElement, transaction, context); 49 | }, 50 | 51 | unmountComponent: function() { 52 | THREEObject3DMixin.unmountComponent.call(this); 53 | }, 54 | 55 | getNativeNode() { 56 | return this._THREEObject3D; 57 | }, 58 | 59 | bindCamera: function(props) { 60 | var camera = props.camera; 61 | if (typeof camera === 'string') { 62 | camera = this._THREEObject3D.getObjectByName(camera, true); 63 | } 64 | else if (camera === null || (typeof camera === 'undefined')) { 65 | warning(false, "No camera prop specified for react-three scene, using 'maincamera'"); 66 | // look for a 'maincamera' object; if none, then make a default camera 67 | camera = this._THREEObject3D.getObjectByName('maincamera', true); 68 | if (typeof camera === 'undefined') { 69 | warning(false, "No camera named 'maincamera' found, creating a default camera"); 70 | camera = new THREE.PerspectiveCamera( 75, props.width / props.height, 1, 5000 ); 71 | camera.aspect = props.width / props.height; 72 | camera.updateProjectionMatrix(); 73 | camera.position.z = 600; 74 | } 75 | } 76 | 77 | this._THREEMetaData.camera = camera; 78 | }, 79 | 80 | bindOrbitControls: function(inst, canvas, props) { 81 | if (props.orbitControls && typeof props.orbitControls === 'function') { 82 | if (!this._THREEMetaData.orbitControls && canvas) { 83 | this._THREEMetaData.orbitControls = new props.orbitControls(this._THREEMetaData.camera, canvas); 84 | } 85 | } 86 | }, 87 | 88 | bindPointerEvents: function (inst, canvas, props) { 89 | if (props.pointerEvents) { 90 | if (canvas) { 91 | props.pointerEvents.forEach(eventName => { 92 | listenTo(eventName, canvas); 93 | EventPluginHub.putListener( 94 | inst, 95 | eventName, 96 | event => this.projectPointerEvent(event, eventName, canvas) ); 97 | }); 98 | } 99 | } 100 | 101 | }, 102 | 103 | findRootDOMNode: function(rootID) { 104 | // fiddle with some internals here - probably a bit brittle 105 | const container = ReactMount.findReactContainerForID(rootID); 106 | return container; 107 | }, 108 | 109 | findDocumentContainer: function() { 110 | let container = this.findRootDOMNode(); 111 | if (container) { 112 | return (container.elementType === ELEMENT_TYPE_NODE ? 113 | container.ownerDocument : 114 | container); 115 | } 116 | return null; 117 | }, 118 | 119 | projectPointerEvent: function (event, eventName, canvas) { 120 | event.preventDefault(); 121 | var rect = canvas.getBoundingClientRect(); 122 | 123 | const {clientX, clientY} = event.touches ? event.touches[0] : event; 124 | var x = ( (clientX - rect.left) / rect.width) * 2 - 1; 125 | var y = - ( (clientY - rect.top) / rect.height) * 2 + 1; 126 | 127 | var mousecoords = new THREE.Vector3(x,y,0.5); 128 | let { raycaster, camera } = this._THREEMetaData; 129 | 130 | raycaster.setFromCamera(mousecoords, camera); 131 | 132 | var intersections = raycaster.intersectObjects( this._THREEObject3D.children, true ); 133 | var firstintersection = ( intersections.length ) > 0 ? intersections[ 0 ] : null; 134 | 135 | if (firstintersection !== null) { 136 | var pickobject = firstintersection.object; 137 | if (typeof pickobject.userData !== 'undefined' && pickobject.userData._currentElement) { 138 | var onpickfunction = pickobject.userData._currentElement.props[eventName + '3D']; 139 | if (typeof onpickfunction === 'function') { 140 | onpickfunction(event, firstintersection); 141 | } 142 | } 143 | } 144 | } 145 | } 146 | ); 147 | 148 | module.exports = THREEScene; 149 | -------------------------------------------------------------------------------- /src/components/cameras/THREEOrthographicCamera.js: -------------------------------------------------------------------------------- 1 | import * as THREE from 'three'; 2 | import { createTHREEComponent } from '../../Utils'; 3 | import THREEObject3DMixin from '../../mixins/THREEObject3DMixin'; 4 | 5 | var THREEOrthographicCamera = createTHREEComponent( 6 | 'OrthographicCamera', 7 | THREEObject3DMixin, 8 | { 9 | createTHREEObject: function() { 10 | return new THREE.OrthographicCamera(); 11 | }, 12 | 13 | applySpecificTHREEProps: function(oldProps, newProps) { 14 | this.transferTHREEObject3DPropsByName(oldProps, newProps, 15 | ['left','right','top','bottom','near','far']); 16 | 17 | this._THREEObject3D.updateProjectionMatrix(); 18 | } 19 | } 20 | ); 21 | 22 | export default THREEOrthographicCamera; 23 | -------------------------------------------------------------------------------- /src/components/cameras/THREEPerspectiveCamera.js: -------------------------------------------------------------------------------- 1 | import * as THREE from 'three'; 2 | import { createTHREEComponent } from '../../Utils'; 3 | import THREEObject3DMixin from '../../mixins/THREEObject3DMixin'; 4 | 5 | var THREEPerspectiveCamera = createTHREEComponent( 6 | 'PerspectiveCamera', 7 | THREEObject3DMixin, 8 | { 9 | createTHREEObject: function() { 10 | return new THREE.PerspectiveCamera(); 11 | }, 12 | 13 | applySpecificTHREEProps: function(oldProps, newProps) { 14 | this.transferTHREEObject3DPropsByName(oldProps, newProps, 15 | ['fov','aspect','near','far']); 16 | 17 | this._THREEObject3D.updateProjectionMatrix(); 18 | } 19 | }); 20 | 21 | export default THREEPerspectiveCamera; 22 | -------------------------------------------------------------------------------- /src/components/extras/THREEDecoratorHelper.js: -------------------------------------------------------------------------------- 1 | import * as THREE from 'three'; 2 | import { createTHREEComponent } from '../../Utils'; 3 | import THREEObject3DMixin from '../../mixins/THREEObject3DMixin'; 4 | import _ from 'lodash'; 5 | 6 | export default createTHREEComponent( 7 | 'THREEDecoratorHelper', 8 | THREEObject3DMixin, 9 | { 10 | createTHREEObject() { 11 | return new THREE.Object3D(); 12 | }, 13 | 14 | applySpecificTHREEProps(/*oldProps, newProps*/) { 15 | // can't apply helpers here since the children haven't be mounted yet 16 | }, 17 | 18 | applyHelpers(newHelperTypes) { 19 | // an array of helper instances 20 | let currentHelpers = this._THREEMetaData; 21 | 22 | // The actual threejs nodes. The immediate child is wrapped by helpers, and helpers 23 | // get put in the base object for this component. 24 | let helperContainerNode = this._THREEObject3D; 25 | let helperWrapNode = helperContainerNode.children[0]; 26 | 27 | // figure out which helpers to add and remove 28 | let helperAddTypeList = _.differenceWith(newHelperTypes, currentHelpers, (a,b) => b instanceof a); 29 | let helperRemoveList = _.differenceWith(currentHelpers, newHelperTypes, (a,b) => a instanceof b); 30 | 31 | for (let newHelperType of helperAddTypeList) { 32 | let newHelper = new newHelperType(helperWrapNode); 33 | currentHelpers.push(newHelper); 34 | helperContainerNode.children.push(newHelper); 35 | } 36 | 37 | for (let removeHelper of helperRemoveList) { 38 | _.remove(currentHelpers, removeHelper); 39 | _.remove(helperContainerNode.children, removeHelper); 40 | } 41 | }, 42 | 43 | mountComponent(rootID, transaction, context) { 44 | let props = this._currentElement.props; 45 | THREEObject3DMixin.mountComponent.call(this, rootID, transaction, context); 46 | 47 | // for this component the metadata is a set of helper objects that 'wrap' the child 48 | this._THREEMetaData = []; 49 | 50 | let helpers = props.helpers || []; 51 | this.applyHelpers(helpers); 52 | 53 | return this._THREEObject3D; 54 | }, 55 | 56 | receiveComponent(nextElement, transaction, context) { 57 | let newProps = nextElement.props; 58 | THREEObject3DMixin.receiveComponent.call(this, nextElement, transaction, context); 59 | this.applyHelpers(newProps.helpers); 60 | 61 | // call update methods where they exist on the helpers 62 | let currentHelpers = this._THREEMetaData; 63 | let helperContainerNode = this._THREEObject3D; 64 | let helperWrapNode = helperContainerNode.children[0]; 65 | for (let helper of currentHelpers) { 66 | if (helper.update) { 67 | helper.update(helperWrapNode); 68 | } 69 | } 70 | }, 71 | 72 | unmountComponent() { 73 | this.applyHelpers([]); // should remove all helpers 74 | THREEObject3DMixin.unmountComponent.call(this); 75 | } 76 | } 77 | ); 78 | -------------------------------------------------------------------------------- /src/components/lights/CommonShadowmapProps.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | const shadowPropNames = [ 4 | // 'shadowCameraNear', // moved 5 | // 'shadowCameraFar', // moved 6 | 'shadowCameraVisible', 7 | // 'shadowBias', // kmoved 8 | // 'shadowDarkness', // removed 9 | // 'shadowMapWidth', // moved 10 | // 'shadowMapHeight', // moved 11 | 'shadowMap', 12 | // 'shadowMapSize', // moved? did this ever exist? 13 | 'shadowCamera', 14 | 'shadowMatrix' 15 | ]; 16 | 17 | export default function tranferCommonShadowmapProps(THREEObject3D, newProps) 18 | { 19 | if (typeof THREEObject3D.shadow === 'undefined') { 20 | // ??? issue a warning? 21 | return; 22 | } 23 | 24 | // props that reference inner object values 25 | if (typeof newProps.shadowBias !== 'undefined') { 26 | THREEObject3D.shadow.bias = newProps.shadowBias; 27 | } 28 | if (typeof newProps.shadowMapWidth !== 'undefined') { 29 | THREEObject3D.shadow.mapSize.width = newProps.shadowMapWidth; 30 | } 31 | if (typeof newProps.shadowMapHeight !== 'undefined') { 32 | THREEObject3D.shadow.mapSize.height = newProps.shadowMapHeight; 33 | } 34 | if (typeof newProps.shadowCameraFar !== 'undefined') { 35 | THREEObject3D.shadow.camera.far = newProps.shadowCameraFar; 36 | } 37 | if (typeof newProps.shadowCameraNear !== 'undefined') { 38 | THREEObject3D.shadow.camera.near = newProps.shadowCameraNear; 39 | } 40 | if (typeof newProps.shadowCameraFov !== 'undefined') { 41 | THREEObject3D.shadow.camera.fov = newProps.shadowCameraFov; 42 | } 43 | 44 | // normal props we can transfer by name 45 | shadowPropNames.forEach(function(propname) { 46 | if (typeof newProps[propname] !== 'undefined') { 47 | THREEObject3D[propname] = newProps[propname]; 48 | } 49 | }); 50 | } 51 | -------------------------------------------------------------------------------- /src/components/lights/THREEAmbientLight.js: -------------------------------------------------------------------------------- 1 | import * as THREE from 'three'; 2 | import { createTHREEComponent } from '../../Utils'; 3 | import THREEObject3DMixin from '../../mixins/THREEObject3DMixin'; 4 | import LightObjectMixin from '../../mixins/LightObjectMixin'; 5 | 6 | var THREEAmbientLight = createTHREEComponent( 7 | 'AmbientLight', 8 | THREEObject3DMixin, 9 | { 10 | createTHREEObject: function() { 11 | return new THREE.AmbientLight(0x000000); 12 | }, 13 | 14 | applySpecificTHREEProps: function (oldProps, newProps) { 15 | LightObjectMixin.applySpecificTHREEProps.call(this, oldProps, newProps); 16 | } 17 | } 18 | ); 19 | 20 | export default THREEAmbientLight; 21 | -------------------------------------------------------------------------------- /src/components/lights/THREEAreaLight.js: -------------------------------------------------------------------------------- 1 | import * as THREE from 'three'; 2 | import { createTHREEComponent } from '../../Utils'; 3 | import THREEObject3DMixin from '../../mixins/THREEObject3DMixin'; 4 | import LightObjectMixin from '../../mixins/LightObjectMixin'; 5 | 6 | var THREEAreaLight = createTHREEComponent( 7 | 'AreaLight', 8 | THREEObject3DMixin, 9 | { 10 | createTHREEObject: function() { 11 | return new THREE.AreaLight(0xffffff, 1); 12 | }, 13 | 14 | applySpecificTHREEProps: function(oldProps, newProps) { 15 | LightObjectMixin.applySpecificTHREEProps.call(this, oldProps, newProps); 16 | 17 | this.transferTHREEObject3DPropsByName(oldProps, newProps, 18 | ['right', 19 | 'normal', 20 | 'height', 21 | 'width', 22 | 'intensity', 23 | 'constantAttenuation', 24 | 'linearAttenuation', 25 | 'quadraticAttenuation']); 26 | } 27 | } 28 | ); 29 | 30 | export default THREEAreaLight; 31 | -------------------------------------------------------------------------------- /src/components/lights/THREEDirectionalLight.js: -------------------------------------------------------------------------------- 1 | import * as THREE from 'three'; 2 | import { createTHREEComponent } from '../../Utils'; 3 | import THREEObject3DMixin from '../../mixins/THREEObject3DMixin'; 4 | import LightObjectMixin from '../../mixins/LightObjectMixin'; 5 | import transferCommonShadowmapProps from './CommonShadowmapProps'; 6 | 7 | var THREEDirectionalLight = createTHREEComponent( 8 | 'DirectionalLight', 9 | THREEObject3DMixin, 10 | { 11 | createTHREEObject: function() { 12 | return new THREE.DirectionalLight(0xffffff,1); 13 | }, 14 | 15 | applySpecificTHREEProps: function(oldProps, newProps) { 16 | LightObjectMixin.applySpecificTHREEProps.call(this, oldProps, newProps); 17 | 18 | transferCommonShadowmapProps(this._THREEObject3D, newProps); 19 | 20 | this.transferTHREEObject3DPropsByName(oldProps, newProps, 21 | ['target', 22 | 'intensity', 23 | 'onlyShadow', 24 | 'shadowCameraLeft', 25 | 'shadowCameraRight', 26 | 'shadowCameraTop', 27 | 'shadowCameraBottom', 28 | 'shadowCascade', 29 | 'shadowCascadeOffset', 30 | 'shadowCascadeCount', 31 | 'shadowCascadeBias', 32 | 'shadowCascadeWidth', 33 | 'shadowCascadeHeight', 34 | 'shadowCascadeNearZ', 35 | 'shadowCascadeFarZ', 36 | 'shadowCascadeArray']); 37 | 38 | } 39 | } 40 | ); 41 | 42 | export default THREEDirectionalLight; 43 | -------------------------------------------------------------------------------- /src/components/lights/THREEHemisphereLight.js: -------------------------------------------------------------------------------- 1 | import * as THREE from 'three'; 2 | import {createTHREEComponent, setNewLightColor} from '../../Utils'; 3 | import THREEObject3DMixin from '../../mixins/THREEObject3DMixin'; 4 | import LightObjectMixin from '../../mixins/LightObjectMixin'; 5 | 6 | var THREEHemisphereLight = createTHREEComponent( 7 | 'HemisphereLight', 8 | THREEObject3DMixin, 9 | { 10 | createTHREEObject: function() { 11 | return new THREE.HemisphereLight(0x8888ff, 0x000000, 1); 12 | }, 13 | 14 | applySpecificTHREEProps: function(oldProps, newProps) { 15 | LightObjectMixin.applySpecificTHREEProps.call(this, oldProps, newProps); 16 | 17 | // sky color gets mapped to 'color' 18 | if (typeof newProps.skyColor !== 'undefined') { 19 | setNewLightColor(this._THREEObject3D.color, newProps.skyColor); 20 | } 21 | 22 | this.transferTHREEObject3DPropsByName(oldProps, newProps, 23 | ['groundColor', 24 | 'intensity']); 25 | } 26 | } 27 | ); 28 | 29 | export default THREEHemisphereLight; 30 | -------------------------------------------------------------------------------- /src/components/lights/THREEPointLight.js: -------------------------------------------------------------------------------- 1 | import * as THREE from 'three'; 2 | import { createTHREEComponent } from '../../Utils'; 3 | import THREEObject3DMixin from '../../mixins/THREEObject3DMixin'; 4 | import LightObjectMixin from '../../mixins/LightObjectMixin'; 5 | 6 | var THREEPointLight = createTHREEComponent( 7 | 'PointLight', 8 | THREEObject3DMixin, 9 | { 10 | createTHREEObject: function() { 11 | return new THREE.PointLight(0xffffff, 1, 100); 12 | }, 13 | 14 | applySpecificTHREEProps: function(oldProps, newProps) { 15 | LightObjectMixin.applySpecificTHREEProps.call(this, oldProps, newProps); 16 | 17 | this.transferTHREEObject3DPropsByName(oldProps, newProps, ['intensity','distance']); 18 | } 19 | } 20 | ); 21 | 22 | export default THREEPointLight; 23 | -------------------------------------------------------------------------------- /src/components/lights/THREESpotLight.js: -------------------------------------------------------------------------------- 1 | import * as THREE from 'three'; 2 | import { createTHREEComponent } from '../../Utils'; 3 | import THREEObject3DMixin from '../../mixins/THREEObject3DMixin'; 4 | import LightObjectMixin from '../../mixins/LightObjectMixin'; 5 | import transferCommonShadowmapProps from './CommonShadowmapProps'; 6 | 7 | var THREESpotLight = createTHREEComponent( 8 | 'SpotLight', 9 | THREEObject3DMixin, 10 | { 11 | createTHREEObject: function() { 12 | return new THREE.SpotLight(0xffffff, 1); 13 | }, 14 | 15 | applySpecificTHREEProps: function(oldProps, newProps) { 16 | LightObjectMixin.applySpecificTHREEProps.call(this, oldProps, newProps); 17 | 18 | 19 | transferCommonShadowmapProps(this._THREEObject3D, newProps); 20 | this.transferTHREEObject3DPropsByName(oldProps, newProps, 21 | ['target', 22 | 'intensity', 23 | 'distance', 24 | 'angle', 25 | 'exponent', 26 | 'castShadow', 27 | 'onlyShadow', 28 | 'shadowCameraFov']); 29 | } 30 | } 31 | ); 32 | 33 | module.exports = THREESpotLight; 34 | -------------------------------------------------------------------------------- /src/components/objects/THREEAxisHelper.js: -------------------------------------------------------------------------------- 1 | import * as THREE from 'three'; 2 | import { createTHREEComponent } from '../../Utils'; 3 | import THREEObject3DMixin from '../../mixins/THREEObject3DMixin'; 4 | 5 | export default createTHREEComponent( 6 | 'AxisHelper', 7 | THREEObject3DMixin, 8 | { 9 | createTHREEObject: function() { 10 | return new THREE.AxisHelper(this._currentElement.props.size || 5); 11 | } 12 | } 13 | ); 14 | -------------------------------------------------------------------------------- /src/components/objects/THREELine.js: -------------------------------------------------------------------------------- 1 | import * as THREE from 'three'; 2 | import { createTHREEComponent } from '../../Utils'; 3 | import THREEObject3DMixin from '../../mixins/THREEObject3DMixin'; 4 | 5 | var THREELine = createTHREEComponent( 6 | 'Line', 7 | THREEObject3DMixin, 8 | { 9 | createTHREEObject: function() { 10 | return new THREE.Line(); 11 | }, 12 | 13 | applySpecificTHREEProps: function(oldProps, newProps) { 14 | this.transferTHREEObject3DPropsByName(oldProps,newProps, 15 | ['geometry','material','mode']); 16 | } 17 | } 18 | ); 19 | 20 | export default THREELine; 21 | -------------------------------------------------------------------------------- /src/components/objects/THREELineSegments.js: -------------------------------------------------------------------------------- 1 | import * as THREE from 'three'; 2 | import { createTHREEComponent } from '../../Utils'; 3 | import THREEObject3DMixin from '../../mixins/THREEObject3DMixin'; 4 | 5 | var THREELineSegments = createTHREEComponent( 6 | 'LineSegments', 7 | THREEObject3DMixin, 8 | { 9 | createTHREEObject: function() { 10 | return new THREE.LineSegments(); 11 | }, 12 | 13 | applySpecificTHREEProps: function(oldProps, newProps) { 14 | this.transferTHREEObject3DPropsByName(oldProps,newProps, 15 | ['geometry','material','mode']); 16 | } 17 | } 18 | ); 19 | 20 | export default THREELineSegments; 21 | -------------------------------------------------------------------------------- /src/components/objects/THREEMesh.js: -------------------------------------------------------------------------------- 1 | import * as THREE from 'three'; 2 | import { createTHREEComponent } from '../../Utils'; 3 | import THREEObject3DMixin from '../../mixins/THREEObject3DMixin'; 4 | 5 | var THREEMesh = createTHREEComponent( 6 | 'Mesh', 7 | THREEObject3DMixin, 8 | { 9 | createTHREEObject: function() { 10 | return new THREE.Mesh(new THREE.Geometry(), new THREE.Material()); // starts out empty 11 | }, 12 | 13 | applySpecificTHREEProps: function (oldProps, newProps) { 14 | var THREEObject3D = this._THREEObject3D; 15 | if ((typeof newProps.geometry !== 'undefined') && 16 | (newProps.geometry !== oldProps.geometry)) 17 | { 18 | THREEObject3D.geometry = newProps.geometry; 19 | } 20 | 21 | if ((typeof newProps.material !== 'undefined') && 22 | (newProps.material !== oldProps.material)) 23 | { 24 | THREEObject3D.material = newProps.material; 25 | } 26 | 27 | } 28 | } 29 | ); 30 | 31 | export default THREEMesh; 32 | -------------------------------------------------------------------------------- /src/components/objects/THREEObject3D.js: -------------------------------------------------------------------------------- 1 | import { createTHREEComponent } from '../../Utils'; 2 | import THREEObject3DMixin from '../../mixins/THREEObject3DMixin'; 3 | 4 | var THREEObject3D = createTHREEComponent( 5 | 'Object3D', 6 | THREEObject3DMixin); 7 | 8 | export default THREEObject3D; 9 | -------------------------------------------------------------------------------- /src/components/objects/THREEPointCloud.js: -------------------------------------------------------------------------------- 1 | import * as THREE from 'three'; 2 | import { createTHREEComponent } from '../../Utils'; 3 | import THREEObject3DMixin from '../../mixins/THREEObject3DMixin'; 4 | 5 | var THREEPointCloud = createTHREEComponent( 6 | 'PointCloud', 7 | THREEObject3DMixin, 8 | { 9 | createTHREEObject: function() { 10 | return new THREE.PointCloud(new THREE.Geometry()); 11 | }, 12 | 13 | applySpecificTHREEProps: function(oldProps, newProps) { 14 | this.transferTHREEObject3DPropsByName(oldProps, newProps, 15 | ['geometry','material','frustumCulled','sortParticles']); 16 | } 17 | } 18 | 19 | ); 20 | 21 | export default THREEPointCloud; 22 | -------------------------------------------------------------------------------- /src/components/objects/THREESkinnedMesh.js: -------------------------------------------------------------------------------- 1 | import * as THREE from 'three'; 2 | import { createTHREEComponent } from '../../Utils'; 3 | import THREEObject3DMixin from '../../mixins/THREEObject3DMixin'; 4 | 5 | var THREESkinnedMesh = createTHREEComponent( 6 | 'SkinnedMesh', 7 | THREEObject3DMixin, 8 | { 9 | // skinned mesh is special since it needs the geometry and material data upon construction 10 | /* jshint unused: vars */ 11 | mountComponent: function(rootID, transaction, context) { 12 | this._THREEObject3D = new THREE.SkinnedMesh(this._currentElement.props.geometry, this._currentElement.props.material); 13 | this.applyTHREEObject3DProps({}, this._currentElement.props); 14 | this.applySpecificTHREEProps({}, this._currentElement.props); 15 | 16 | this.mountAndAddChildren(this._currentElement.props.children, transaction, context); 17 | return this._THREEObject3D; 18 | }, 19 | /* jshint unused: true */ 20 | 21 | applySpecificTHREEProps: function(/*oldProps, newProps*/) { 22 | } 23 | } 24 | ); 25 | 26 | export default THREESkinnedMesh; 27 | -------------------------------------------------------------------------------- /src/components/objects/THREESprite.js: -------------------------------------------------------------------------------- 1 | import * as THREE from 'three'; 2 | import { createTHREEComponent } from '../../Utils'; 3 | import THREEObject3DMixin from '../../mixins/THREEObject3DMixin'; 4 | 5 | var THREESprite = createTHREEComponent( 6 | 'Sprite', 7 | THREEObject3DMixin, 8 | { 9 | createTHREEObject: function() { 10 | return new THREE.Sprite(); 11 | }, 12 | 13 | applySpecificTHREEProps: function(oldProps, newProps) { 14 | this.transferTHREEObject3DPropsByName(oldProps, newProps, 15 | ['material']); 16 | } 17 | } 18 | ); 19 | 20 | export default THREESprite; 21 | -------------------------------------------------------------------------------- /src/deps.cljs: -------------------------------------------------------------------------------- 1 | {:foreign-libs 2 | [{:file "react_three/react-three.js" 3 | :file-min "react_three/react-three.js" 4 | :provides ["cljsjs.react"]}] 5 | :externs ["react_three/react-three.js"] 6 | } 7 | -------------------------------------------------------------------------------- /src/mixins/LightObjectMixin.js: -------------------------------------------------------------------------------- 1 | import { setNewLightColor } from '../Utils'; 2 | 3 | var LightObjectMixin = { 4 | applySpecificTHREEProps: function (oldProps, newProps) { 5 | var THREEObject3D = this._THREEObject3D; 6 | if ((typeof newProps.color !== 'undefined') && 7 | (newProps.color !== oldProps.color)) 8 | { 9 | setNewLightColor(THREEObject3D.color, newProps.color); 10 | } 11 | } 12 | }; 13 | 14 | export default LightObjectMixin; 15 | 16 | -------------------------------------------------------------------------------- /src/mixins/THREEContainerMixin.js: -------------------------------------------------------------------------------- 1 | import ReactMultiChild from 'react-dom/lib/ReactMultiChild'; 2 | import assign from 'object-assign'; 3 | 4 | // 5 | // Generates a React component by combining several mixin components 6 | // 7 | 8 | var THREEContainerMixin = assign({}, ReactMultiChild.Mixin, { 9 | moveChild: function(prevChild, lastPlacedNode, nextIndex, lastIndex) { // eslint-disable-line no-unused-vars 10 | // no-op for the renderer 11 | if (typeof this.renderScene !== 'undefined') { return; } 12 | 13 | let prevChildTHREEObject3D = prevChild._mountImage; // should be a three.js Object3D 14 | //let prevChildTHREEObject3D = prevChild.getNativeNode(); 15 | let THREEObject3D = this._mountImage || this._THREEObject3D; 16 | //let THREEObject3D = this.getNativeNode(); 17 | 18 | var prevChildIndex = THREEObject3D.children.indexOf(prevChildTHREEObject3D); 19 | if (prevChildIndex !== -1) { 20 | THREEObject3D.children.splice(prevChildIndex,1); 21 | } 22 | 23 | // remove from old location, put in the new location 24 | THREEObject3D.children.splice(nextIndex,0,prevChildTHREEObject3D); 25 | }, 26 | 27 | createChild: function(child, afterNode, childTHREEObject3D) { 28 | child._mountImage = childTHREEObject3D; 29 | this._THREEObject3D.add(childTHREEObject3D); 30 | }, 31 | 32 | removeChild: function(child, node) { // eslint-disable-line no-unused-vars 33 | var childTHREEObject3D = child._mountImage; 34 | 35 | this._THREEObject3D.remove(childTHREEObject3D); 36 | child._mountImage = null; 37 | }, 38 | 39 | /** 40 | * Override to bypass batch updating because it is not necessary. 41 | * 42 | * @param {?object} nextChildren. 43 | * @param {ReactReconcileTransaction} transaction 44 | * @internal 45 | * @override {ReactMultiChild.Mixin.updateChildren} 46 | */ 47 | updateChildren: function(nextChildren, transaction, context) { 48 | this._updateChildren(nextChildren, transaction, context); 49 | }, 50 | 51 | // called by any container component after it gets mounted 52 | mountAndAddChildren: function(children, transaction, context) { 53 | var mountedImages = this.mountChildren( 54 | children, 55 | transaction, 56 | context 57 | ); 58 | // Each mount image corresponds to one of the flattened children 59 | const thisTHREEObject3D = this._THREEObject3D; 60 | var i = 0; 61 | for (var key in this._renderedChildren) { 62 | if (this._renderedChildren.hasOwnProperty(key)) { 63 | var child = this._renderedChildren[key]; 64 | child._mountImage = mountedImages[i]; 65 | 66 | // THREERenderer has no _THREEObject3D 67 | if (thisTHREEObject3D) thisTHREEObject3D.add(child._mountImage); 68 | i++; 69 | } 70 | } 71 | } 72 | }); 73 | 74 | export default THREEContainerMixin; 75 | -------------------------------------------------------------------------------- /src/mixins/THREEObject3DMixin.js: -------------------------------------------------------------------------------- 1 | import * as THREE from 'three'; 2 | import assign from 'object-assign'; 3 | import THREEContainerMixin from './THREEContainerMixin'; 4 | import warning from 'fbjs/lib/warning'; 5 | 6 | // 7 | // The container methods are use by both the THREEScene composite component 8 | // and by THREEObject3D components, so container/child stuff is in a separate 9 | // mixin (THREEContainerMixin) and here gets merged into the typical THREE 10 | // node methods for applying and updating props 11 | // 12 | var THREEObject3DMixin = assign({}, THREEContainerMixin, { 13 | 14 | construct: function(element) { 15 | this._currentElement = element; 16 | this._THREEObject3D = null; 17 | this._nativeParent = null; 18 | this._nativeContainerInfo = null; 19 | }, 20 | 21 | getPublicInstance: function() { 22 | return this._THREEObject3D; 23 | }, 24 | 25 | createTHREEObject: function() { 26 | return new THREE.Object3D(); 27 | }, 28 | 29 | applyTHREEObject3DProps: function(oldProps, props) { 30 | this.applyTHREEObject3DPropsToObject(this._THREEObject3D, oldProps, props); 31 | }, 32 | 33 | applyTHREEObject3DPropsToObject: function(THREEObject3D, oldProps, props) { 34 | // if a matrix transformation is defined, it will handle position, 35 | // rotation, and scaling 36 | if (typeof props.matrix !== 'undefined') { 37 | THREEObject3D.matrix.copy(props.matrix); 38 | 39 | // prevent the matrix from being over-written on render 40 | // (see https://threejs.org/docs/manual/introduction/Matrix-transformations.html) 41 | THREEObject3D.matrixAutoUpdate = false; 42 | 43 | warning(typeof props.position === 'undefined', "The `position` property "+ 44 | "of 3D objects is ignored when the `matrix` property is specified"); 45 | warning(typeof props.rotation === 'undefined', "The `rotation` property "+ 46 | "of 3D objects is ignored when the `matrix` property is specified"); 47 | warning(typeof props.quaternion === 'undefined', "The `quaternion` property "+ 48 | "of 3D objects is ignored when the `matrix` property is specified"); 49 | warning(typeof props.scale === 'undefined', "The `scale` property "+ 50 | "of 3D objects is ignored when the `matrix` property is specified"); 51 | } else { 52 | if (typeof props.position !== 'undefined') { 53 | THREEObject3D.position.copy(props.position); 54 | } else { 55 | THREEObject3D.position.set(0,0,0); 56 | } 57 | 58 | // rotation be specified using either Euler angles or a quaternion 59 | if (typeof props.rotation !== 'undefined') { 60 | THREEObject3D.rotation.copy(props.rotation); 61 | } else if (typeof props.quaternion !== 'undefined') { 62 | THREEObject3D.quaternion.copy(props.quaternion); 63 | } else { 64 | THREEObject3D.quaternion.set(0,0,0,1); // no rotation 65 | } 66 | 67 | // scale supports a single number or a Vector3 68 | if (typeof props.scale === "number") { 69 | THREEObject3D.scale.set(props.scale, props.scale, props.scale); 70 | } else if (props.scale instanceof THREE.Vector3) { 71 | // copy over scale values 72 | THREEObject3D.scale.copy(props.scale); 73 | } else { 74 | THREEObject3D.scale.set(1,1,1); 75 | } 76 | } 77 | 78 | if (typeof props.visible !== 'undefined') { 79 | THREEObject3D.visible = props.visible; 80 | } else { 81 | THREEObject3D.visible = true; 82 | } 83 | 84 | if (typeof props.up !== 'undefined') { 85 | THREEObject3D.up.copy(props.up); 86 | } 87 | 88 | if (typeof props.lookat !== 'undefined') { 89 | THREEObject3D.lookAt(props.lookat); 90 | } 91 | 92 | 93 | if (typeof props.name !== 'undefined') { 94 | THREEObject3D.name = props.name; 95 | } 96 | 97 | if (typeof props.castShadow !== 'undefined') { 98 | THREEObject3D.castShadow = props.castShadow; 99 | } 100 | 101 | if (typeof props.receiveShadow !== 'undefined') { 102 | THREEObject3D.receiveShadow = props.receiveShadow; 103 | } 104 | 105 | if (typeof props.fog !== 'undefined') { 106 | THREEObject3D.fog = props.fog; 107 | } 108 | }, 109 | 110 | transferTHREEObject3DPropsByName: function(oldProps, newProps, propnames) { 111 | var THREEObject3D = this._THREEObject3D; 112 | 113 | propnames.forEach(function(propname) { 114 | if (typeof newProps[propname] !== 'undefined') { 115 | THREEObject3D[propname] = newProps[propname]; 116 | } 117 | }); 118 | }, 119 | 120 | applySpecificTHREEProps: function(/*oldProps, newProps*/) { 121 | // the default props are applied in applyTHREEObject3DProps. 122 | // to create a new object type, mixin your own version of this method 123 | }, 124 | 125 | mountComponent: function(transaction, nativeParent, nativeContainerInfo, context) { 126 | var props = this._currentElement.props; 127 | this._nativeParent = nativeParent; 128 | this._nativeContainerInfo = nativeContainerInfo; 129 | 130 | /* jshint unused: vars */ 131 | this._THREEObject3D = this.createTHREEObject(arguments); 132 | this._THREEObject3D.userData = this; 133 | this.applyTHREEObject3DProps({}, props); 134 | this.applySpecificTHREEProps({}, props); 135 | 136 | this.mountAndAddChildren(props.children, transaction, context); 137 | return this._THREEObject3D; 138 | }, 139 | 140 | receiveComponent: function(nextElement, transaction, context) { 141 | var oldProps = this._currentElement.props; 142 | var props = nextElement.props; 143 | this.applyTHREEObject3DProps(oldProps, props); 144 | this.applySpecificTHREEProps(oldProps, props); 145 | 146 | this.updateChildren(props.children, transaction, context); 147 | this._currentElement = nextElement; 148 | }, 149 | 150 | unmountComponent: function() { 151 | this.unmountChildren(); 152 | }, 153 | 154 | /*eslint no-unused-vars: [2, { "args": "none" }]*/ 155 | mountComponentIntoNode: function(rootID, container) { 156 | throw new Error( 157 | 'You cannot render an THREE Object3D standalone. ' + 158 | 'You need to wrap it in a THREEScene.' 159 | ); 160 | }, 161 | 162 | getNativeNode: function() { 163 | return this._THREEObject3D; 164 | } 165 | }); 166 | 167 | export default THREEObject3DMixin; 168 | -------------------------------------------------------------------------------- /src/project_template.clj: -------------------------------------------------------------------------------- 1 | (defproject org.clojars.haussman/react-three "<%= version %>" 2 | :description "Library to control threejs using React" 3 | :url "https://github.com/Izzimach/react-three" 4 | :license {:name "Apache 2.0" 5 | :url "http://www.apache.org/licenses/LICENSE-2.0.html"}) 6 | -------------------------------------------------------------------------------- /src/react-three-exposeglobals.js: -------------------------------------------------------------------------------- 1 | // 2 | // require and then expose React and ReactTHREE in the global namespace 3 | // 4 | 5 | // here we use the expose-loader of webpack. Should maybe dump this into the 6 | // config file? 7 | 8 | require('expose?React!react'); 9 | require('expose?ReactDOM!react-dom'); 10 | require('expose?THREE!three'); 11 | 12 | 13 | module.exports = require('./ReactTHREE.js'); 14 | -------------------------------------------------------------------------------- /test/basics/tests.js: -------------------------------------------------------------------------------- 1 | 2 | describe("React and ReactTHREE modules", function() { 3 | 4 | // make sure we're running jasmine 2.0 by using the new 5 | // versions of the async functions 6 | it("are tested using Jasmine 2.0", function(done) { 7 | done(); 8 | }); 9 | 10 | it("exist and are loaded", function() { 11 | expect(React).toBeDefined(); 12 | expect(ReactTHREE).toBeDefined(); 13 | expect(THREE).toBeDefined(); 14 | }); 15 | 16 | it("has all the components you expect", function() { 17 | expect(ReactTHREE.Scene).toBeDefined(); 18 | expect(ReactTHREE.PerspectiveCamera).toBeDefined(); 19 | expect(ReactTHREE.OrthographicCamera).toBeDefined(); 20 | expect(ReactTHREE.Object3D).toBeDefined(); 21 | expect(ReactTHREE.Line).toBeDefined(); 22 | expect(ReactTHREE.PointCloud).toBeDefined(); 23 | expect(ReactTHREE.Mesh).toBeDefined(); 24 | expect(ReactTHREE.SkinnedMesh).toBeDefined(); 25 | expect(ReactTHREE.Sprite).toBeDefined(); 26 | expect(ReactTHREE.AmbientLight).toBeDefined(); 27 | expect(ReactTHREE.PointLight).toBeDefined(); 28 | expect(ReactTHREE.AreaLight).toBeDefined(); 29 | expect(ReactTHREE.DirectionalLight).toBeDefined(); 30 | expect(ReactTHREE.HemisphereLight).toBeDefined(); 31 | expect(ReactTHREE.SpotLight).toBeDefined(); 32 | expect(ReactTHREE.render).toBeDefined(); 33 | }); 34 | }); 35 | -------------------------------------------------------------------------------- /test/components/composite.js: -------------------------------------------------------------------------------- 1 | 2 | describe("ReactTHREE composite components", function() { 3 | var Renderer = React.createFactory(ReactTHREE.Renderer); 4 | var Scene = React.createFactory(ReactTHREE.Scene); 5 | var Object3D = React.createFactory(ReactTHREE.Object3D); 6 | var Mesh = React.createFactory(ReactTHREE.Mesh); 7 | var PerspectiveCamera = React.createFactory(ReactTHREE.PerspectiveCamera); 8 | 9 | var mountpoint = null; 10 | 11 | beforeEach(function() { mountpoint = createTestFixtureMountPoint(); }); 12 | afterEach(function() { removeTestFixtureMountPoint(mountpoint); }); 13 | 14 | 15 | it("correctly replaces THREE objects instead of setting HTML markup when replacing components in-place", function() { 16 | 17 | // 18 | // This occurs when a composite element is updated in-place. To create this (admittedly uncommon) 19 | // situation we create a composite component that changes the key of its child while everything else 20 | // (including the key of the composite element) remains unchanged. In this case _updateChildren 21 | // in ReactMultiChildMixin will update in-place and then updateComponent in 22 | // ReactCompositeComponentMixin will try to nuke and replace the child 23 | // component since the keys don't match. 24 | // 25 | var injectedKeyComponent = React.createClass({ 26 | displayName: 'injectedKeyComponent', 27 | render: function () { 28 | var propswithkey = _.clone(this.props); 29 | propswithkey.key = this.props.injectedkey; 30 | return Object3D(propswithkey); 31 | } 32 | }); 33 | var injectedKeyFactory = React.createFactory(injectedKeyComponent); 34 | 35 | var injectedKeyStage = React.createClass({ 36 | displayName: 'injectedKeyStage', 37 | render: function () { 38 | const injectedprops = {x:100, y:100, key: 'argh', injectedkey:this.props.injectedkey}; 39 | return Renderer({width:this.props.width, height:this.props.height}, 40 | Scene({ref:'scene'}, 41 | injectedKeyFactory(injectedprops))); 42 | } 43 | }); 44 | var injectedKeyStageFactory = React.createFactory(injectedKeyStage); 45 | 46 | // generate two sets of props, identical except that they contain different 47 | // values of injectedkey. 48 | 49 | var baseprops = {width:300, height:300, key:'argh'}; 50 | var addinjectedkey = function(originalprops, injectedkey) { 51 | var newprops = _.clone(originalprops); 52 | newprops.injectedkey = injectedkey; 53 | return newprops; 54 | }; 55 | var props1 = addinjectedkey(baseprops, 'one'); 56 | var props2 = addinjectedkey(baseprops, 'two'); 57 | 58 | // 59 | // render with the original set of props, then again with a new injected key. 60 | // this should keep the same injectedKeyComponent instance but force React to 61 | // replace the Object3D inside of injectedKeyComponent. If we 62 | // don't switch to a different key then React will just update the current instance 63 | // of Object3D instead of trying to replace it. 64 | // 65 | var reactinstance = ReactTHREE.render(injectedKeyStageFactory(props1),mountpoint); 66 | 67 | // this should destroy and replace the child instance instead of updating it 68 | reactinstance = ReactTHREE.render(injectedKeyStageFactory(props2),mountpoint); 69 | 70 | expect(mountpoint.childNodes.length).toBe(1); 71 | expect(mountpoint.childNodes[0].nodeName).toBe('CANVAS'); 72 | 73 | // the tree from here on down is three.js objects, not DOM nodes 74 | expect(mountpoint.childNodes[0].childNodes.length).toBe(0); 75 | 76 | // examine the three.js objects 77 | var scene = reactinstance.refs.scene; 78 | expect(scene.children.length).toBe(1); 79 | }); 80 | 81 | it ("correctly replaces owned components", function() { 82 | // if a composite component switches its child (the root component 83 | // that is returned by the render method) it should remove the old 84 | // child and add the new child. This also requires doing the same 85 | // thing to the parallel tree used by three.js. But since the composite 86 | // itself itsn't part of three.js scene graph this can get tricky. 87 | var changedChildComponent = React.createClass({ 88 | displayName:'changeChildComponent', 89 | render: function () { 90 | var compositechild = this.props.renderstate; 91 | if (this.props.thingindex === 1) { 92 | return Object3D({text:'oldtext',key:1}); 93 | } else { 94 | return PerspectiveCamera({key:2}); 95 | } 96 | } 97 | }); 98 | var changedChildSceneFactory = React.createFactory(React.createClass({ 99 | render: function() { 100 | return Renderer({width:300,height:300}, 101 | Scene({ref:'scene'}, 102 | React.createElement(changedChildComponent, this.props))); 103 | } 104 | })); 105 | 106 | var reactinstance = ReactTHREE.render(changedChildSceneFactory({thingindex:1,text:'newtext'}), mountpoint); 107 | 108 | var scene = reactinstance.refs.scene; 109 | expect(scene.children.length).toBe(1); 110 | 111 | // should switch from Object3D to Camera node... the old node shouldn't be 112 | // stashed somewhere (in _mountImage perhaps?) 113 | reactinstance = ReactTHREE.render(changedChildSceneFactory({thingindex:2,text:'newtext'}), mountpoint); 114 | expect(scene.children.length).toBe(1); 115 | 116 | // If buggy, this will pull the old node (Object3D) and add it in, resulting 117 | // in two children 118 | reactinstance = ReactTHREE.render(changedChildSceneFactory({thingindex:2,text:'ack'}), mountpoint); 119 | expect(scene.children.length).toBe(1); // might be 0 or 2 if buggy 120 | }); 121 | 122 | it("still works on non-THREE nodes", function () { 123 | // we need to fall back on the default DOM behavior for nodes that are 124 | // not THREE elements. So we'll do the same tests as above but with DOM nodes 125 | 126 | var injectedKeyFactory = React.createFactory(React.createClass({ 127 | displayName: 'injectedKeyComponent', 128 | render : function() { 129 | var propswithkey = _.clone(this.props); 130 | propswithkey.key = this.props.injectedkey; 131 | return React.createElement('div', propswithkey); 132 | } 133 | })); 134 | var injectedKeyStageFactory = React.createFactory(React.createClass({ 135 | displayName: 'injectedKeyStage', 136 | render: function () { 137 | return React.createElement('div', {ref:'rootnode'}, 138 | injectedKeyFactory({key:'argh', injectedkey:this.props.injectedkey})); 139 | } 140 | })); 141 | 142 | var baseprops = {key:'argh'}; 143 | var addinjectedkey = function(originalprops, injectedkey) { 144 | var newprops = _.clone(originalprops); 145 | newprops.injectedkey = injectedkey; 146 | return newprops; 147 | }; 148 | var props1 = addinjectedkey(baseprops, 'one'); 149 | var props2 = addinjectedkey(baseprops, 'two'); 150 | 151 | var reactinstance = ReactTHREE.render(injectedKeyStageFactory(props1),mountpoint); 152 | 153 | // this should destroy and replace the child instance instead of updating it 154 | ReactTHREE.render(injectedKeyStageFactory(props2),mountpoint); 155 | 156 | expect(mountpoint.childNodes.length).toBe(1); 157 | expect(mountpoint.childNodes[0].nodeName).toBe('DIV'); 158 | expect(mountpoint.childNodes[0].childNodes.length).toBe(1); 159 | expect(mountpoint.childNodes[0].childNodes[0].nodeName).toBe('DIV'); 160 | }); 161 | }); 162 | -------------------------------------------------------------------------------- /test/components/mesh.js: -------------------------------------------------------------------------------- 1 | // for Mesh we perform some pixel-compare tests 2 | 3 | describe("THREE Mesh Component", function() { 4 | 5 | // need to prepend 'base' to the path since that's how the karma webserver 6 | // routes static file serving 7 | var imagePath = 'base/test/pixels/'; 8 | var pixelReferenceImage = function(index) { 9 | return [imagePath, 'testrender',index,'.png'].join(''); 10 | }; 11 | 12 | var mountpoint = null; 13 | 14 | beforeEach(function() { mountpoint = createTestFixtureMountPoint(); }); 15 | afterEach(function() { removeTestFixtureMountPoint(mountpoint); }); 16 | 17 | 18 | it("puts pixels on the canvas", function(done) { 19 | pixelTests(mountpoint, imagePath, function (results) { 20 | expect(results).toBeDefined(); 21 | expect(results.length).toBeGreaterThan(0); 22 | 23 | var comparesperformed = 0; 24 | for (var compareindex=0; compareindex < results.length; compareindex++) { 25 | var refimageURI = pixelReferenceImage(compareindex); 26 | var testimageURI = results[compareindex]; 27 | 28 | resemble(testimageURI) 29 | .compareTo(refimageURI) 30 | .onComplete(function (data) { 31 | expect(data).toBeDefined(); 32 | expect(typeof data).toEqual('object'); 33 | expect(data.isSameDimensions).toEqual(true); 34 | // we allow for a fairly big mismatch since we may be comparing 35 | // WebGL-rendered images to canvas-rendered images and vice-versa 36 | if (data.misMatchPercentage > 7) { 37 | console.log("mismatch is " + data.misMatchPercentage.toString()); 38 | console.log("reference image URI is " + refimageURI); 39 | console.log("test image URI is " + testimageURI); 40 | console.log("mismatch image data URI is " + data.getImageDataUrl()); 41 | } 42 | expect(data.misMatchPercentage).toBeLessThan(7); 43 | 44 | comparesperformed++; 45 | if (comparesperformed === results.length) { 46 | done(); 47 | } 48 | }); 49 | } 50 | 51 | }); 52 | }); 53 | }); 54 | -------------------------------------------------------------------------------- /test/components/object3d.js: -------------------------------------------------------------------------------- 1 | describe("THREE Object3D Component", function() { 2 | var Object3DContainer = React.createFactory(ReactTHREE.Object3D); 3 | 4 | var mountpoint = null; 5 | 6 | beforeEach(function() { mountpoint = createTestFixtureMountPoint(); }); 7 | afterEach(function() { removeTestFixtureMountPoint(mountpoint); }); 8 | 9 | // 10 | // This component just renders an Object3D which 11 | // has some specific number of objects as children. 12 | // you specify the number of children as props.childCount 13 | // 14 | var VariableChildrenComponent = React.createClass({ 15 | displayName: 'variableChildrenComponent', 16 | render: function () { 17 | var o3dargs = [{key:'argh'}]; 18 | for (var childindex=0; childindex < this.props.childCount; childindex++) { 19 | var somechild = Object3DContainer( 20 | { 21 | key:childindex, 22 | ref:'child' + childindex.toString(), 23 | position: new THREE.Vector3(childindex,0,0) 24 | } 25 | ); 26 | o3dargs.push(somechild); 27 | } 28 | 29 | return Object3DContainer.apply(null, o3dargs); 30 | } 31 | }); 32 | 33 | function VariableChildrenTest(numchildren) { 34 | return createTestFixture({ 35 | width:300, 36 | height:300, 37 | subcomponentfactory: React.createFactory(VariableChildrenComponent), 38 | subcomponentprops:{childCount:numchildren} 39 | }); 40 | }; 41 | 42 | var maxtestchildren = 10; 43 | 44 | it("maintains proper references to the parent Object3D", function() { 45 | var reactinstance = ReactTHREE.render(VariableChildrenTest(1),mountpoint); 46 | 47 | var scene = reactinstance.refs.scene; 48 | var testpoint = scene.children[0]; 49 | 50 | expect(testpoint.parent).toBe(scene); 51 | }); 52 | 53 | it("can hold a variable number of children", function() { 54 | 55 | for (var numchildren = 0; numchildren < maxtestchildren; numchildren++) { 56 | var reactinstance = ReactTHREE.render( 57 | VariableChildrenTest(numchildren), 58 | mountpoint); 59 | 60 | expect(mountpoint.childNodes.length).toBe(1); 61 | expect(mountpoint.childNodes[0].nodeName).toBe('CANVAS'); 62 | 63 | // the tree from here on down is three.js objects, not DOM nodes! 64 | expect(mountpoint.childNodes[0].childNodes.length).toBe(0); 65 | 66 | // examine the three.js objects 67 | var scene = reactinstance.refs.scene; 68 | var testpoint = scene.children[0]; 69 | 70 | expect(scene.children.length).toBe(1); 71 | expect(testpoint.children.length).toBe(numchildren); 72 | 73 | // make sure they're in the right order by examing the x-coordinate of 74 | // each child object 75 | for (var testindex=0; testindex < numchildren; testindex++) { 76 | expect(testpoint.children[testindex].position.x).toBeCloseTo(testindex,2); 77 | } 78 | } 79 | }); 80 | 81 | it ("can add Object3D node to an already-mounted tree", function() { 82 | var reactinstance = ReactTHREE.render( 83 | VariableChildrenTest(0), 84 | mountpoint); 85 | 86 | for (var numchildren = 1; numchildren < maxtestchildren; numchildren++) { 87 | 88 | // this should add another Object3D as a child 89 | reactinstance = ReactTHREE.render( 90 | VariableChildrenTest(numchildren), 91 | mountpoint); 92 | 93 | expect(mountpoint.childNodes.length).toBe(1); 94 | expect(mountpoint.childNodes[0].nodeName).toBe('CANVAS'); 95 | 96 | // the tree from here on down is three.js objects, not DOM nodes 97 | expect(mountpoint.childNodes[0].childNodes.length).toBe(0); 98 | 99 | // examine the three.js objects 100 | var scene = reactinstance.refs.scene; 101 | var testpoint = scene.children[0]; 102 | 103 | expect(scene.children.length).toBe(1); 104 | expect(testpoint.children.length).toBe(numchildren); 105 | 106 | // here we don't unmount, so that the next time through React has to 107 | // add children instead of building them from scratch 108 | } 109 | }); 110 | 111 | it("can remove Object3D nodes from an already-mounted tree", function() { 112 | var reactinstance = ReactTHREE.render( 113 | VariableChildrenTest(maxtestchildren), 114 | mountpoint); 115 | 116 | for (var numchildren = maxtestchildren-1; numchildren > 0; numchildren--) { 117 | 118 | // this should remove an already existing child 119 | var reactinstance = ReactTHREE.render( 120 | VariableChildrenTest(numchildren), 121 | mountpoint); 122 | 123 | expect(mountpoint.childNodes.length).toBe(1); 124 | expect(mountpoint.childNodes[0].nodeName).toBe('CANVAS'); 125 | 126 | // the tree from here on down is pixi objects, no DOM nodes 127 | expect(mountpoint.childNodes[0].childNodes.length).toBe(0); 128 | 129 | // examine the pixi objects 130 | var scene = reactinstance.refs.scene; 131 | var testpoint = scene.children[0]; 132 | 133 | expect(scene.children.length).toBe(1); 134 | expect(testpoint.children.length).toBe(numchildren); 135 | } 136 | }); 137 | 138 | // 139 | // not implemented yet, so the following test is ignored 140 | // 141 | xit("correctly replaces THREE objects instead of setting HTML markup when replacing components in-place", function() { 142 | var Object3DFactory = React.createFactory(ReactTHREE.Object3D); 143 | var SceneFactory = React.createFactory(ReactTHREE.Scene); 144 | 145 | // 146 | // This occurs when a composite element is updated in-place. To create this (admittedly uncommon) 147 | // situation we create a composite component that changes the key of its child while everything else 148 | // (including the key of the composite element) remains unchanged. In this case _updateChildren in ReactMultiChildMixin 149 | // will update in-place and then updateComponent in ReactCompositeComponentMixin will try to nuke and replace the child 150 | // component since the keys don't match. 151 | // 152 | var injectedKeyComponent = React.createClass({ 153 | displayName: 'injectedKeyComponent', 154 | render: function () { 155 | var propswithkey = _.clone(this.props); 156 | propswithkey.key = this.props.injectedkey; 157 | return Object3DFactory(propswithkey); 158 | } 159 | }); 160 | var injectedKeyFactory = React.createFactory(injectedKeyComponent); 161 | 162 | var injectedKeyScene = React.createClass({ 163 | displayName: 'injectedKeyScene', 164 | render: function () { 165 | return SceneFactory({width:this.props.width, height:this.props.height, ref:'stage'}, 166 | injectedKeyFactory({x:100, y:100, key: 'argh', injectedkey:this.props.injectedkey})); 167 | } 168 | }); 169 | var injectedKeySceneFactory = React.createFactory(injectedKeyScene); 170 | 171 | // generate two sets of props, identical except that they contain different 172 | // values of injectedkey. 173 | 174 | var baseprops = {width:300, height:300, key:'argh'}; 175 | var addinjectedkey = function(originalprops, injectedkey) { 176 | var newprops = _.clone(originalprops); 177 | newprops.injectedkey = injectedkey; 178 | return newprops; 179 | }; 180 | var props1 = addinjectedkey(baseprops, 'one'); 181 | var props2 = addinjectedkey(baseprops, 'two'); 182 | 183 | // 184 | // render with the original set of props, then again with a new injected key. 185 | // this should keep the same injectedKeyComponent instance but force React to 186 | // replace the Object3D inside of injectedKeyComponent. Note that the injectedkey 187 | // has to change to make this happens. If we don't switch the injected key 188 | // then React will just update the current instance 189 | // of Object3D instead of replacing it. 190 | // 191 | var reactinstance = ReactTHREE.render(injectedKeyStageFactory(props1),mountpoint); 192 | 193 | // this should destroy and replace the child instance instead of updating it 194 | ReactTHREE.render(injectedKeyStageFactory(props2),mountpont); 195 | 196 | expect(mountpoint.childNodes.length).toBe(1); 197 | expect(mountpoint.childNodes[0].nodeName).toBe('CANVAS'); 198 | 199 | // the tree from here on down is three.js objects, not DOM nodes 200 | expect(mountpoint.childNodes[0].childNodes.length).toBe(0); 201 | 202 | // examine the three.js objects 203 | var scene = reactinstance.refs.scene; 204 | expect(scene.children.length).toBe(1); 205 | }) 206 | }); 207 | -------------------------------------------------------------------------------- /test/components/scene.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | describe("THREE Scene Component", function() { 4 | var scenecomponent = createTestFixture({width:300, height:300}); 5 | var mountpoint = null; 6 | 7 | beforeEach(function() { mountpoint = createTestFixtureMountPoint(); }); 8 | afterEach(function() { removeTestFixtureMountPoint(mountpoint); }); 9 | 10 | it("creates a canvas used by THREE", function() { 11 | ReactTHREE.render(scenecomponent,mountpoint); 12 | 13 | expect(mountpoint.childNodes.length).toBe(1); 14 | expect(mountpoint.childNodes[0].nodeName).toBe('CANVAS'); 15 | expect(mountpoint.childNodes[0].childNodes.length).toBe(0); 16 | }); 17 | 18 | it("creates a THREE Scene object", function() { 19 | var reactinstance = ReactTHREE.render(scenecomponent,mountpoint); 20 | 21 | // hm, probably need some equivalent of getDOMNode 22 | expect(reactinstance.refs.scene).toBeDefined(); 23 | expect(reactinstance.refs.scene).toBeDefined(); 24 | expect(reactinstance.refs.scene instanceof THREE.Scene).toBe(true); 25 | }); 26 | 27 | it("destroys the canvas when the stage is unmounted", function() { 28 | reactinstance = ReactTHREE.render(scenecomponent,mountpoint); 29 | 30 | // this should unmount the stage and remove the canvas 31 | var reactinstance = ReactTHREE.render(React.DOM.div(), mountpoint); 32 | 33 | expect(mountpoint.childNodes.length).toBe(1); 34 | expect(mountpoint.childNodes[0].nodeName).not.toBe('CANVAS'); 35 | expect(mountpoint.childNodes[0].childNodes.length).toBe(0); 36 | 37 | ReactTHREE.unmountComponentAtNode(mountpoint); 38 | 39 | expect(mountpoint.childNodes.length).toBe(0); 40 | }); 41 | 42 | it("passes the context down into threejs elements", function() { 43 | 44 | // this component is an object that uses the x/y/z from the context to position the object 45 | var Object3DFromContext = React.createClass({ 46 | displayName:'Object3D_PositionFromContext', 47 | contextTypes: { 48 | position_context: React.PropTypes.any 49 | }, 50 | render: function() { 51 | console.log(this.context); 52 | return React.createElement( 53 | ReactTHREE.Object3D, 54 | {position: this.context.position_context} 55 | ); 56 | } 57 | }); 58 | 59 | // this component creates a context that contains the desired position 60 | var TestFixtureWithContext = React.createClass({ 61 | displayName:'TestFixtureWithContext', 62 | childContextTypes: { 63 | position_context: React.PropTypes.any 64 | }, 65 | getChildContext: function() { 66 | return { 67 | position_context: new THREE.Vector3( 68 | this.props.mesh_x, 69 | this.props.mesh_y, 70 | this.props.mesh_z 71 | ) 72 | }; 73 | }, 74 | render: function() { 75 | var rendererprops = {width:this.props.width, height:this.props.height}; 76 | var sceneprops = {width:this.props.width, height:this.props.height, ref:'scene'}; 77 | 78 | // note that object3d x/y/z are not passed down in the props, but must 79 | // be obtained from the context 80 | return React.createElement(ReactTHREE.Renderer, 81 | rendererprops, 82 | React.createElement(ReactTHREE.Scene, 83 | sceneprops, 84 | React.createElement(Object3DFromContext))); 85 | } 86 | }); 87 | 88 | var contextcomponent = React.createElement( 89 | TestFixtureWithContext, 90 | { 91 | width: 300, 92 | height: 300, 93 | mesh_x: 51, 94 | mesh_y: 52, 95 | mesh_z: 53 96 | }); 97 | 98 | var reactinstance = ReactTHREE.render(contextcomponent, mountpoint); 99 | 100 | // if the context was passed in the sprite x/y should have been 101 | // determined by the x/y values in the context 102 | var scene = reactinstance.refs.scene; 103 | expect(scene.children[0].position.x).toBe(51); 104 | expect(scene.children[0].position.y).toBe(52); 105 | expect(scene.children[0].position.z).toBe(53); 106 | 107 | }); 108 | 109 | }); 110 | -------------------------------------------------------------------------------- /test/createTestFixtureMountPoint.js: -------------------------------------------------------------------------------- 1 | 2 | // 3 | // ideally we would create a new canvas for each new test and destroy it when 4 | // the test is over in order to keep each test clean. However, some browsers 5 | // get a little grumpy if you create a lot of WebGL contexts. Typically they 6 | // try to free up old contexts as you create new ones but sometimes you'll 7 | // still end up with no context and a blank canvas. 8 | // 9 | 10 | // 11 | // Instead, the tests here all share the same root DOM element and 12 | // WebGL canvas. When browsers are a little better at handling this we 13 | // can always add code later to properly tear down the WebGL context after 14 | // each test 15 | // 16 | 17 | // the test fixture is a single div with the id 'test-fixture' and 18 | // the BasicTestFixture is mounted on it. All tests work by 19 | // adding sub-objects to the BasicTestFixture component. This way we 20 | // avoid creating and destroying WebGL contexts, although you can 21 | // still it do 'by hand' by unmounting the component 22 | var BasicTestFixture = React.createClass({ 23 | displayName: 'BasicTestFixture', 24 | render: function() { 25 | var rendererprops = {width:this.props.width, height:this.props.height}; 26 | var sceneprops = {width:this.props.width, height:this.props.height, ref:'scene'}; 27 | 28 | if (typeof this.props.subcomponentfactory === 'undefined' || 29 | this.props.subcomponentfactory === null) { 30 | return React.createElement(ReactTHREE.Renderer, 31 | rendererprops, 32 | React.createElement(ReactTHREE.Scene, 33 | sceneprops)); 34 | } else { 35 | return React.createElement(ReactTHREE.Renderer, 36 | rendererprops, 37 | React.createElement(ReactTHREE.Scene, 38 | sceneprops, 39 | this.props.subcomponentfactory(this.props.subcomponentprops))); 40 | } 41 | } 42 | }); 43 | 44 | var createTestFixture = React.createFactory(BasicTestFixture); 45 | 46 | function createTestFixtureMountPoint() { 47 | var testDOMelement = window.document.getElementById('test-fixture'); 48 | if (testDOMelement === null) { 49 | testDOMelement = window.document.createElement('div'); 50 | testDOMelement.id = 'test-fixture'; 51 | window.document.body.appendChild(testDOMelement); 52 | } 53 | 54 | return testDOMelement; 55 | } 56 | 57 | function removeTestFixtureMountPoint(mountpoint) { 58 | // someday in the glorious future we'll remove the dom element here 59 | } 60 | -------------------------------------------------------------------------------- /test/pixels/generatetestrender.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | React-three test render generator 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | -------------------------------------------------------------------------------- /test/pixels/generatetestrender.js: -------------------------------------------------------------------------------- 1 | // 2 | // code to generate test render images 3 | // run this in slimerjs from the root directory: 4 | // 'slimerjs test/pixels/generatetestrender.js' 5 | // 6 | 7 | var webPage = require('webpage'); 8 | var fs = require('fs'); 9 | 10 | var page = webPage.create(); 11 | 12 | page.viewportSize = {width:400,height:400}; 13 | 14 | page.onConsoleMessage = function(msg) { 15 | console.log('Web page log message: ' + msg); 16 | }; 17 | 18 | // 19 | // the pixelTests function renders the test images, converts them to binary data, 20 | // and then calls the phantomjs callback with the data 21 | // 22 | 23 | page.onCallback = function(refimages) { 24 | 25 | for (var renderindex=0; renderindex < refimages.length; renderindex++) { 26 | var filename = 'test/pixels/testrender' + renderindex.toString() + '.png'; 27 | 28 | fs.write(filename,refimages[renderindex], 'wb'); 29 | console.log('Wrote test render file ' + filename); 30 | } 31 | slimer.exit(); 32 | } 33 | 34 | page.open('test/pixels/generatetestrender.html', function() { 35 | 36 | // invoke pixelTests to render images and return them via the phantomjs callback 37 | var fixture = page.evaluateAsync(function() { 38 | 39 | // shim out requestAnimationFrame as a no-op 40 | window.requestAnimationFrame = window.requestAnimationFrame || function() {}; 41 | 42 | // this code executes in the webpage context! 43 | var fixture = document.getElementById('test-fixture'); 44 | console.log("Got test fixture"); 45 | var renderresults = pixelTests(fixture, './', function(results) { 46 | console.log('Got reference images'); 47 | 48 | // pixelTests() generates base64 encoded images, so convert them to raw 49 | // binary PNGs before returning them 50 | 51 | var binaryresults = []; 52 | results.forEach(function(refimage) { 53 | // split into two parts; the base64 header and the actual data. we just want the data 54 | var renderURLbits = refimage.split(','); 55 | binaryresults.push(window.atob(renderURLbits[1])); 56 | }); 57 | window.callPhantom(binaryresults); 58 | }); 59 | 60 | return renderresults; 61 | }); 62 | }); 63 | -------------------------------------------------------------------------------- /test/pixels/pixelTests.js: -------------------------------------------------------------------------------- 1 | // 2 | // pixelTests(fixture) will render various 3 | // React-THREE components, capture the rendered canvas pixels, and return 4 | // the pixels as byte data. 5 | // 6 | /* jshint strict: false */ 7 | /* global React : false */ 8 | /* global ReactTHREE : false */ 9 | /* global _ : false */ 10 | 11 | function drawTestRenders(mountpoint, testimage) { 12 | var origin = new THREE.Vector3(0,0,0); 13 | var testgeometry = new THREE.BoxGeometry( 200,200,200); 14 | var testmaterial = new THREE.MeshBasicMaterial( { map: testimage } ); 15 | 16 | var defaultcameraprops = { 17 | name:'maincamera', 18 | fov:'75', 19 | aspect: 1, 20 | near: 10, 21 | far: 700, 22 | position: new THREE.Vector3(0,0,600), 23 | lookat:origin 24 | }; 25 | var imagesize = 400; 26 | 27 | var MeshTestComponent = React.createClass({ 28 | displayName:'SpriteTextComponent', 29 | render: function () { 30 | var cameraprops = _.clone(defaultcameraprops); 31 | cameraprops.aspectratio = this.props.width/this.props.height; 32 | 33 | var rendererprops = { width : this.props.width, height : this.props.height, ref : 'renderer', background: 0xff00ff }; 34 | var sceneprops = _.assign({ camera : 'maincamera', ref : 'scene' }, rendererprops); 35 | 36 | 37 | return React.createElement(ReactTHREE.Renderer, 38 | rendererprops, 39 | React.createElement(ReactTHREE.Scene, 40 | // props 41 | sceneprops, 42 | // children 43 | React.createElement(ReactTHREE.Mesh, this.props.meshprops), 44 | React.createElement(ReactTHREE.PerspectiveCamera, cameraprops))); 45 | } 46 | }); 47 | var MeshTest = React.createFactory(MeshTestComponent); 48 | 49 | // now make multiple renders with slightly different mesh props. For each set of sprite props 50 | // we record a snapshot. These snapshots are compared with the known 'good' versions. 51 | 52 | var meshtestprops = [ 53 | { position: origin, geometry:testgeometry, material:testmaterial}, 54 | { position: new THREE.Vector3(100,0,0), geometry:testgeometry, material:testmaterial}, 55 | { position: new THREE.Vector3(0,100,0), geometry:testgeometry, material:testmaterial}, 56 | { position: new THREE.Vector3(0,0,100), geometry:testgeometry, material:testmaterial}, 57 | { position: origin, quaternion: new THREE.Quaternion().setFromEuler(new THREE.Euler(0,2.5,0)), geometry:testgeometry, material:testmaterial}, 58 | { position: origin, quaternion: new THREE.Quaternion().setFromEuler(new THREE.Euler(0,0,2.5)), geometry:testgeometry, material:testmaterial}, 59 | { position: origin, scale:2, geometry:testgeometry, material:testmaterial}, 60 | ]; 61 | 62 | var renderresults = []; 63 | 64 | meshtestprops.forEach(function (curprops) { 65 | curprops.key = 'urgh'; // re-use the same sprite instance 66 | var reactinstance = ReactTHREE.render(MeshTest({width:imagesize, height:imagesize, meshprops:curprops}), mountpoint); 67 | 68 | // Convert the rendered image to a data blob we can use. We do this by 69 | // getting a data URL from the scene canvas 70 | var renderURL = ReactDOM.findDOMNode(reactinstance.refs['renderer']).toDataURL('image/png'); 71 | 72 | renderresults.push(renderURL); 73 | }); 74 | 75 | ReactTHREE.unmountComponentAtNode(mountpoint); 76 | 77 | return renderresults; 78 | } 79 | 80 | function pixelTests(fixture, testimagepath, resultscallback) { 81 | // preload the image so that we don't get a blank render 82 | console.log("Loading test image..."); 83 | THREE.ImageUtils.loadTexture(testimagepath + 'testimage.png', THREE.UVMapping, function (testtexture) { 84 | var results = drawTestRenders(fixture, testtexture); 85 | resultscallback(results); 86 | }, function() { console.log("error loading test image");}); 87 | 88 | return null; 89 | } 90 | -------------------------------------------------------------------------------- /test/pixels/testimage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Izzimach/react-three-legacy/3ede927e66db1261794402acafc52968f4791d01/test/pixels/testimage.png -------------------------------------------------------------------------------- /test/pixels/testrender0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Izzimach/react-three-legacy/3ede927e66db1261794402acafc52968f4791d01/test/pixels/testrender0.png -------------------------------------------------------------------------------- /test/pixels/testrender1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Izzimach/react-three-legacy/3ede927e66db1261794402acafc52968f4791d01/test/pixels/testrender1.png -------------------------------------------------------------------------------- /test/pixels/testrender2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Izzimach/react-three-legacy/3ede927e66db1261794402acafc52968f4791d01/test/pixels/testrender2.png -------------------------------------------------------------------------------- /test/pixels/testrender3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Izzimach/react-three-legacy/3ede927e66db1261794402acafc52968f4791d01/test/pixels/testrender3.png -------------------------------------------------------------------------------- /test/pixels/testrender4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Izzimach/react-three-legacy/3ede927e66db1261794402acafc52968f4791d01/test/pixels/testrender4.png -------------------------------------------------------------------------------- /test/pixels/testrender5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Izzimach/react-three-legacy/3ede927e66db1261794402acafc52968f4791d01/test/pixels/testrender5.png -------------------------------------------------------------------------------- /test/pixels/testrender6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Izzimach/react-three-legacy/3ede927e66db1261794402acafc52968f4791d01/test/pixels/testrender6.png -------------------------------------------------------------------------------- /vendor/OrbitControls.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author qiao / https://github.com/qiao 3 | * @author mrdoob / http://mrdoob.com 4 | * @author alteredq / http://alteredqualia.com/ 5 | * @author WestLangley / http://github.com/WestLangley 6 | * @author erich666 / http://erichaines.com 7 | */ 8 | /*global THREE, console */ 9 | 10 | // This set of controls performs orbiting, dollying (zooming), and panning. It maintains 11 | // the "up" direction as +Y, unlike the TrackballControls. Touch on tablet and phones is 12 | // supported. 13 | // 14 | // Orbit - left mouse / touch: one finger move 15 | // Zoom - middle mouse, or mousewheel / touch: two finger spread or squish 16 | // Pan - right mouse, or arrow keys / touch: three finter swipe 17 | 18 | THREE.OrbitControls = function ( object, domElement ) { 19 | 20 | this.object = object; 21 | this.domElement = ( domElement !== undefined ) ? domElement : document; 22 | 23 | // API 24 | 25 | // Set to false to disable this control 26 | this.enabled = true; 27 | 28 | // "target" sets the location of focus, where the control orbits around 29 | // and where it pans with respect to. 30 | this.target = new THREE.Vector3(); 31 | 32 | // center is old, deprecated; use "target" instead 33 | this.center = this.target; 34 | 35 | // This option actually enables dollying in and out; left as "zoom" for 36 | // backwards compatibility 37 | this.noZoom = false; 38 | this.zoomSpeed = 1.0; 39 | 40 | // Limits to how far you can dolly in and out ( PerspectiveCamera only ) 41 | this.minDistance = 0; 42 | this.maxDistance = Infinity; 43 | 44 | // Limits to how far you can zoom in and out ( OrthographicCamera only ) 45 | this.minZoom = 0; 46 | this.maxZoom = Infinity; 47 | 48 | // Set to true to disable this control 49 | this.noRotate = false; 50 | this.rotateSpeed = 1.0; 51 | 52 | // Set to true to disable this control 53 | this.noPan = false; 54 | this.keyPanSpeed = 7.0; // pixels moved per arrow key push 55 | 56 | // Set to true to automatically rotate around the target 57 | this.autoRotate = false; 58 | this.autoRotateSpeed = 2.0; // 30 seconds per round when fps is 60 59 | 60 | // How far you can orbit vertically, upper and lower limits. 61 | // Range is 0 to Math.PI radians. 62 | this.minPolarAngle = 0; // radians 63 | this.maxPolarAngle = Math.PI; // radians 64 | 65 | // How far you can orbit horizontally, upper and lower limits. 66 | // If set, must be a sub-interval of the interval [ - Math.PI, Math.PI ]. 67 | this.minAzimuthAngle = - Infinity; // radians 68 | this.maxAzimuthAngle = Infinity; // radians 69 | 70 | // Set to true to disable use of the keys 71 | this.noKeys = false; 72 | 73 | // The four arrow keys 74 | this.keys = { LEFT: 37, UP: 38, RIGHT: 39, BOTTOM: 40 }; 75 | 76 | // Mouse buttons 77 | this.mouseButtons = { ORBIT: THREE.MOUSE.LEFT, ZOOM: THREE.MOUSE.MIDDLE, PAN: THREE.MOUSE.RIGHT }; 78 | 79 | //////////// 80 | // internals 81 | 82 | var scope = this; 83 | 84 | var EPS = 0.000001; 85 | 86 | var rotateStart = new THREE.Vector2(); 87 | var rotateEnd = new THREE.Vector2(); 88 | var rotateDelta = new THREE.Vector2(); 89 | 90 | var panStart = new THREE.Vector2(); 91 | var panEnd = new THREE.Vector2(); 92 | var panDelta = new THREE.Vector2(); 93 | var panOffset = new THREE.Vector3(); 94 | 95 | var offset = new THREE.Vector3(); 96 | 97 | var dollyStart = new THREE.Vector2(); 98 | var dollyEnd = new THREE.Vector2(); 99 | var dollyDelta = new THREE.Vector2(); 100 | 101 | var theta; 102 | var phi; 103 | var phiDelta = 0; 104 | var thetaDelta = 0; 105 | var scale = 1; 106 | var pan = new THREE.Vector3(); 107 | 108 | var lastPosition = new THREE.Vector3(); 109 | var lastQuaternion = new THREE.Quaternion(); 110 | 111 | var STATE = { NONE : -1, ROTATE : 0, DOLLY : 1, PAN : 2, TOUCH_ROTATE : 3, TOUCH_DOLLY : 4, TOUCH_PAN : 5 }; 112 | 113 | var state = STATE.NONE; 114 | 115 | // for reset 116 | 117 | this.target0 = this.target.clone(); 118 | this.position0 = this.object.position.clone(); 119 | this.zoom0 = this.object.zoom; 120 | 121 | // so camera.up is the orbit axis 122 | 123 | var quat = new THREE.Quaternion().setFromUnitVectors( object.up, new THREE.Vector3( 0, 1, 0 ) ); 124 | var quatInverse = quat.clone().inverse(); 125 | 126 | // events 127 | 128 | var changeEvent = { type: 'change' }; 129 | var startEvent = { type: 'start' }; 130 | var endEvent = { type: 'end' }; 131 | 132 | this.rotateLeft = function ( angle ) { 133 | 134 | if ( angle === undefined ) { 135 | 136 | angle = getAutoRotationAngle(); 137 | 138 | } 139 | 140 | thetaDelta -= angle; 141 | 142 | }; 143 | 144 | this.rotateUp = function ( angle ) { 145 | 146 | if ( angle === undefined ) { 147 | 148 | angle = getAutoRotationAngle(); 149 | 150 | } 151 | 152 | phiDelta -= angle; 153 | 154 | }; 155 | 156 | // pass in distance in world space to move left 157 | this.panLeft = function ( distance ) { 158 | 159 | var te = this.object.matrix.elements; 160 | 161 | // get X column of matrix 162 | panOffset.set( te[ 0 ], te[ 1 ], te[ 2 ] ); 163 | panOffset.multiplyScalar( - distance ); 164 | 165 | pan.add( panOffset ); 166 | 167 | }; 168 | 169 | // pass in distance in world space to move up 170 | this.panUp = function ( distance ) { 171 | 172 | var te = this.object.matrix.elements; 173 | 174 | // get Y column of matrix 175 | panOffset.set( te[ 4 ], te[ 5 ], te[ 6 ] ); 176 | panOffset.multiplyScalar( distance ); 177 | 178 | pan.add( panOffset ); 179 | 180 | }; 181 | 182 | // pass in x,y of change desired in pixel space, 183 | // right and down are positive 184 | this.pan = function ( deltaX, deltaY ) { 185 | 186 | var element = scope.domElement === document ? scope.domElement.body : scope.domElement; 187 | 188 | if ( scope.object instanceof THREE.PerspectiveCamera ) { 189 | 190 | // perspective 191 | var position = scope.object.position; 192 | var offset = position.clone().sub( scope.target ); 193 | var targetDistance = offset.length(); 194 | 195 | // half of the fov is center to top of screen 196 | targetDistance *= Math.tan( ( scope.object.fov / 2 ) * Math.PI / 180.0 ); 197 | 198 | // we actually don't use screenWidth, since perspective camera is fixed to screen height 199 | scope.panLeft( 2 * deltaX * targetDistance / element.clientHeight ); 200 | scope.panUp( 2 * deltaY * targetDistance / element.clientHeight ); 201 | 202 | } else if ( scope.object instanceof THREE.OrthographicCamera ) { 203 | 204 | // orthographic 205 | scope.panLeft( deltaX * (scope.object.right - scope.object.left) / element.clientWidth ); 206 | scope.panUp( deltaY * (scope.object.top - scope.object.bottom) / element.clientHeight ); 207 | 208 | } else { 209 | 210 | // camera neither orthographic or perspective 211 | console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - pan disabled.' ); 212 | 213 | } 214 | 215 | }; 216 | 217 | this.dollyIn = function ( dollyScale ) { 218 | 219 | if ( dollyScale === undefined ) { 220 | 221 | dollyScale = getZoomScale(); 222 | 223 | } 224 | 225 | if ( scope.object instanceof THREE.PerspectiveCamera ) { 226 | 227 | scale /= dollyScale; 228 | 229 | } else if ( scope.object instanceof THREE.OrthographicCamera ) { 230 | 231 | scope.object.zoom = Math.max( this.minZoom, Math.min( this.maxZoom, this.object.zoom * dollyScale ) ); 232 | scope.object.updateProjectionMatrix(); 233 | scope.dispatchEvent( changeEvent ); 234 | 235 | } else { 236 | 237 | console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' ); 238 | 239 | } 240 | 241 | }; 242 | 243 | this.dollyOut = function ( dollyScale ) { 244 | 245 | if ( dollyScale === undefined ) { 246 | 247 | dollyScale = getZoomScale(); 248 | 249 | } 250 | 251 | if ( scope.object instanceof THREE.PerspectiveCamera ) { 252 | 253 | scale *= dollyScale; 254 | 255 | } else if ( scope.object instanceof THREE.OrthographicCamera ) { 256 | 257 | scope.object.zoom = Math.max( this.minZoom, Math.min( this.maxZoom, this.object.zoom / dollyScale ) ); 258 | scope.object.updateProjectionMatrix(); 259 | scope.dispatchEvent( changeEvent ); 260 | 261 | } else { 262 | 263 | console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' ); 264 | 265 | } 266 | 267 | }; 268 | 269 | this.update = function () { 270 | 271 | var position = this.object.position; 272 | 273 | offset.copy( position ).sub( this.target ); 274 | 275 | // rotate offset to "y-axis-is-up" space 276 | offset.applyQuaternion( quat ); 277 | 278 | // angle from z-axis around y-axis 279 | 280 | theta = Math.atan2( offset.x, offset.z ); 281 | 282 | // angle from y-axis 283 | 284 | phi = Math.atan2( Math.sqrt( offset.x * offset.x + offset.z * offset.z ), offset.y ); 285 | 286 | if ( this.autoRotate && state === STATE.NONE ) { 287 | 288 | this.rotateLeft( getAutoRotationAngle() ); 289 | 290 | } 291 | 292 | theta += thetaDelta; 293 | phi += phiDelta; 294 | 295 | // restrict theta to be between desired limits 296 | theta = Math.max( this.minAzimuthAngle, Math.min( this.maxAzimuthAngle, theta ) ); 297 | 298 | // restrict phi to be between desired limits 299 | phi = Math.max( this.minPolarAngle, Math.min( this.maxPolarAngle, phi ) ); 300 | 301 | // restrict phi to be betwee EPS and PI-EPS 302 | phi = Math.max( EPS, Math.min( Math.PI - EPS, phi ) ); 303 | 304 | var radius = offset.length() * scale; 305 | 306 | // restrict radius to be between desired limits 307 | radius = Math.max( this.minDistance, Math.min( this.maxDistance, radius ) ); 308 | 309 | // move target to panned location 310 | this.target.add( pan ); 311 | 312 | offset.x = radius * Math.sin( phi ) * Math.sin( theta ); 313 | offset.y = radius * Math.cos( phi ); 314 | offset.z = radius * Math.sin( phi ) * Math.cos( theta ); 315 | 316 | // rotate offset back to "camera-up-vector-is-up" space 317 | offset.applyQuaternion( quatInverse ); 318 | 319 | position.copy( this.target ).add( offset ); 320 | 321 | this.object.lookAt( this.target ); 322 | 323 | thetaDelta = 0; 324 | phiDelta = 0; 325 | scale = 1; 326 | pan.set( 0, 0, 0 ); 327 | 328 | // update condition is: 329 | // min(camera displacement, camera rotation in radians)^2 > EPS 330 | // using small-angle approximation cos(x/2) = 1 - x^2 / 8 331 | 332 | if ( lastPosition.distanceToSquared( this.object.position ) > EPS 333 | || 8 * (1 - lastQuaternion.dot(this.object.quaternion)) > EPS ) { 334 | 335 | this.dispatchEvent( changeEvent ); 336 | 337 | lastPosition.copy( this.object.position ); 338 | lastQuaternion.copy (this.object.quaternion ); 339 | 340 | } 341 | 342 | }; 343 | 344 | 345 | this.reset = function () { 346 | 347 | state = STATE.NONE; 348 | 349 | this.target.copy( this.target0 ); 350 | this.object.position.copy( this.position0 ); 351 | this.object.zoom = this.zoom0; 352 | 353 | this.object.updateProjectionMatrix(); 354 | this.dispatchEvent( changeEvent ); 355 | 356 | this.update(); 357 | 358 | }; 359 | 360 | this.getPolarAngle = function () { 361 | 362 | return phi; 363 | 364 | }; 365 | 366 | this.getAzimuthalAngle = function () { 367 | 368 | return theta 369 | 370 | }; 371 | 372 | function getAutoRotationAngle() { 373 | 374 | return 2 * Math.PI / 60 / 60 * scope.autoRotateSpeed; 375 | 376 | } 377 | 378 | function getZoomScale() { 379 | 380 | return Math.pow( 0.95, scope.zoomSpeed ); 381 | 382 | } 383 | 384 | function onMouseDown( event ) { 385 | 386 | if ( scope.enabled === false ) return; 387 | event.preventDefault(); 388 | 389 | if ( event.button === scope.mouseButtons.ORBIT ) { 390 | if ( scope.noRotate === true ) return; 391 | 392 | state = STATE.ROTATE; 393 | 394 | rotateStart.set( event.clientX, event.clientY ); 395 | 396 | } else if ( event.button === scope.mouseButtons.ZOOM ) { 397 | if ( scope.noZoom === true ) return; 398 | 399 | state = STATE.DOLLY; 400 | 401 | dollyStart.set( event.clientX, event.clientY ); 402 | 403 | } else if ( event.button === scope.mouseButtons.PAN ) { 404 | if ( scope.noPan === true ) return; 405 | 406 | state = STATE.PAN; 407 | 408 | panStart.set( event.clientX, event.clientY ); 409 | 410 | } 411 | 412 | if ( state !== STATE.NONE ) { 413 | document.addEventListener( 'mousemove', onMouseMove, false ); 414 | document.addEventListener( 'mouseup', onMouseUp, false ); 415 | scope.dispatchEvent( startEvent ); 416 | } 417 | 418 | } 419 | 420 | function onMouseMove( event ) { 421 | 422 | if ( scope.enabled === false ) return; 423 | 424 | event.preventDefault(); 425 | 426 | var element = scope.domElement === document ? scope.domElement.body : scope.domElement; 427 | 428 | if ( state === STATE.ROTATE ) { 429 | 430 | if ( scope.noRotate === true ) return; 431 | 432 | rotateEnd.set( event.clientX, event.clientY ); 433 | rotateDelta.subVectors( rotateEnd, rotateStart ); 434 | 435 | // rotating across whole screen goes 360 degrees around 436 | scope.rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientWidth * scope.rotateSpeed ); 437 | 438 | // rotating up and down along whole screen attempts to go 360, but limited to 180 439 | scope.rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight * scope.rotateSpeed ); 440 | 441 | rotateStart.copy( rotateEnd ); 442 | 443 | } else if ( state === STATE.DOLLY ) { 444 | 445 | if ( scope.noZoom === true ) return; 446 | 447 | dollyEnd.set( event.clientX, event.clientY ); 448 | dollyDelta.subVectors( dollyEnd, dollyStart ); 449 | 450 | if ( dollyDelta.y > 0 ) { 451 | 452 | scope.dollyIn(); 453 | 454 | } else if ( dollyDelta.y < 0 ) { 455 | 456 | scope.dollyOut(); 457 | 458 | } 459 | 460 | dollyStart.copy( dollyEnd ); 461 | 462 | } else if ( state === STATE.PAN ) { 463 | 464 | if ( scope.noPan === true ) return; 465 | 466 | panEnd.set( event.clientX, event.clientY ); 467 | panDelta.subVectors( panEnd, panStart ); 468 | 469 | scope.pan( panDelta.x, panDelta.y ); 470 | 471 | panStart.copy( panEnd ); 472 | 473 | } 474 | 475 | if ( state !== STATE.NONE ) scope.update(); 476 | 477 | } 478 | 479 | function onMouseUp( /* event */ ) { 480 | 481 | if ( scope.enabled === false ) return; 482 | 483 | document.removeEventListener( 'mousemove', onMouseMove, false ); 484 | document.removeEventListener( 'mouseup', onMouseUp, false ); 485 | scope.dispatchEvent( endEvent ); 486 | state = STATE.NONE; 487 | 488 | } 489 | 490 | function onMouseWheel( event ) { 491 | 492 | if ( scope.enabled === false || scope.noZoom === true || state !== STATE.NONE ) return; 493 | 494 | event.preventDefault(); 495 | event.stopPropagation(); 496 | 497 | var delta = 0; 498 | 499 | if ( event.wheelDelta !== undefined ) { // WebKit / Opera / Explorer 9 500 | 501 | delta = event.wheelDelta; 502 | 503 | } else if ( event.detail !== undefined ) { // Firefox 504 | 505 | delta = - event.detail; 506 | 507 | } 508 | 509 | if ( delta > 0 ) { 510 | 511 | scope.dollyOut(); 512 | 513 | } else if ( delta < 0 ) { 514 | 515 | scope.dollyIn(); 516 | 517 | } 518 | 519 | scope.update(); 520 | scope.dispatchEvent( startEvent ); 521 | scope.dispatchEvent( endEvent ); 522 | 523 | } 524 | 525 | function onKeyDown( event ) { 526 | 527 | if ( scope.enabled === false || scope.noKeys === true || scope.noPan === true ) return; 528 | 529 | switch ( event.keyCode ) { 530 | 531 | case scope.keys.UP: 532 | scope.pan( 0, scope.keyPanSpeed ); 533 | scope.update(); 534 | break; 535 | 536 | case scope.keys.BOTTOM: 537 | scope.pan( 0, - scope.keyPanSpeed ); 538 | scope.update(); 539 | break; 540 | 541 | case scope.keys.LEFT: 542 | scope.pan( scope.keyPanSpeed, 0 ); 543 | scope.update(); 544 | break; 545 | 546 | case scope.keys.RIGHT: 547 | scope.pan( - scope.keyPanSpeed, 0 ); 548 | scope.update(); 549 | break; 550 | 551 | } 552 | 553 | } 554 | 555 | function touchstart( event ) { 556 | 557 | if ( scope.enabled === false ) return; 558 | 559 | switch ( event.touches.length ) { 560 | 561 | case 1: // one-fingered touch: rotate 562 | 563 | if ( scope.noRotate === true ) return; 564 | 565 | state = STATE.TOUCH_ROTATE; 566 | 567 | rotateStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); 568 | break; 569 | 570 | case 2: // two-fingered touch: dolly 571 | 572 | if ( scope.noZoom === true ) return; 573 | 574 | state = STATE.TOUCH_DOLLY; 575 | 576 | var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX; 577 | var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY; 578 | var distance = Math.sqrt( dx * dx + dy * dy ); 579 | dollyStart.set( 0, distance ); 580 | break; 581 | 582 | case 3: // three-fingered touch: pan 583 | 584 | if ( scope.noPan === true ) return; 585 | 586 | state = STATE.TOUCH_PAN; 587 | 588 | panStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); 589 | break; 590 | 591 | default: 592 | 593 | state = STATE.NONE; 594 | 595 | } 596 | 597 | if ( state !== STATE.NONE ) scope.dispatchEvent( startEvent ); 598 | 599 | } 600 | 601 | function touchmove( event ) { 602 | 603 | if ( scope.enabled === false ) return; 604 | 605 | event.preventDefault(); 606 | event.stopPropagation(); 607 | 608 | var element = scope.domElement === document ? scope.domElement.body : scope.domElement; 609 | 610 | switch ( event.touches.length ) { 611 | 612 | case 1: // one-fingered touch: rotate 613 | 614 | if ( scope.noRotate === true ) return; 615 | if ( state !== STATE.TOUCH_ROTATE ) return; 616 | 617 | rotateEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); 618 | rotateDelta.subVectors( rotateEnd, rotateStart ); 619 | 620 | // rotating across whole screen goes 360 degrees around 621 | scope.rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientWidth * scope.rotateSpeed ); 622 | // rotating up and down along whole screen attempts to go 360, but limited to 180 623 | scope.rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight * scope.rotateSpeed ); 624 | 625 | rotateStart.copy( rotateEnd ); 626 | 627 | scope.update(); 628 | break; 629 | 630 | case 2: // two-fingered touch: dolly 631 | 632 | if ( scope.noZoom === true ) return; 633 | if ( state !== STATE.TOUCH_DOLLY ) return; 634 | 635 | var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX; 636 | var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY; 637 | var distance = Math.sqrt( dx * dx + dy * dy ); 638 | 639 | dollyEnd.set( 0, distance ); 640 | dollyDelta.subVectors( dollyEnd, dollyStart ); 641 | 642 | if ( dollyDelta.y > 0 ) { 643 | 644 | scope.dollyOut(); 645 | 646 | } else if ( dollyDelta.y < 0 ) { 647 | 648 | scope.dollyIn(); 649 | 650 | } 651 | 652 | dollyStart.copy( dollyEnd ); 653 | 654 | scope.update(); 655 | break; 656 | 657 | case 3: // three-fingered touch: pan 658 | 659 | if ( scope.noPan === true ) return; 660 | if ( state !== STATE.TOUCH_PAN ) return; 661 | 662 | panEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); 663 | panDelta.subVectors( panEnd, panStart ); 664 | 665 | scope.pan( panDelta.x, panDelta.y ); 666 | 667 | panStart.copy( panEnd ); 668 | 669 | scope.update(); 670 | break; 671 | 672 | default: 673 | 674 | state = STATE.NONE; 675 | 676 | } 677 | 678 | } 679 | 680 | function touchend( /* event */ ) { 681 | 682 | if ( scope.enabled === false ) return; 683 | 684 | scope.dispatchEvent( endEvent ); 685 | state = STATE.NONE; 686 | 687 | } 688 | 689 | this.domElement.addEventListener( 'contextmenu', function ( event ) { event.preventDefault(); }, false ); 690 | this.domElement.addEventListener( 'mousedown', onMouseDown, false ); 691 | this.domElement.addEventListener( 'mousewheel', onMouseWheel, false ); 692 | this.domElement.addEventListener( 'DOMMouseScroll', onMouseWheel, false ); // firefox 693 | 694 | this.domElement.addEventListener( 'touchstart', touchstart, false ); 695 | this.domElement.addEventListener( 'touchend', touchend, false ); 696 | this.domElement.addEventListener( 'touchmove', touchmove, false ); 697 | 698 | window.addEventListener( 'keydown', onKeyDown, false ); 699 | 700 | // force an update at start 701 | this.update(); 702 | 703 | }; 704 | 705 | THREE.OrbitControls.prototype = Object.create( THREE.EventDispatcher.prototype ); 706 | THREE.OrbitControls.prototype.constructor = THREE.OrbitControls; 707 | -------------------------------------------------------------------------------- /vendor/lodash.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Lo-Dash 2.4.1 (Custom Build) lodash.com/license | Underscore.js 1.5.2 underscorejs.org/LICENSE 4 | * Build: `lodash modern -o ./dist/lodash.js` 5 | */ 6 | ;(function(){function n(n,t,e){e=(e||0)-1;for(var r=n?n.length:0;++ea||typeof i=="undefined")return 1;if(ie?0:e);++r=b&&i===n,l=[];if(f){var p=o(r);p?(i=t,r=p):f=false}for(;++ui(r,p)&&l.push(p);return f&&c(r),l}function ut(n,t,e,r){r=(r||0)-1;for(var u=n?n.length:0,o=[];++r=b&&f===n,h=u||v?a():s; 18 | for(v&&(h=o(h),f=t);++if(h,y))&&((u||v)&&h.push(y),s.push(g))}return v?(l(h.k),c(h)):u&&l(h),s}function lt(n){return function(t,e,r){var u={};e=J.createCallback(e,r,3),r=-1;var o=t?t.length:0;if(typeof o=="number")for(;++re?Ie(0,o+e):e)||0,Te(n)?i=-1o&&(o=a)}}else t=null==t&&kt(n)?r:J.createCallback(t,e,3),St(n,function(n,e,r){e=t(n,e,r),e>u&&(u=e,o=n)});return o}function Dt(n,t,e,r){if(!n)return e;var u=3>arguments.length;t=J.createCallback(t,r,4);var o=-1,i=n.length;if(typeof i=="number")for(u&&(e=n[++o]);++oarguments.length;return t=J.createCallback(t,r,4),Et(n,function(n,r,o){e=u?(u=false,n):t(e,n,r,o)}),e}function Tt(n){var t=-1,e=n?n.length:0,r=Xt(typeof e=="number"?e:0);return St(n,function(n){var e=at(0,++t);r[t]=r[e],r[e]=n}),r}function Ft(n,t,e){var r;t=J.createCallback(t,e,3),e=-1;var u=n?n.length:0;if(typeof u=="number")for(;++er?Ie(0,u+r):r||0}else if(r)return r=zt(t,e),t[r]===e?r:-1;return n(t,e,r)}function qt(n,t,e){if(typeof t!="number"&&null!=t){var r=0,u=-1,o=n?n.length:0;for(t=J.createCallback(t,e,3);++u>>1,e(n[r])e?0:e);++t=v; 29 | m?(i&&(i=ve(i)),s=f,a=n.apply(l,o)):i||(i=_e(r,v))}return m&&c?c=ve(c):c||t===h||(c=_e(u,t)),e&&(m=true,a=n.apply(l,o)),!m||c||i||(o=l=null),a}}function Ut(n){return n}function Gt(n,t,e){var r=true,u=t&&bt(t);t&&(e||u.length)||(null==e&&(e=t),o=Q,t=n,n=J,u=bt(t)),false===e?r=false:wt(e)&&"chain"in e&&(r=e.chain);var o=n,i=dt(o);St(u,function(e){var u=n[e]=t[e];i&&(o.prototype[e]=function(){var t=this.__chain__,e=this.__wrapped__,i=[e];if(be.apply(i,arguments),i=u.apply(n,i),r||t){if(e===i&&wt(i))return this; 30 | i=new o(i),i.__chain__=t}return i})})}function Ht(){}function Jt(n){return function(t){return t[n]}}function Qt(){return this.__wrapped__}e=e?Y.defaults(G.Object(),e,Y.pick(G,A)):G;var Xt=e.Array,Yt=e.Boolean,Zt=e.Date,ne=e.Function,te=e.Math,ee=e.Number,re=e.Object,ue=e.RegExp,oe=e.String,ie=e.TypeError,ae=[],fe=re.prototype,le=e._,ce=fe.toString,pe=ue("^"+oe(ce).replace(/[.*+?^${}()|[\]\\]/g,"\\$&").replace(/toString| for [^\]]+/g,".*?")+"$"),se=te.ceil,ve=e.clearTimeout,he=te.floor,ge=ne.prototype.toString,ye=vt(ye=re.getPrototypeOf)&&ye,me=fe.hasOwnProperty,be=ae.push,_e=e.setTimeout,de=ae.splice,we=ae.unshift,je=function(){try{var n={},t=vt(t=re.defineProperty)&&t,e=t(n,n,n)&&t 31 | }catch(r){}return e}(),ke=vt(ke=re.create)&&ke,xe=vt(xe=Xt.isArray)&&xe,Ce=e.isFinite,Oe=e.isNaN,Ne=vt(Ne=re.keys)&&Ne,Ie=te.max,Se=te.min,Ee=e.parseInt,Re=te.random,Ae={};Ae[$]=Xt,Ae[T]=Yt,Ae[F]=Zt,Ae[B]=ne,Ae[q]=re,Ae[W]=ee,Ae[z]=ue,Ae[P]=oe,Q.prototype=J.prototype;var De=J.support={};De.funcDecomp=!vt(e.a)&&E.test(s),De.funcNames=typeof ne.name=="string",J.templateSettings={escape:/<%-([\s\S]+?)%>/g,evaluate:/<%([\s\S]+?)%>/g,interpolate:N,variable:"",imports:{_:J}},ke||(nt=function(){function n(){}return function(t){if(wt(t)){n.prototype=t; 32 | var r=new n;n.prototype=null}return r||e.Object()}}());var $e=je?function(n,t){M.value=t,je(n,"__bindData__",M)}:Ht,Te=xe||function(n){return n&&typeof n=="object"&&typeof n.length=="number"&&ce.call(n)==$||false},Fe=Ne?function(n){return wt(n)?Ne(n):[]}:H,Be={"&":"&","<":"<",">":">",'"':""","'":"'"},We=_t(Be),qe=ue("("+Fe(We).join("|")+")","g"),ze=ue("["+Fe(Be).join("")+"]","g"),Pe=ye?function(n){if(!n||ce.call(n)!=q)return false;var t=n.valueOf,e=vt(t)&&(e=ye(t))&&ye(e);return e?n==e||ye(n)==e:ht(n) 33 | }:ht,Ke=lt(function(n,t,e){me.call(n,e)?n[e]++:n[e]=1}),Le=lt(function(n,t,e){(me.call(n,e)?n[e]:n[e]=[]).push(t)}),Me=lt(function(n,t,e){n[e]=t}),Ve=Rt,Ue=vt(Ue=Zt.now)&&Ue||function(){return(new Zt).getTime()},Ge=8==Ee(d+"08")?Ee:function(n,t){return Ee(kt(n)?n.replace(I,""):n,t||0)};return J.after=function(n,t){if(!dt(t))throw new ie;return function(){return 1>--n?t.apply(this,arguments):void 0}},J.assign=U,J.at=function(n){for(var t=arguments,e=-1,r=ut(t,true,false,1),t=t[2]&&t[2][t[1]]===n?1:r.length,u=Xt(t);++e=b&&o(r?e[r]:s)))}var p=e[0],h=-1,g=p?p.length:0,y=[];n:for(;++h(m?t(m,v):f(s,v))){for(r=u,(m||s).push(v);--r;)if(m=i[r],0>(m?t(m,v):f(e[r],v)))continue n;y.push(v)}}for(;u--;)(m=i[u])&&c(m);return l(i),l(s),y},J.invert=_t,J.invoke=function(n,t){var e=p(arguments,2),r=-1,u=typeof t=="function",o=n?n.length:0,i=Xt(typeof o=="number"?o:0);return St(n,function(n){i[++r]=(u?t:n[t]).apply(n,e)}),i},J.keys=Fe,J.map=Rt,J.mapValues=function(n,t,e){var r={}; 39 | return t=J.createCallback(t,e,3),h(n,function(n,e,u){r[e]=t(n,e,u)}),r},J.max=At,J.memoize=function(n,t){function e(){var r=e.cache,u=t?t.apply(this,arguments):m+arguments[0];return me.call(r,u)?r[u]:r[u]=n.apply(this,arguments)}if(!dt(n))throw new ie;return e.cache={},e},J.merge=function(n){var t=arguments,e=2;if(!wt(n))return n;if("number"!=typeof t[2]&&(e=t.length),3e?Ie(0,r+e):Se(e,r-1))+1);r--;)if(n[r]===t)return r;return-1},J.mixin=Gt,J.noConflict=function(){return e._=le,this},J.noop=Ht,J.now=Ue,J.parseInt=Ge,J.random=function(n,t,e){var r=null==n,u=null==t;return null==e&&(typeof n=="boolean"&&u?(e=n,n=1):u||typeof t!="boolean"||(e=t,u=true)),r&&u&&(t=1),n=+n||0,u?(t=n,n=0):t=+t||0,e||n%1||t%1?(e=Re(),Se(n+e*(t-n+parseFloat("1e-"+((e+"").length-1))),t)):at(n,t) 50 | },J.reduce=Dt,J.reduceRight=$t,J.result=function(n,t){if(n){var e=n[t];return dt(e)?n[t]():e}},J.runInContext=s,J.size=function(n){var t=n?n.length:0;return typeof t=="number"?t:Fe(n).length},J.some=Ft,J.sortedIndex=zt,J.template=function(n,t,e){var r=J.templateSettings;n=oe(n||""),e=_({},e,r);var u,o=_({},e.imports,r.imports),r=Fe(o),o=xt(o),a=0,f=e.interpolate||S,l="__p+='",f=ue((e.escape||S).source+"|"+f.source+"|"+(f===N?x:S).source+"|"+(e.evaluate||S).source+"|$","g");n.replace(f,function(t,e,r,o,f,c){return r||(r=o),l+=n.slice(a,c).replace(R,i),e&&(l+="'+__e("+e+")+'"),f&&(u=true,l+="';"+f+";\n__p+='"),r&&(l+="'+((__t=("+r+"))==null?'':__t)+'"),a=c+t.length,t 51 | }),l+="';",f=e=e.variable,f||(e="obj",l="with("+e+"){"+l+"}"),l=(u?l.replace(w,""):l).replace(j,"$1").replace(k,"$1;"),l="function("+e+"){"+(f?"":e+"||("+e+"={});")+"var __t,__p='',__e=_.escape"+(u?",__j=Array.prototype.join;function print(){__p+=__j.call(arguments,'')}":";")+l+"return __p}";try{var c=ne(r,"return "+l).apply(v,o)}catch(p){throw p.source=l,p}return t?c(t):(c.source=l,c)},J.unescape=function(n){return null==n?"":oe(n).replace(qe,gt)},J.uniqueId=function(n){var t=++y;return oe(null==n?"":n)+t 52 | },J.all=Ot,J.any=Ft,J.detect=It,J.findWhere=It,J.foldl=Dt,J.foldr=$t,J.include=Ct,J.inject=Dt,Gt(function(){var n={};return h(J,function(t,e){J.prototype[e]||(n[e]=t)}),n}(),false),J.first=Bt,J.last=function(n,t,e){var r=0,u=n?n.length:0;if(typeof t!="number"&&null!=t){var o=u;for(t=J.createCallback(t,e,3);o--&&t(n[o],o,n);)r++}else if(r=t,null==r||e)return n?n[u-1]:v;return p(n,Ie(0,u-r))},J.sample=function(n,t,e){return n&&typeof n.length!="number"&&(n=xt(n)),null==t||e?n?n[at(0,n.length-1)]:v:(n=Tt(n),n.length=Se(Ie(0,t),n.length),n) 53 | },J.take=Bt,J.head=Bt,h(J,function(n,t){var e="sample"!==t;J.prototype[t]||(J.prototype[t]=function(t,r){var u=this.__chain__,o=n(this.__wrapped__,t,r);return u||null!=t&&(!r||e&&typeof t=="function")?new Q(o,u):o})}),J.VERSION="2.4.1",J.prototype.chain=function(){return this.__chain__=true,this},J.prototype.toString=function(){return oe(this.__wrapped__)},J.prototype.value=Qt,J.prototype.valueOf=Qt,St(["join","pop","shift"],function(n){var t=ae[n];J.prototype[n]=function(){var n=this.__chain__,e=t.apply(this.__wrapped__,arguments); 54 | return n?new Q(e,n):e}}),St(["push","reverse","sort","unshift"],function(n){var t=ae[n];J.prototype[n]=function(){return t.apply(this.__wrapped__,arguments),this}}),St(["concat","slice","splice"],function(n){var t=ae[n];J.prototype[n]=function(){return new Q(t.apply(this.__wrapped__,arguments),this.__chain__)}}),J}var v,h=[],g=[],y=0,m=+new Date+"",b=75,_=40,d=" \t\x0B\f\xa0\ufeff\n\r\u2028\u2029\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000",w=/\b__p\+='';/g,j=/\b(__p\+=)''\+/g,k=/(__e\(.*?\)|\b__t\))\+'';/g,x=/\$\{([^\\}]*(?:\\.[^\\}]*)*)\}/g,C=/\w*$/,O=/^\s*function[ \n\r\t]+\w/,N=/<%=([\s\S]+?)%>/g,I=RegExp("^["+d+"]*0+(?=.$)"),S=/($^)/,E=/\bthis\b/,R=/['\n\r\t\u2028\u2029\\]/g,A="Array Boolean Date Function Math Number Object RegExp String _ attachEvent clearTimeout isFinite isNaN parseInt setTimeout".split(" "),D="[object Arguments]",$="[object Array]",T="[object Boolean]",F="[object Date]",B="[object Function]",W="[object Number]",q="[object Object]",z="[object RegExp]",P="[object String]",K={}; 55 | K[B]=false,K[D]=K[$]=K[T]=K[F]=K[W]=K[q]=K[z]=K[P]=true;var L={leading:false,maxWait:0,trailing:false},M={configurable:false,enumerable:false,value:null,writable:false},V={"boolean":false,"function":true,object:true,number:false,string:false,undefined:false},U={"\\":"\\","'":"'","\n":"n","\r":"r","\t":"t","\u2028":"u2028","\u2029":"u2029"},G=V[typeof window]&&window||this,H=V[typeof exports]&&exports&&!exports.nodeType&&exports,J=V[typeof module]&&module&&!module.nodeType&&module,Q=J&&J.exports===H&&H,X=V[typeof global]&&global;!X||X.global!==X&&X.window!==X||(G=X); 56 | var Y=s();typeof define=="function"&&typeof define.amd=="object"&&define.amd?(G._=Y, define(function(){return Y})):H&&J?Q?(J.exports=Y)._=Y:H._=Y:G._=Y}).call(this); -------------------------------------------------------------------------------- /vendor/phantomjs-shims.js: -------------------------------------------------------------------------------- 1 | // For some reason this file didn't have a copyright notice. Attaching it for completeness - Gary 2 | 3 | /** 4 | * Copyright 2013-2014 Facebook, Inc. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | * 18 | */ 19 | 20 | (function() { 21 | 22 | var Ap = Array.prototype; 23 | var slice = Ap.slice; 24 | var Fp = Function.prototype; 25 | 26 | if (!Fp.bind) { 27 | // PhantomJS doesn't support Function.prototype.bind natively, so 28 | // polyfill it whenever this module is required. 29 | Fp.bind = function(context) { 30 | var func = this; 31 | var args = slice.call(arguments, 1); 32 | 33 | function bound() { 34 | var invokedAsConstructor = func.prototype && (this instanceof func); 35 | return func.apply( 36 | // Ignore the context parameter when invoking the bound function 37 | // as a constructor. Note that this includes not only constructor 38 | // invocations using the new keyword but also calls to base class 39 | // constructors such as BaseClass.call(this, ...) or super(...). 40 | !invokedAsConstructor && context || this, 41 | args.concat(slice.call(arguments)) 42 | ); 43 | } 44 | 45 | // The bound function must share the .prototype of the unbound 46 | // function so that any object created by one constructor will count 47 | // as an instance of both constructors. 48 | bound.prototype = func.prototype; 49 | 50 | return bound; 51 | }; 52 | } 53 | 54 | })(); 55 | -------------------------------------------------------------------------------- /webpack-commonjs.config.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var _ = require('lodash'); 3 | 4 | // copy most settings from the default config, and then modify 5 | // the stuff that has changed 6 | 7 | var defaultconfig = require('./webpack.config.js'); 8 | 9 | var commonjsconfig = _.cloneDeep(defaultconfig); 10 | _.assign(commonjsconfig, { 11 | entry: path.join(__dirname, "src", "ReactTHREE.js"), 12 | externals: [ 13 | "three", 14 | "react", 15 | "react-dom", 16 | /^react\/lib\/.+/, // any require that refers to internal react modules 17 | /^react-dom\/lib\/.+/ // any require that refers to internal react modules 18 | ] 19 | }); 20 | _.assign(commonjsconfig.output, { 21 | path: path.join(__dirname, "es5"), 22 | libraryTarget: "commonjs2", 23 | library: "react-three", 24 | filename: "react-three-commonjs.js" 25 | }); 26 | 27 | module.exports = commonjsconfig; 28 | -------------------------------------------------------------------------------- /webpack-examples.config.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var _ = require('lodash'); 3 | 4 | // copy most settings from the default config, and then modify 5 | // the stuff that has changed 6 | 7 | var defaultconfig = require('./webpack.config.js'); 8 | var examplesconfig = _.cloneDeep(defaultconfig); 9 | 10 | var examplesdirectory = path.join(__dirname, "examples"); 11 | 12 | examplesconfig.entry = { 13 | jsxtransform: path.join(examplesdirectory, "jsxtransform", "jsxtransform.jsx"), 14 | shader: path.join(examplesdirectory, 'shader', 'shader.jsx') 15 | }; 16 | 17 | examplesconfig.output = { 18 | path: path.join(__dirname, "examples", "build"), 19 | filename: "[name].js", 20 | publicPath: "/examples/build/" 21 | }; 22 | 23 | // add a jsx processor 24 | examplesconfig.module.loaders.push( 25 | { 26 | test: /\.jsx$/, 27 | loader: 'babel', 28 | include: path.join(__dirname, 'examples'), 29 | query: { 30 | cacheDirectory: true, 31 | presets: ['es2015', 'stage-2', 'react'], 32 | plugins: ['transform-runtime'] 33 | } 34 | } 35 | ); 36 | 37 | examplesconfig.devtool = 'cheap-module-eval-source-map', 38 | 39 | module.exports = examplesconfig; 40 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | 3 | module.exports = { 4 | entry: path.join(__dirname, "src", "react-three-exposeglobals.js"), 5 | 6 | output: { 7 | path: path.join(__dirname, "build"), 8 | publicPath: "/build/", 9 | filename: "react-three.js", 10 | libraryTarget: "var", 11 | library:"ReactTHREE" 12 | }, 13 | 14 | module: { 15 | loaders: [ 16 | { 17 | test: /\.js$/, 18 | loader: 'babel', 19 | include: path.join(__dirname, 'src'), 20 | exclude: /(node_modules|bower_components)/, 21 | query: { 22 | cacheDirectory: true, 23 | presets: ['es2015', 'stage-2'], 24 | plugins: ['transform-runtime'] 25 | } 26 | } 27 | ] 28 | } 29 | } 30 | --------------------------------------------------------------------------------