Repository: toxicFork/react-three-renderer-example
Branch: master
Commit: 0398b735bdc1
Files: 51
Total size: 157.6 KB
Directory structure:
gitextract_5jlez6np/
├── .eslintrc
├── .gitignore
├── .gitmodules
├── CONTRIBUTORS.md
├── README.md
├── assets/
│ ├── advanced.html
│ └── index.html
├── config/
│ ├── webpackCommonsChunkPluginConfig.js
│ └── webpackPluginsWithoutUglify.js
├── gulpfile.babel.js
├── package.json
├── src/
│ ├── examples/
│ │ ├── AdvancedExample/
│ │ │ ├── AdvancedComponent.js
│ │ │ └── index.js
│ │ ├── AnimationCloth/
│ │ │ ├── Cloth.js
│ │ │ ├── ClothGeometry.jsx
│ │ │ ├── Info.js
│ │ │ ├── Poles.js
│ │ │ ├── Sphere.js
│ │ │ ├── StaticWorld.js
│ │ │ ├── index.js
│ │ │ ├── index.jsx
│ │ │ └── shaders/
│ │ │ ├── depth.frag
│ │ │ └── depth.vert
│ │ ├── Benchmark/
│ │ │ ├── RotatingCube.js
│ │ │ ├── RotatingCubes.js
│ │ │ └── RotatingCubesDirectUpdates.js
│ │ ├── DraggableCubes/
│ │ │ ├── AllCubes.js
│ │ │ ├── DraggableCube.js
│ │ │ └── index.js
│ │ ├── ExampleBase.js
│ │ ├── ExampleBrowser.js
│ │ ├── ExampleViewer.js
│ │ ├── Geometries/
│ │ │ └── index.js
│ │ ├── GeometryShapes/
│ │ │ ├── Rect.js
│ │ │ ├── Resources.js
│ │ │ ├── Shape.js
│ │ │ ├── Shapes.js
│ │ │ └── index.js
│ │ ├── ManualRendering/
│ │ │ ├── Info.js
│ │ │ └── index.js
│ │ ├── Physics/
│ │ │ ├── index.js
│ │ │ ├── mousePick/
│ │ │ │ └── PickableMesh.js
│ │ │ └── mousePick.js
│ │ ├── Simple/
│ │ │ └── index.js
│ │ ├── WebGLCameraExample/
│ │ │ ├── Info.js
│ │ │ ├── PointCloud.js
│ │ │ └── index.js
│ │ └── inputs/
│ │ └── MouseInput.js
│ ├── index.jsx
│ └── ref/
│ └── trackball.js
└── webpack.config.babel.js
================================================
FILE CONTENTS
================================================
================================================
FILE: .eslintrc
================================================
// Use this file as a starting point for your project's .eslintrc.
// Copy this file, and add rule overrides as needed.
{
"extends": "airbnb",
"parser": "babel-eslint",
"rules": {
"id-length": 0,
"no-bitwise": "off",
"no-underscore-dangle": 0,
"linebreak-style": "off",
"no-mixed-operators": "off",
"no-plusplus": [
0
],
"no-param-reassign": [
2,
{
"props": false
}
]
}
}
================================================
FILE: .gitignore
================================================
/node_modules
.idea/dictionaries/
.idea/uiDesigner.xml
.idea/
================================================
FILE: .gitmodules
================================================
[submodule "pages"]
path = pages
url = git@github.com:toxicFork/react-three-renderer-example.git
branch = gh-pages
================================================
FILE: CONTRIBUTORS.md
================================================
* [@toxicFork](https://github.com/toxicFork)
* Original Author
* [@vkammerer](https://github.com/vkammerer)
* Added installation instructions
================================================
FILE: README.md
================================================
react-three-renderer-example
============================
Examples for [react-three-renderer](https://github.com/toxicFork/react-three-renderer).
https://toxicfork.github.com/react-three-renderer-example/
#### Installation
``
npm install
``
#### Local server
``
npm start
``
Then visit [http://localhost:8080](http://localhost:8080) on your favorite webgl-enabled browser :)
================================================
FILE: assets/advanced.html
================================================
================================================
FILE: assets/index.html
================================================
================================================
FILE: config/webpackCommonsChunkPluginConfig.js
================================================
/* eslint-disable import/no-extraneous-dependencies */
import webpack from 'webpack';
import path from 'path';
// noinspection WebpackConfigHighlighting
module.exports = new webpack.optimize.CommonsChunkPlugin(
{
name: 'common',
filename: path.join('js', 'bundle-commons.js'),
});
================================================
FILE: config/webpackPluginsWithoutUglify.js
================================================
/* eslint-disable import/no-extraneous-dependencies */
import webpack from 'webpack';
import commonsChunkPluginConfig from './webpackCommonsChunkPluginConfig';
// noinspection WebpackConfigHighlighting
module.exports = [
new webpack.DefinePlugin({
'process.env': {
NODE_ENV: '"production"',
},
}),
commonsChunkPluginConfig,
];
================================================
FILE: gulpfile.babel.js
================================================
/* eslint-disable import/no-extraneous-dependencies */
import gulp from 'gulp';
import webpack from 'webpack';
import WebpackDevServer from 'webpack-dev-server';
import gutil from 'gulp-util';
import path from 'path';
import runSequence from 'run-sequence';
import del from 'del';
import webpackConfig from './webpack.config.babel';
import pluginsWithoutUglify from './config/webpackPluginsWithoutUglify';
import webpackCommonsChunkPluginConfig from './config/webpackCommonsChunkPluginConfig';
const cache = {};
const config = {
prod: false,
addon: false,
noEval: false,
};
webpackConfig.output.devtoolModuleFilenameTemplate = info =>
`wp:///${path.relative(__dirname, info.resourcePath)}`;
webpackConfig.output.devtoolFallbackModuleFilenameTemplate = info =>
`wp:///${path.relative(__dirname, info.resourcePath)}?${info.hash}`;
require('webpack/lib/ModuleFilenameHelpers').createFooter = () => '';
// pretend it's prod ( still has sourcemaps )
// slowest compilation
gulp.task('webpack-dev-server-prod', () => {
config.prod = true;
config.addon = true;
runSequence('webpack-dev-server');
});
// no eval, faster runtime, slower compilation
gulp.task('webpack-dev-server-no-eval', () => {
config.noEval = true;
runSequence('webpack-dev-server');
});
// fast compilation, low runtime performance
gulp.task('webpack-dev-server', () => {
const host = '0.0.0.0';
const port = 8080;
webpackConfig.cache = cache;
webpackConfig.entry.app = [
`webpack-dev-server/client?http://${host}:${port}`, // WebpackDevServer host and port
'webpack/hot/only-dev-server', // "only" prevents reload on syntax errors
].concat(webpackConfig.entry.app);
webpackConfig.entry.advanced = [
`webpack-dev-server/client?http://${host}:${port}`, // WebpackDevServer host and port
'webpack/hot/only-dev-server', // "only" prevents reload on syntax errors
].concat(webpackConfig.entry.advanced);
webpackConfig.module.loaders.forEach((loader) => {
if (loader.loader === 'babel-loader') {
loader.query.plugins.push('react-hot-loader/babel');
}
});
if (config.prod) {
webpackConfig.devtool = 'source-map';
} else if (config.noEval) {
webpackConfig.devtool = 'cheap-module-source-map';
} else {
webpackConfig.devtool = 'eval-cheap-module-source-map';
}
webpackConfig.plugins = [
new webpack.HotModuleReplacementPlugin(),
webpackCommonsChunkPluginConfig,
];
if (config.prod) {
webpackConfig.plugins.unshift(
new webpack.DefinePlugin({
'process.env': {
NODE_ENV: '"production"',
ENABLE_REACT_ADDON_HOOKS: config.addon ? '"true"' : '"false"',
},
}));
webpackConfig.plugins.push(
new webpack.optimize.UglifyJsPlugin({
compress: {
warnings: false,
},
mangle: true,
}));
}
// Start a webpack-dev-server
const compiler = webpack(webpackConfig);
new WebpackDevServer(compiler, webpackConfig.devServer).listen(port, host, (err) => {
if (err) {
throw new gutil.PluginError('webpack-dev-server', err);
}
// Server listening
gutil.log('[webpack-dev-server]', 'http://localhost:8080/webpack-dev-server/index.html');
// keep the server alive or continue?
// callback();
});
});
// only enable addon integration, everything else in prod settings
gulp.task('build-prod-with-addon', (callback) => {
webpackConfig.plugins.unshift(new webpack.DefinePlugin({
'process.env': {
ENABLE_REACT_ADDON_HOOKS: '"true"',
},
}));
runSequence('build', callback);
});
// also adds sourceMaps too!
gulp.task('build-prod-with-addon-no-mangle', (callback) => {
webpackConfig.devtool = 'source-map';
webpackConfig.plugins = pluginsWithoutUglify.concat([
new webpack.optimize.UglifyJsPlugin({
compress: {
warnings: false,
},
mangle: false,
}),
]);
runSequence('build-prod-with-addon', callback);
});
// build without production node env
gulp.task('build-dev', (callback) => {
webpackConfig.plugins = [
new webpack.optimize.UglifyJsPlugin({
compress: {
warnings: false,
},
mangle: true,
}),
];
runSequence('build', callback);
});
gulp.task('clean-pages', () => del('pages/**/!(.git|README.md)'));
gulp.task('copy-assets', () => gulp
.src('assets/**/*')
.pipe(gulp.dest('pages/')));
// just run webpack with default config (prod)
gulp.task('build', ['clean-pages'], (callback) => {
webpack(webpackConfig, (err, stats) => {
if (err) {
throw new gutil.PluginError('webpack', err);
}
gutil.log('[webpack]', stats.toString({
// output options
}));
runSequence('copy-assets', callback);
});
});
gulp.task('default', ['webpack-dev-server']);
================================================
FILE: package.json
================================================
{
"name": "react-three-renderer-example",
"version": "1.0.0",
"description": "An example showing how to use the react-three-renderer package",
"main": "index.js",
"scripts": {
"start": "gulp webpack-dev-server",
"eslint-internal": "eslint ./src/",
"eslint": "npm run eslint-internal -loglevel silent || true",
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [
"reactjs",
"threejs",
"renderer"
],
"author": "Firtina \"toxicFork\" Ozbalikci",
"license": "MIT",
"dependencies": {
"cannon": "^0.6.2",
"history": "^4.6.3",
"prop-types": "^15.5.10",
"react": "^15.6.1",
"react-dom": "^15.6.1",
"react-router": "^4.1.2",
"react-router-dom": "^4.1.2",
"react-sizeme": "^2.3.4",
"react-three-renderer": "^3.2.1",
"stats.js": "^1.0.0",
"three": "^0.86.0",
"react-addons-perf": "^15.4.2"
},
"devDependencies": {
"babel-eslint": "^7.1.1",
"babel-cli": "^6.24.1",
"babel-core": "^6.25.0",
"babel-loader": "^7.1.1",
"babel-plugin-transform-decorators-legacy": "^1.3.4",
"eslint": "^3.11.1",
"eslint-config-airbnb": "^13.0.0",
"eslint-plugin-import": "^2.2.0",
"eslint-plugin-jsx-a11y": "2.2.3",
"eslint-plugin-react": "^6.7.1",
"babel-plugin-transform-runtime": "^6.23.0",
"babel-polyfill": "^6.23.0",
"babel-preset-es2015": "^6.24.1",
"babel-preset-react": "^6.24.1",
"babel-preset-stage-0": "^6.24.1",
"babel-runtime": "^6.25.0",
"gulp": "^3.9.1",
"gulp-babel": "^6.1.2",
"gulp-util": "^3.0.8",
"json-loader": "^0.5.7",
"raw-loader": "^0.5.1",
"react-hot-loader": "^3.0.0-beta.6",
"run-sequence": "^2.1.0",
"webpack": "^3.4.1",
"webpack-dev-server": "^2.6.1",
"del": "latest"
},
"repository": {
"type": "git",
"url": "git+https://github.com/toxicFork/react-three-renderer-example.git"
},
"bugs": {
"url": "https://github.com/toxicFork/react-three-renderer-example/issues"
},
"babel": {
"presets": [
"es2015",
"stage-0",
"react"
],
"plugins": [
"transform-runtime",
"transform-decorators-legacy"
]
},
"homepage": "https://github.com/toxicFork/react-three-renderer-example#readme"
}
================================================
FILE: src/examples/AdvancedExample/AdvancedComponent.js
================================================
================================================
FILE: src/examples/AdvancedExample/index.js
================================================
// see advanced.html :)
import React from 'react';
import React3Renderer from 'react-three-renderer/lib/React3Renderer';
import * as THREE from 'three';
// ^ Look mom, no react-dom!
const canvas = document.getElementById('canvas');
const react3Renderer = new React3Renderer();
const width = 800;
const height = 600;
const cameraPosition = new THREE.Vector3(0, 0, 5);
let cubeRotation = new THREE.Euler();
function onRecreateCanvas() {
// no need to deal with this now, but here we'd need to create a new canvas and
// re-render the scene there.
}
function animate() {
cubeRotation = new THREE.Euler(
cubeRotation.x + 0.1,
cubeRotation.y + 0.1,
0
);
react3Renderer.render(
, canvas);
requestAnimationFrame(animate);
}
animate();
================================================
FILE: src/examples/AnimationCloth/Cloth.js
================================================
/*
* Cloth Simulation using a relaxed constrains solver
*/
// Suggested Readings
// Advanced Character Physics by Thomas Jakobsen Character
// http://freespace.virgin.net/hugo.elias/models/m_cloth.htm
// http://en.wikipedia.org/wiki/Cloth_modeling
// http://cg.alexandra.dk/tag/spring-mass-system/
// Real-time Cloth Animation http://www.darwin3d.com/gamedev/articles/col0599.pdf
import * as THREE from 'three';
function plane(width, height) {
return (u, v) => {
const x = (u - 0.5) * width;
const y = (v + 0.5) * height;
const z = 0;
return new THREE.Vector3(x, y, z);
};
}
const DAMPING = 0.03;
const DRAG = 1 - DAMPING;
const MASS = 0.1;
const restDistance = 25;
const xSegs = 10; //
const ySegs = 10; //
const clothFunction = plane(restDistance * xSegs, restDistance * ySegs);
class Particle {
constructor(x, y, z, mass) {
this.position = clothFunction(x, y); // position
this.previous = clothFunction(x, y); // previous
this.original = clothFunction(x, y);
this.a = new THREE.Vector3(0, 0, 0); // acceleration
this.mass = mass;
this.invMass = 1 / mass;
this.tmp = new THREE.Vector3();
this.tmp2 = new THREE.Vector3();
}
// Force -> Acceleration
addForce(force) {
this.a.add(
this.tmp2.copy(force).multiplyScalar(this.invMass),
);
}
// Performs verlet integration
integrate(timesQ) {
const newPos = this.tmp.subVectors(this.position, this.previous);
newPos.multiplyScalar(DRAG).add(this.position);
newPos.add(this.a.multiplyScalar(timesQ));
this.tmp = this.previous;
this.previous = this.position;
this.position = newPos;
this.a.set(0, 0, 0);
}
}
class Cloth {
static clothFunction = clothFunction;
static MASS = MASS;
constructor(w = 10, h = 10) {
this.w = w;
this.h = h;
const particles = [];
const constrains = [];
let u;
let v;
// Create particles
for (v = 0; v <= h; ++v) {
for (u = 0; u <= w; ++u) {
particles.push(
new Particle(u / w, v / h, 0, MASS),
);
}
}
function index(indexU, indexV) {
return indexU + indexV * (w + 1);
}
// Structural
for (v = 0; v < h; v++) {
for (u = 0; u < w; u++) {
constrains.push([
particles[index(u, v)],
particles[index(u, v + 1)],
restDistance,
]);
constrains.push([
particles[index(u, v)],
particles[index(u + 1, v)],
restDistance,
]);
}
}
for (u = w, v = 0; v < h; v++) {
constrains.push([
particles[index(u, v)],
particles[index(u, v + 1)],
restDistance,
]);
}
for (v = h, u = 0; u < w; u++) {
constrains.push([
particles[index(u, v)],
particles[index(u + 1, v)],
restDistance,
]);
}
this.particles = particles;
this.constrains = constrains;
this.index = index;
}
}
export default Cloth;
================================================
FILE: src/examples/AnimationCloth/ClothGeometry.jsx
================================================
import React from 'react';
import PropTypes from 'prop-types';
import PureRenderMixin from 'react/lib/ReactComponentWithPureRenderMixin';
import Cloth from './Cloth';
class ClothGeometry extends React.Component {
static propTypes = {
cloth: PropTypes.instanceOf(Cloth).isRequired,
};
componentDidMount() {
const geometry = this.geometry;
geometry.computeFaceNormals();
}
_geometryRef = (geometry) => {
this.geometry = geometry;
};
shouldComponentUpdate = PureRenderMixin.shouldComponentUpdate;
render() {
const {
cloth,
} = this.props;
return ( );
}
}
export default ClothGeometry;
================================================
FILE: src/examples/AnimationCloth/Info.js
================================================
import React from 'react';
import PropTypes from 'prop-types';
class Info extends React.Component {
static propTypes = {
toggleWind: PropTypes.func.isRequired,
toggleSphere: PropTypes.func.isRequired,
togglePins: PropTypes.func.isRequired,
toggleRotate: PropTypes.func.isRequired,
onFrameChange: PropTypes.func.isRequired,
minTimePerFrame: PropTypes.number.isRequired,
rotating: PropTypes.bool.isRequired,
winding: PropTypes.bool.isRequired,
balling: PropTypes.bool.isRequired,
};
render() {
const linkStyle = {
textDecoration: 'underline',
cursor: 'pointer',
};
const {
toggleRotate,
toggleWind,
toggleSphere,
togglePins,
minTimePerFrame,
onFrameChange,
rotating,
winding,
balling,
} = this.props;
return ();
}
}
export default Info;
================================================
FILE: src/examples/AnimationCloth/Poles.js
================================================
import React from 'react';
import * as THREE from 'three';
import PureRenderMixin from 'react/lib/ReactComponentWithPureRenderMixin';
class Poles extends React.Component {
constructor(props, context) {
super(props, context);
this.state = {
poleMaterialColor: Number(0xffffff).toString(16),
poleMaterialSpecular: Number(0x111111).toString(16),
poleMaterialShininess: 100,
sidePolePositions: [
new THREE.Vector3(-125, -62, 0),
new THREE.Vector3(125, -62, 0),
],
boxPositions: [
new THREE.Vector3(125, -250, 0),
new THREE.Vector3(-125, -250, 0),
],
topPolePosition: new THREE.Vector3(0, -250 + 750 / 2, 0),
subResource: false,
};
}
shouldComponentUpdate = PureRenderMixin.shouldComponentUpdate;
render() {
return (
{this.state.sidePolePositions.map((position, i) =>
(
))}
{ this.state.subResource ?
{this.state.subResource ? : null}
{
}
: null }
{this.state.boxPositions.map((position, i) =>
(
))}
);
}
}
export default Poles;
================================================
FILE: src/examples/AnimationCloth/Sphere.js
================================================
import React from 'react';
import * as THREE from 'three';
import PureRenderMixin from 'react/lib/ReactComponentWithPureRenderMixin';
import PropTypes from 'prop-types';
const ballSize = 60; // 40
class Sphere extends React.Component {
static propTypes = {
visible: PropTypes.bool.isRequired,
position: PropTypes.instanceOf(THREE.Vector3).isRequired,
};
constructor(props, context) {
super(props, context);
this.state = {
color: '0xaaaaaa',
};
}
shouldComponentUpdate = PureRenderMixin.shouldComponentUpdate;
render() {
const {
visible,
position,
} = this.props;
return (
);
}
}
export default Sphere;
================================================
FILE: src/examples/AnimationCloth/StaticWorld.js
================================================
import React from 'react';
import * as THREE from 'three';
import PropTypes from 'prop-types';
import PureRenderMixin from 'react/lib/ReactComponentWithPureRenderMixin';
import fragmentShaderDepth from 'raw-loader!./shaders/depth.frag';
import vertexShaderDepth from 'raw-loader!./shaders/depth.vert';
import ClothGeometry from './ClothGeometry';
import Poles from './Poles';
import Cloth from './Cloth';
class StaticWorld extends React.Component {
static propTypes = {
clothRef: PropTypes.func.isRequired,
cloth: PropTypes.instanceOf(Cloth).isRequired,
};
constructor(props, context) {
super(props, context);
this.directionalLightPosition = new THREE.Vector3(50, 200, 100).multiplyScalar(1.3);
this.lightTarget = new THREE.Vector3(0, 0, 0);
this.groundPosition = new THREE.Vector3(0, -250, 0);
this.groundRotation = new THREE.Euler(-Math.PI / 2, 0, 0);
this.groundRepeat = new THREE.Vector2(25, 25);
this.state = {
ambientLightColor: '666666',
directionalLightColor: 'dfebff',
fragmentShaderDepth,
vertexShaderDepth,
};
// check if HMR is enabled
if (module.hot) {
// accept update of dependency
module.hot.accept('raw-loader!./shaders/depth.frag', () => {
this.setState({
fragmentShaderDepth: require('raw-loader!./shaders/depth.frag'),
});
});
module.hot.accept('raw-loader!./shaders/depth.vert', () => {
this.setState({
vertexShaderDepth: require('raw-loader!./shaders/depth.vert'),
});
});
}
}
shouldComponentUpdate = PureRenderMixin.shouldComponentUpdate;
render() {
const shadowCameraSize = 300;
const {
ambientLightColor,
directionalLightColor,
fragmentShaderDepth: frag,
vertexShaderDepth: vert,
} = this.state;
return (
{ /* */ }
);
}
}
export default StaticWorld;
================================================
FILE: src/examples/AnimationCloth/index.js
================================================
import jsx from './index.jsx';
module.exports = jsx;
================================================
FILE: src/examples/AnimationCloth/index.jsx
================================================
import React from 'react';
import * as THREE from 'three';
import Stats from 'stats.js';
import React3 from 'react-three-renderer';
import ExampleBase from '../ExampleBase';
import Info from './Info';
import Cloth from './Cloth';
import StaticWorld from './StaticWorld';
import Sphere from './Sphere';
import TrackballControls from '../../ref/trackball';
const ballSize = 60; // 40
const GRAVITY = 981 * 1.4; //
const gravity = new THREE.Vector3(0, -GRAVITY, 0).multiplyScalar(Cloth.MASS);
const TIMESTEP = 18 / 1000;
const TIMESTEP_SQ = TIMESTEP * TIMESTEP;
const diff = new THREE.Vector3();
function satisfyConstrains(p1, p2, distance) {
diff.subVectors(p2.position, p1.position);
const currentDist = diff.length();
if (currentDist === 0) {
return;
} // prevents division by 0
const correction = diff.multiplyScalar(1 - distance / currentDist);
const correctionHalf = correction.multiplyScalar(0.5);
p1.position.add(correctionHalf);
p2.position.sub(correctionHalf);
}
const tmpForce = new THREE.Vector3();
class AnimationCloth extends ExampleBase {
constructor(props, context) {
super(props, context);
this.state = {
...this.state,
minTimePerFrame: 0,
rotate: true,
wind: true,
sphere: false,
};
const xSegs = 10; //
const ySegs = 10; //
this.cloth = new Cloth(xSegs, ySegs);
const pinsFormation = [];
let pins = [6];
pinsFormation.push(pins);
pins = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
pinsFormation.push(pins);
pins = [0];
pinsFormation.push(pins);
pins = []; // cut the rope ;)
pinsFormation.push(pins);
pins = [0, this.cloth.w]; // classic 2 pins
pinsFormation.push(pins);
pins = pinsFormation[1];
this.pins = pins;
this.pinsFormation = pinsFormation;
this.fog = new THREE.Fog(0xcce0ff, 500, 10000);
this.windForce = new THREE.Vector3(0, 0, 0);
this.state = {
...this.state,
ballPosition: new THREE.Vector3(0, -45, 0),
cameraPosition: new THREE.Vector3(0, 50, 1500),
};
this.scenePosition = new THREE.Vector3(0, 0, 0);
}
componentDidMount() {
const controls = new TrackballControls(
this.mainCamera, this.react3);
controls.rotateSpeed = 1.0;
controls.zoomSpeed = 1.2;
controls.panSpeed = 0.8;
controls.noZoom = false;
controls.noPan = false;
controls.staticMoving = true;
controls.dynamicDampingFactor = 0.3;
controls.addEventListener('change', () => {
this.setState({
cameraPosition: this.mainCamera.position,
});
});
this.controls = controls;
this.stats = new Stats();
this.stats.domElement.style.position = 'absolute';
this.stats.domElement.style.top = '0px';
this.container.appendChild(this.stats.domElement);
}
componentWillUnmount() {
delete this.stats;
this.controls.dispose();
delete this.controls;
}
_toggleRotate = () => {
this.setState({ rotate: !this.state.rotate });
};
_toggleWind = () => {
this.setState({ wind: !this.state.wind });
};
_toggleSphere = () => {
this.setState({ sphere: !this.state.sphere });
};
_togglePins = () => {
this.pins = this.pinsFormation[~~(Math.random() * this.pinsFormation.length)];
};
_simulate(time) {
if (!this.lastTime) {
this.lastTime = time;
return;
}
let i;
let il;
let particles;
let particle;
let constrain;
const clothGeometry = React3.findTHREEObject(this._clothGeometry);
const sphere = React3.findTHREEObject(this.sphere);
// Aerodynamics forces
if (this.state.wind) {
let face;
const faces = clothGeometry.faces;
let normal;
particles = this.cloth.particles;
for (i = 0, il = faces.length; i < il; i++) {
face = faces[i];
normal = face.normal;
tmpForce.copy(normal).normalize().multiplyScalar(normal.dot(this.windForce));
particles[face.a].addForce(tmpForce);
particles[face.b].addForce(tmpForce);
particles[face.c].addForce(tmpForce);
}
}
for (particles = this.cloth.particles, i = 0, il = particles.length; i < il; i++) {
particle = particles[i];
particle.addForce(gravity);
particle.integrate(TIMESTEP_SQ);
}
// Start Constrains
const constrains = this.cloth.constrains;
il = constrains.length;
for (i = 0; i < il; i++) {
constrain = constrains[i];
satisfyConstrains(constrain[0], constrain[1], constrain[2]);
}
const ballPosition = this.state.ballPosition.clone();
// Ball Constrains
ballPosition.z = -Math.sin(Date.now() / 600) * 90; // + 40;
ballPosition.x = Math.cos(Date.now() / 400) * 70;
if (sphere.visible) {
for (particles = this.cloth.particles,
i = 0,
il = particles.length; i < il; i++) {
particle = particles[i];
const pos = particle.position;
diff.subVectors(pos, ballPosition);
if (diff.length() < ballSize) {
// collided
diff.normalize().multiplyScalar(ballSize);
pos.copy(ballPosition).add(diff);
}
}
}
// Floor Constraints
for (particles = this.cloth.particles, i = 0, il = particles.length
; i < il; i++) {
particle = particles[i];
const pos = particle.position;
if (pos.y < -250) {
pos.y = -250;
}
}
// Pin Constrains
for (i = 0, il = this.pins.length; i < il; i++) {
const xy = this.pins[i];
const p = particles[xy];
p.position.copy(p.original);
p.previous.copy(p.original);
}
this.setState({
ballPosition,
});
}
_onAnimate = () => {
this.controls.update();
const {
minTimePerFrame,
} = this.state;
let time;
if (minTimePerFrame > 0) {
time = Math.round(Date.now() / minTimePerFrame) * minTimePerFrame;
} else {
time = Date.now();
}
if (time === this.state.time) {
return;
}
const windStrength = Math.cos(time / 7000) * 20 + 40;
this.windForce.set(
Math.sin(time / 2000),
Math.cos(time / 3000),
Math.sin(time / 1000)).normalize().multiplyScalar(windStrength);
this._simulate(time);
const clothGeometry = React3.findTHREEObject(this._clothGeometry);
// render
const timer = time * 0.0002;
const p = this.cloth.particles;
let il;
let i;
for (i = 0, il = p.length; i < il; ++i) {
clothGeometry.vertices[i].copy(p[i].position);
}
clothGeometry.computeFaceNormals();
clothGeometry.computeVertexNormals();
clothGeometry.normalsNeedUpdate = true;
clothGeometry.verticesNeedUpdate = true;
const newState = {
time,
spherePosition: this.ballPosition,
};
if (this.state.rotate) {
newState.cameraPosition = new THREE.Vector3(
Math.cos(timer) * 1500,
this.state.cameraPosition.y,
Math.sin(timer) * 1500);
}
this.setState(newState);
this.stats.update();
};
_clothRef = (ref) => {
this._clothGeometry = ref;
};
_onFrameChange = (event) => {
this.setState({
minTimePerFrame: +event.target.value,
});
};
_containerRef = (container) => {
this.container = container;
};
_sphereRef = (sphere) => {
this.sphere = sphere;
};
_react3Ref = (react3) => {
this.react3 = react3;
};
_mainCameraRef = (mainCamera) => {
this.mainCamera = mainCamera;
};
render() {
const {
width,
height,
} = this.props;
const {
minTimePerFrame,
} = this.state;
return ();
}
}
export default AnimationCloth;
================================================
FILE: src/examples/AnimationCloth/shaders/depth.frag
================================================
uniform sampler2D texture;
varying vec2 vUV;
vec4 pack_depth( const in float depth ) {
const vec4 bit_shift = vec4( 256.0 * 256.0 * 256.0, 256.0 * 256.0, 256.0, 1.0 );
const vec4 bit_mask = vec4( 0.0, 1.0 / 256.0, 1.0 / 256.0, 1.0 / 256.0 );
vec4 res = fract( depth * bit_shift );
res -= res.xxyz * bit_mask;
return res;
}
void main() {
vec4 pixel = texture2D( texture, vUV );
if ( pixel.a < 0.5 ) discard;
gl_FragData[ 0 ] = pack_depth( gl_FragCoord.z );
}
================================================
FILE: src/examples/AnimationCloth/shaders/depth.vert
================================================
varying vec2 vUV;
void main() {
vUV = 0.75 * uv;
vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );
gl_Position = projectionMatrix * mvPosition;
}
================================================
FILE: src/examples/Benchmark/RotatingCube.js
================================================
import React from 'react';
import * as THREE from 'three';
import PropTypes from 'prop-types';
const meshScale = new THREE.Vector3(1, 1, 1).multiplyScalar(0.5);
class RotatingCube extends React.Component {
static propTypes = {
position: PropTypes.instanceOf(THREE.Vector3).isRequired,
quaternion: PropTypes.instanceOf(THREE.Quaternion).isRequired,
};
render() {
const {
position,
quaternion,
} = this.props;
return (
);
}
}
export default RotatingCube;
================================================
FILE: src/examples/Benchmark/RotatingCubes.js
================================================
import React from 'react';
import React3 from 'react-three-renderer';
import * as THREE from 'three';
import ExampleBase from '../ExampleBase';
import Stats from 'stats.js';
import RotatingCube from './RotatingCube';
class RotatingCubes extends ExampleBase {
constructor(props, context) {
super(props, context);
const N = 200;
this.fog = new THREE.Fog(0x001525, 10, 40);
const d = 20;
this.lightPosition = new THREE.Vector3(d, d, d);
this.lightTarget = new THREE.Vector3(0, 0, 0);
this.groundQuaternion = new THREE.Quaternion()
.setFromAxisAngle(new THREE.Vector3(1, 0, 0), -Math.PI / 2);
this.cameraPosition = new THREE.Vector3(10, 2, 0);
this.cameraQuaternion = new THREE.Quaternion()
.setFromAxisAngle(new THREE.Vector3(0, 1, 0), Math.PI / 2);
const bodies = [];
bodies.length = N;
this.bodies = bodies;
this._createBodies();
this.state = {
numBodies: N,
meshStates: this._getMeshStates(),
};
}
_getMeshStates() {
return this.bodies.map(({ position, quaternion }) => ({
position: new THREE.Vector3().copy(position),
quaternion: new THREE.Quaternion().copy(quaternion),
}));
}
_onAnimate = () => {
this._updatePhysics();
this._updateGraphics();
this.stats.update();
};
_updateGraphics() {
this.setState({
meshStates: this._getMeshStates(),
});
}
_updatePhysics() {
const time = new Date().getTime();
const bodies = this.bodies;
for (let i = 0; i < bodies.length; ++i) {
const body = bodies[i];
const sinTime = Math.sin(time * body.timeScale);
body.quaternion.multiply(body.rotationDeltaPerFrame);
const { movementPerFrame } = body;
body.position.copy(body.startPosition.clone()
.add(movementPerFrame.clone()
.multiplyScalar(sinTime)));
}
}
componentDidMount() {
const {
container,
} = this.refs;
this.stats = new Stats();
this.stats.domElement.style.position = 'absolute';
this.stats.domElement.style.top = '0px';
container.appendChild(this.stats.domElement);
}
componentWillUnmount() {
delete this.stats;
}
_createBodies() {
const { bodies } = this;
const N = bodies.length;
for (let i = 0; i < N; ++i) {
bodies[i] = this._createBody(i);
}
}
_createBody() {
const position = new THREE.Vector3(
-2.5 + Math.random() * 5,
0.5 + Math.random() * 5,
-2.5 + Math.random() * 5);
return {
position,
timeScale: Math.random() * 0.005,
startPosition: position.clone(),
movementPerFrame: new THREE.Vector3(Math.random(), Math.random(), Math.random()),
rotationDeltaPerFrame: new THREE.Quaternion()
.setFromEuler(new THREE.Euler(
Math.random() * 0.05,
Math.random() * 0.05,
Math.random() * 0.05)),
quaternion: new THREE.Quaternion(),
};
}
_onBodiesSelectChange = (event) => {
const numBodies = event.target.value;
this.bodies.length = numBodies;
this._createBodies();
this.setState({
numBodies,
meshStates: this._getMeshStates(),
});
this._updateGraphics();
};
_getInputBox(title) {
const { numBodies } = this.state;
return (
{title}
Bodies:
{[10, 50, 100, 200, 300, 500, 1000, 1500, 2000, 2500, 3000]
.map(val => {val} )}
);
}
render() {
const {
width,
height,
} = this.props;
const {
meshStates,
} = this.state;
const d = 20;
const cubeMeshes = meshStates.map(({ position, quaternion }, i) =>
( ));
return (
{this._getInputBox('Rotating Cubes - Through React')}
{cubeMeshes}
);
}
}
export default RotatingCubes;
================================================
FILE: src/examples/Benchmark/RotatingCubesDirectUpdates.js
================================================
import React from 'react';
import React3 from 'react-three-renderer';
import * as THREE from 'three';
import RotatingCube from './RotatingCube';
import RotatingCubes from './RotatingCubes';
class RotatingCubesDirectUpdates extends RotatingCubes {
_getMeshStates() {
const { bodies } = this;
return bodies.map(({ position, quaternion, ref }) => ({
position: new THREE.Vector3().copy(position),
quaternion: new THREE.Quaternion().copy(quaternion),
ref,
}));
}
_bodyRef(index, body) {
if (body === null) {
// dismounted
return;
}
this.bodies[index].body = React3.findTHREEObject(body);
}
_updateGraphics() {
const { bodies } = this;
for (let i = 0; i < bodies.length; ++i) {
const body = bodies[i];
if (body.body) {
body.body.position.copy(body.position);
body.body.quaternion.copy(body.quaternion);
}
}
}
_createBody(i) {
return {
...super._createBody(),
ref: this._bodyRef.bind(this, i),
};
}
render() {
const {
width,
height,
} = this.props;
const {
meshStates,
} = this.state;
const d = 20;
const cubeMeshes = meshStates.map(({ position, quaternion, ref }, i) => ( ));
return (
{this._getInputBox('Rotating Cubes - Direct Updates')}
{cubeMeshes}
);
}
}
export default RotatingCubesDirectUpdates;
================================================
FILE: src/examples/DraggableCubes/AllCubes.js
================================================
import React from 'react';
import DraggableCube from './DraggableCube';
import * as THREE from 'three';
import PureRenderMixin from 'react/lib/ReactComponentWithPureRenderMixin';
import PropTypes from 'prop-types';
import MouseInput from '../inputs/MouseInput';
class AllCubes extends React.Component {
static propTypes = {
mouseInput: PropTypes.instanceOf(MouseInput),
camera: PropTypes.instanceOf(THREE.PerspectiveCamera),
onCubesMounted: PropTypes.func.isRequired,
onHoverStart: PropTypes.func.isRequired,
onHoverEnd: PropTypes.func.isRequired,
onDragStart: PropTypes.func.isRequired,
onDragEnd: PropTypes.func.isRequired,
cursor: PropTypes.any,
};
constructor(props, context) {
super(props, context);
const cubePositions = [];
cubePositions.length = 200;
for (let i = 0; i < 200; ++i) {
cubePositions[i] = new THREE.Vector3(
Math.random() * 1000 - 500,
Math.random() * 600 - 300,
Math.random() * 800 - 400
);
}
const cubes = [];
cubes.length = cubePositions.length;
this.cubes = cubes;
this.cubePositions = cubePositions;
this._hoveredCubes = 0;
this._draggingCubes = 0;
}
componentDidMount() {
const {
onCubesMounted,
} = this.props;
onCubesMounted(this.cubes);
}
shouldComponentUpdate = PureRenderMixin.shouldComponentUpdate;
_onCubeCreate = (index, cube) => {
this.cubes[index] = cube;
};
_onCubeMouseEnter = () => {
if (this._hoveredCubes === 0) {
const {
onHoverStart,
} = this.props;
onHoverStart();
}
this._hoveredCubes++;
};
_onCubeMouseLeave = () => {
this._hoveredCubes--;
if (this._hoveredCubes === 0) {
const {
onHoverEnd,
} = this.props;
onHoverEnd();
}
};
_onCubeDragStart = () => {
if (this._draggingCubes === 0) {
const {
onDragStart,
} = this.props;
onDragStart();
}
this._draggingCubes++;
};
_onCubeDragEnd = () => {
this._draggingCubes--;
if (this._draggingCubes === 0) {
const {
onDragEnd,
} = this.props;
onDragEnd();
}
};
render() {
const {
mouseInput,
camera,
cursor,
} = this.props;
return (
{this.cubePositions.map((cubePosition, index) => {
const onCreate = this._onCubeCreate.bind(this, index);
return ( );
})}
);
}
}
export default AllCubes;
================================================
FILE: src/examples/DraggableCubes/DraggableCube.js
================================================
import React from 'react';
import PropTypes from 'prop-types';
import * as THREE from 'three';
import PureRenderMixin from 'react/lib/ReactComponentWithPureRenderMixin';
import MouseInput from '../inputs/MouseInput';
// shared plane for dragging purposes
// it's good to share because you can drag only one cube at a time
const dragPlane = new THREE.Plane();
const backVector = new THREE.Vector3(0, 0, -1);
class DraggableCube extends React.Component {
static propTypes = {
initialPosition: PropTypes.instanceOf(THREE.Vector3).isRequired,
mouseInput: PropTypes.instanceOf(MouseInput),
camera: PropTypes.instanceOf(THREE.PerspectiveCamera),
onCreate: PropTypes.func.isRequired,
onMouseEnter: PropTypes.func.isRequired,
onMouseLeave: PropTypes.func.isRequired,
onDragStart: PropTypes.func.isRequired,
onDragEnd: PropTypes.func.isRequired,
cursor: PropTypes.any,
};
constructor(props, context) {
super(props, context);
this.rotation = new THREE.Euler(
Math.random() * 2 * Math.PI,
Math.random() * 2 * Math.PI,
Math.random() * 2 * Math.PI
);
this.scale = new THREE.Vector3(
Math.random() * 2 + 1,
Math.random() * 2 + 1,
Math.random() * 2 + 1
);
this.color = new THREE.Color(Math.random() * 0xffffff);
const hsl = this.color.getHSL();
hsl.s = Math.min(1, hsl.s * 1.1);
hsl.l = Math.min(1, hsl.l * 1.1);
const { h, s, l } = hsl;
this.hoverColor = new THREE.Color().setHSL(h, s, l);
this.pressedColor = 0xff0000;
const {
initialPosition,
} = props;
this.state = {
hovered: false,
pressed: false,
position: initialPosition,
};
}
shouldComponentUpdate = PureRenderMixin.shouldComponentUpdate;
componentWillUnmount() {
document.removeEventListener('mouseup', this._onDocumentMouseUp);
}
_onMouseEnter = () => {
this.setState({
hovered: true,
});
const { onMouseEnter } = this.props;
onMouseEnter();
};
_onMouseDown = (event, intersection) => {
event.preventDefault();
event.stopPropagation();
const {
position,
} = this.state;
const {
onDragStart,
camera,
} = this.props;
dragPlane.setFromNormalAndCoplanarPoint(backVector.clone()
.applyQuaternion(camera.quaternion), intersection.point);
this._offset = intersection.point.clone().sub(position);
document.addEventListener('mouseup', this._onDocumentMouseUp);
document.addEventListener('mousemove', this._onDocumentMouseMove);
this.setState({
pressed: true,
});
onDragStart();
};
_onDocumentMouseMove = (event) => {
event.preventDefault();
const {
mouseInput,
} = this.props;
const ray:THREE.Ray = mouseInput.getCameraRay(new THREE
.Vector2(event.clientX, event.clientY));
const intersection = dragPlane.intersectLine(new THREE.Line3(
ray.origin,
ray.origin.clone()
.add(ray.direction.clone().multiplyScalar(10000))
));
if (intersection) {
this.setState({
position: intersection.sub(this._offset),
});
}
};
_onDocumentMouseUp = (event) => {
event.preventDefault();
document.removeEventListener('mouseup', this._onDocumentMouseUp);
document.removeEventListener('mousemove', this._onDocumentMouseMove);
const {
onDragEnd,
} = this.props;
onDragEnd();
this.setState({
pressed: false,
});
};
_onMouseLeave = () => {
if (this.state.hovered) {
this.setState({
hovered: false,
});
}
const {
onMouseLeave,
} = this.props;
onMouseLeave();
};
_ref = (mesh) => {
const {
onCreate,
} = this.props;
onCreate(mesh);
};
render() {
const {
rotation,
scale,
} = this;
const {
cursor: {
dragging,
},
} = this.props;
const {
hovered,
pressed,
position,
} = this.state;
let color;
const hoverHighlight = (hovered && !dragging);
if (pressed) {
color = this.pressedColor;
} else if (hoverHighlight) {
color = this.hoverColor;
} else {
color = this.color;
}
return (
{hoverHighlight ?
: null}
);
}
}
export default DraggableCube;
================================================
FILE: src/examples/DraggableCubes/index.js
================================================
import React from 'react';
import PureRenderMixin from 'react/lib/ReactComponentWithPureRenderMixin';
import * as THREE from 'three';
import Stats from 'stats.js';
import React3 from 'react-three-renderer';
import ExampleBase from '../ExampleBase';
import TrackballControls from '../../ref/trackball';
import MouseInput from '../inputs/MouseInput';
import AllCubes from './AllCubes';
class DraggableCubes extends ExampleBase {
constructor(props, context) {
super(props, context);
this.state = {
cameraPosition: new THREE.Vector3(0, 0, 1000),
cameraRotation: new THREE.Euler(),
mouseInput: null,
hovering: false,
dragging: false,
};
this._cursor = {
hovering: false,
dragging: false,
};
this.lightPosition = new THREE.Vector3(0, 500, 2000);
this.lightTarget = new THREE.Vector3(0, 0, 0);
}
shouldComponentUpdate = PureRenderMixin.shouldComponentUpdate;
_onAnimate = () => {
this._onAnimateInternal();
};
componentDidMount() {
this.stats = new Stats();
this.stats.domElement.style.position = 'absolute';
this.stats.domElement.style.top = '0px';
const {
container,
camera,
} = this.refs;
container.appendChild(this.stats.domElement);
const controls = new TrackballControls(camera);
controls.rotateSpeed = 1.0;
controls.zoomSpeed = 1.2;
controls.panSpeed = 0.8;
controls.noZoom = false;
controls.noPan = false;
controls.staticMoving = true;
controls.dynamicDampingFactor = 0.3;
this.controls = controls;
this.controls.addEventListener('change', this._onTrackballChange);
}
_onCubesMounted = (cubes) => {
this.cubes = cubes;
};
_onHoverStart = () => {
this.setState({
hovering: true,
});
};
_onHoverEnd = () => {
this.setState({
hovering: false,
});
};
_onDragStart = () => {
this.setState({
dragging: true,
});
};
_onDragEnd = () => {
this.setState({
dragging: false,
});
};
componentDidUpdate(newProps) {
const {
mouseInput,
} = this.refs;
const {
width,
height,
} = this.props;
if (width !== newProps.width || height !== newProps.height) {
mouseInput.containerResized();
}
}
_onTrackballChange = () => {
this.setState({
cameraPosition: this.refs.camera.position.clone(),
cameraRotation: this.refs.camera.rotation.clone(),
});
};
componentWillUnmount() {
this.controls.removeEventListener('change', this._onTrackballChange);
this.controls.dispose();
delete this.controls;
delete this.stats;
}
_onAnimateInternal() {
const {
mouseInput,
camera,
} = this.refs;
if (!mouseInput.isReady()) {
const {
scene,
container,
} = this.refs;
mouseInput.ready(scene, container, camera);
mouseInput.restrictIntersections(this.cubes);
mouseInput.setActive(false);
}
if (this.state.mouseInput !== mouseInput) {
this.setState({
mouseInput,
});
}
if (this.state.camera !== camera) {
this.setState({
camera,
});
}
this.stats.update();
this.controls.update();
}
render() {
const {
width,
height,
} = this.props;
const {
cameraPosition,
cameraRotation,
mouseInput,
camera,
hovering,
dragging,
} = this.state;
const style = {};
if (dragging) {
style.cursor = 'move';
} else if (hovering) {
style.cursor = 'pointer';
}
this._cursor.hovering = hovering;
this._cursor.dragging = dragging;
return ();
}
}
export default DraggableCubes;
================================================
FILE: src/examples/ExampleBase.js
================================================
import React from 'react';
import PropTypes from 'prop-types';
class ExampleBase extends React.Component {
static propTypes = {
width: PropTypes.number.isRequired,
height: PropTypes.number.isRequired,
};
}
export default ExampleBase;
================================================
FILE: src/examples/ExampleBrowser.js
================================================
import React from 'react';
import { NavLink } from 'react-router-dom';
import ExampleViewer from './ExampleViewer';
import SimpleExample from './Simple/index';
import ManualRenderingExample from './ManualRendering/index';
import ClothExample from './AnimationCloth/index';
import GeometriesExample from './Geometries/index';
import CameraExample from './WebGLCameraExample/index';
import GeometryShapesExample from './GeometryShapes/index';
import DraggableCubes from './DraggableCubes/index';
import Physics from './Physics/index';
import PhysicsMousePick from './Physics/mousePick';
import BenchmarkRotatingCubes from './Benchmark/RotatingCubes';
import RotatingCubesDirectUpdates from './Benchmark/RotatingCubesDirectUpdates';
const examples = [
{
name: 'Simple',
component: SimpleExample,
url: 'Simple/index',
slug: 'webgl_simple',
},
{
name: 'Cloth',
component: ClothExample,
url: 'AnimationCloth/index',
slug: 'webgl_cloth',
},
{
name: 'Camera',
component: CameraExample,
url: 'WebGLCameraExample/index',
slug: 'webgl_camera',
},
{
name: 'Geometries',
component: GeometriesExample,
url: 'Geometries/index',
slug: 'webgl_geometries',
},
{
name: 'Geometry Shapes',
component: GeometryShapesExample,
url: 'GeometryShapes/index',
slug: 'webgl_geometry_shapes',
},
{
name: 'Draggable Cubes',
component: DraggableCubes,
url: 'DraggableCubes/index',
slug: 'webgl_draggable_cubes',
},
{
name: 'Physics',
component: Physics,
url: 'Physics/index',
slug: 'webgl_physics',
},
{
name: 'Physics - MousePick',
component: PhysicsMousePick,
url: 'Physics/mousePick',
slug: 'webgl_physics_mousepick',
},
{
separator: true,
name: 'Advanced',
},
{
name: 'Without react-dom',
advanced: true,
page: 'advanced.html',
},
{
name: 'Manual rendering',
component: ManualRenderingExample,
url: 'ManualRendering/index',
slug: 'advanced_manual_rendering',
},
{
separator: true,
name: 'Benchmarks',
},
{
name: 'RotatingCubes - Through React',
component: BenchmarkRotatingCubes,
url: 'Benchmark/RotatingCubes',
slug: 'benchmarks_rotating_cubes_react',
},
{
name: 'RotatingCubes - Direct Updates',
component: RotatingCubesDirectUpdates,
url: 'Benchmark/RotatingCubesDirectUpdates',
slug: 'benchmarks_rotating_cubes_direct',
},
];
const ExampleBrowser = ({ match }) => {
const { params } = match;
const activeExample = params.slug && examples.find(example => example.slug === params.slug);
return (
webgl
{examples.map((example, index) => {
if (example.separator) {
return (
{example.name} );
}
if (example.advanced) {
return (
);
}
return (
{example.name}
);
})}
);
};
ExampleBrowser.propTypes = {
match: React.PropTypes.object.isRequired,
};
export default ExampleBrowser;
================================================
FILE: src/examples/ExampleViewer.js
================================================
import React from 'react';
import sizeMe from 'react-sizeme';
const ExampleViewer = ({ example, size }) => {
let sourceButton = null;
let exampleContent = null;
if (example) {
const {
component: ExampleComponent,
url,
} = example;
exampleContent = ( );
sourceButton = ();
}
return (
{exampleContent}
{sourceButton}
);
};
ExampleViewer.propTypes = {
example: React.PropTypes.object,
size: React.PropTypes.object,
};
export default sizeMe({ monitorHeight: true, refreshRate: 200 })(ExampleViewer);
================================================
FILE: src/examples/Geometries/index.js
================================================
import React from 'react';
import * as THREE from 'three';
import Stats from 'stats.js';
import React3 from 'react-three-renderer';
import ExampleBase from '../ExampleBase';
class Geometries extends ExampleBase {
constructor(props, context) {
super(props, context);
this.directionalLightPosition = new THREE.Vector3(0, 1, 0);
this.objectPositions = [
new THREE.Vector3(-400, 0, 200),
new THREE.Vector3(-200, 0, 200),
new THREE.Vector3(0, 0, 200),
new THREE.Vector3(200, 0, 200),
new THREE.Vector3(-400, 0, 0),
new THREE.Vector3(-200, 0, 0),
new THREE.Vector3(0, 0, 0),
new THREE.Vector3(200, 0, 0),
new THREE.Vector3(400, 0, 0),
new THREE.Vector3(-400, 0, -200),
new THREE.Vector3(-200, 0, -200),
new THREE.Vector3(0, 0, -200),
new THREE.Vector3(200, 0, -200),
new THREE.Vector3(400, 0, -200),
];
this.lathePoints = [];
for (let i = 0; i < 50; i++) {
this.lathePoints.push(new THREE
.Vector2(Math.sin(i * 0.2) * Math.sin(i * 0.1) * 15 + 50, (i - 5) * 2));
}
this.arrowDir = new THREE.Vector3(0, 1, 0);
this.arrowOrigin = new THREE.Vector3(0, 0, 0);
this.scenePosition = new THREE.Vector3(0, 0, 0);
this.state = {
...this.state,
timer: Date.now() * 0.0001,
};
}
_onAnimate = () => {
this._onAnimateInternal();
};
componentDidMount() {
this.stats = new Stats();
this.stats.domElement.style.position = 'absolute';
this.stats.domElement.style.top = '0px';
this.refs.container.appendChild(this.stats.domElement);
}
componentWillUnmount() {
delete this.stats;
}
_onAnimateInternal() {
const timer = Date.now() * 0.0001;
this.setState({
timer,
});
this.stats.update();
}
render() {
const {
width,
height,
} = this.props;
const {
timer,
} = this.state;
const objectRotation = new THREE.Euler(
timer * 5,
timer * 2.5,
0
);
return ();
}
}
export default Geometries;
================================================
FILE: src/examples/GeometryShapes/Rect.js
================================================
import React from 'react';
import PropTypes from 'react/lib/ReactPropTypes';
function Rect(props) {
const {
width,
length,
resourceId,
} = props;
return (
);
}
Rect.propTypes = {
width: PropTypes.number.isRequired,
length: PropTypes.number.isRequired,
resourceId: PropTypes.string.isRequired,
};
export default Rect;
================================================
FILE: src/examples/GeometryShapes/Resources.js
================================================
import React from 'react';
import * as THREE from 'three';
import Rect from './Rect';
class Resources extends React.Component {
shouldComponentUpdate() {
return false;
}
render() {
this.textureRepeat = new THREE.Vector2(0.008, 0.008);
const x = 0;
const y = 0;
const sqLength = 80;
const rectLength = 120;
const rectWidth = 40;
const californiaPts = [];
californiaPts.push(new THREE.Vector2(610, 320));
californiaPts.push(new THREE.Vector2(450, 300));
californiaPts.push(new THREE.Vector2(392, 392));
californiaPts.push(new THREE.Vector2(266, 438));
californiaPts.push(new THREE.Vector2(190, 570));
californiaPts.push(new THREE.Vector2(190, 600));
californiaPts.push(new THREE.Vector2(160, 620));
californiaPts.push(new THREE.Vector2(160, 650));
californiaPts.push(new THREE.Vector2(180, 640));
californiaPts.push(new THREE.Vector2(165, 680));
californiaPts.push(new THREE.Vector2(150, 670));
californiaPts.push(new THREE.Vector2(90, 737));
californiaPts.push(new THREE.Vector2(80, 795));
californiaPts.push(new THREE.Vector2(50, 835));
californiaPts.push(new THREE.Vector2(64, 870));
californiaPts.push(new THREE.Vector2(60, 945));
californiaPts.push(new THREE.Vector2(300, 945));
californiaPts.push(new THREE.Vector2(300, 743));
californiaPts.push(new THREE.Vector2(600, 473));
californiaPts.push(new THREE.Vector2(626, 425));
californiaPts.push(new THREE.Vector2(600, 370));
californiaPts.push(new THREE.Vector2(610, 320));
for (let i = 0; i < californiaPts.length; i++) californiaPts[i].multiplyScalar(0.25);
this.californiaPts = californiaPts;
return (
{((function roundedRect(rectX, rectY,
roundedRectWidth, roundedRectHeight,
radius) {
return (
);
})(0, 0, 50, 50, 20))}
{((function circleShape() {
const circleRadius = 40;
return (
);
})())}
{((function splineShape() {
const splinePoints = [];
splinePoints.push(new THREE.Vector2(70, 20));
splinePoints.push(new THREE.Vector2(80, 90));
splinePoints.push(new THREE.Vector2(-30, 70));
splinePoints.push(new THREE.Vector2(0, 0));
return (
);
})())}
);
}
}
export default Resources;
================================================
FILE: src/examples/GeometryShapes/Shape.js
================================================
import React from 'react';
import * as THREE from 'three';
import PropTypes from 'react/lib/ReactPropTypes';
import PureRenderMixin from 'react/lib/ReactComponentWithPureRenderMixin';
const extrudeSettings = {
amount: 8,
bevelEnabled: true,
bevelSegments: 2,
steps: 2,
bevelSize: 1,
bevelThickness: 1,
};
class Shape extends React.Component {
static propTypes = {
resourceId: PropTypes.string.isRequired,
color: PropTypes.any.isRequired,
x: PropTypes.number.isRequired,
y: PropTypes.number.isRequired,
z: PropTypes.number.isRequired,
rx: PropTypes.number.isRequired,
ry: PropTypes.number.isRequired,
rz: PropTypes.number.isRequired,
s: PropTypes.number.isRequired,
};
shouldComponentUpdate = PureRenderMixin.shouldComponentUpdate;
render() {
const {
rx,
ry,
rz,
s,
resourceId,
color,
x,
y,
z,
} = this.props;
const rotation = new THREE.Euler(rx, ry, rz);
const scale = new THREE.Vector3(s, s, s);
return (
);
}
}
export default Shape;
================================================
FILE: src/examples/GeometryShapes/Shapes.js
================================================
import React from 'react';
import Shape from './Shape';
class Shapes extends React.Component {
shouldComponentUpdate() {
return false;
}
render() {
return (
);
}
}
export default Shapes;
================================================
FILE: src/examples/GeometryShapes/index.js
================================================
import React from 'react';
import * as THREE from 'three';
import Stats from 'stats.js';
import React3 from 'react-three-renderer';
import ExampleBase from '../ExampleBase';
import Resources from './Resources';
import Shapes from './Shapes';
class GeometryShapes extends ExampleBase {
constructor(props, context) {
super(props, context);
this.cameraPosition = new THREE.Vector3(0, 150, 500);
this.groupPosition = new THREE.Vector3(0, 50, 0);
this.targetRotationOnMouseDown = 0;
this.mouseX = 0;
this.mouseXOnMouseDown = 0;
this.targetRotation = 0;
this.state = {
...this.state,
groupRotation: new THREE.Euler(0, 0, 0),
};
}
componentDidMount() {
this.stats = new Stats();
const container = this.refs.container;
this.stats.domElement.style.position = 'absolute';
this.stats.domElement.style.top = '0px';
container.appendChild(this.stats.domElement);
container.addEventListener('mousedown', this._onDocumentMouseDown, false);
container.addEventListener('touchstart', this._onDocumentTouchStart, false);
document.addEventListener('touchmove', this._onDocumentTouchMove, false);
}
componentWillUnmount() {
const container = this.refs.container;
container.removeEventListener('mousedown', this._onDocumentMouseDown, false);
container.removeEventListener('touchstart', this._onDocumentTouchStart, false);
document.removeEventListener('touchmove', this._onDocumentTouchMove, false);
document.removeEventListener('mousemove', this._onDocumentMouseMove, false);
document.removeEventListener('mouseup', this._onDocumentMouseUp, false);
document.removeEventListener('mouseout', this._onDocumentMouseOut, false);
delete this.stats;
}
_onDocumentMouseDown = (event) => {
event.preventDefault();
document.addEventListener('mousemove', this._onDocumentMouseMove, false);
document.addEventListener('mouseup', this._onDocumentMouseUp, false);
document.addEventListener('mouseout', this._onDocumentMouseOut, false);
const {
width,
} = this.props;
const windowHalfX = width / 2;
this.mouseXOnMouseDown = event.clientX - windowHalfX;
this.targetRotationOnMouseDown = this.targetRotation;
};
_onDocumentMouseMove = (event) => {
const {
width,
} = this.props;
const windowHalfX = width / 2;
this.mouseX = event.clientX - windowHalfX;
this.targetRotation = this.targetRotationOnMouseDown +
(this.mouseX - this.mouseXOnMouseDown) * 0.02;
};
_onDocumentMouseUp = () => {
document.removeEventListener('mousemove', this._onDocumentMouseMove, false);
document.removeEventListener('mouseup', this._onDocumentMouseUp, false);
document.removeEventListener('mouseout', this._onDocumentMouseOut, false);
};
_onDocumentMouseOut = () => {
document.removeEventListener('mousemove', this._onDocumentMouseMove, false);
document.removeEventListener('mouseup', this._onDocumentMouseUp, false);
document.removeEventListener('mouseout', this._onDocumentMouseOut, false);
};
_onDocumentTouchStart = (event) => {
if (event.touches.length === 1) {
event.preventDefault();
const {
width,
} = this.props;
const windowHalfX = width / 2;
this.mouseXOnMouseDown = event.touches[0].pageX - windowHalfX;
this.targetRotationOnMouseDown = this.targetRotation;
}
};
_onDocumentTouchMove = (event) => {
if (event.touches.length === 1) {
event.preventDefault();
const {
width,
} = this.props;
const windowHalfX = width / 2;
this.mouseX = event.touches[0].pageX - windowHalfX;
this.targetRotation = this.targetRotationOnMouseDown +
(this.mouseX - this.mouseXOnMouseDown) * 0.05;
}
};
_onAnimate = () => {
this._onAnimateInternal();
};
_onAnimateInternal() {
const groupRotationY = this.state.groupRotation.y;
if (Math.abs(groupRotationY - this.targetRotation) > 0.0001) {
this.setState({
groupRotation: new THREE.Euler(0, groupRotationY +
(this.targetRotation - groupRotationY) * 0.05, 0),
});
}
this.stats.update();
}
render() {
const {
width,
height,
} = this.props;
const {
groupRotation,
} = this.state;
return (
Simple procedurally generated 3D shapes
Drag to spin
);
}
}
export default GeometryShapes;
================================================
FILE: src/examples/ManualRendering/Info.js
================================================
import React from 'react';
import PropTypes from 'prop-types';
class Info extends React.Component {
static propTypes = {
onUpdateColorButtonPress: PropTypes.func.isRequired,
onRenderButtonPress: PropTypes.func.isRequired,
onManualButtonPress: PropTypes.func.isRequired,
forceManual: PropTypes.bool,
cubeColor: PropTypes.number.isRequired,
};
constructor(props, context) {
super(props, context);
this.state = {
colorButtonPressed: false,
};
}
_onColorButtonPress = () => {
this.props.onUpdateColorButtonPress();
this.setState({
colorButtonPressed: true,
});
};
_onTriggerPress = () => {
this.setState({
colorButtonPressed: false,
});
this.props.onRenderButtonPress();
};
_manualButtonPress = () => {
this.setState({
colorButtonPressed: false,
});
this.props.onManualButtonPress();
};
render() {
const {
forceManual,
cubeColor,
} = this.props;
const {
colorButtonPressed,
} = this.state;
const triggerButtonStyle = {};
if (colorButtonPressed && forceManual) {
triggerButtonStyle.fontWeight = 'bold';
}
return (
Manual rendering
If automatic rendering is off, the canvas will re-render only
when you press the "Trigger render" button.
This way you can save battery life
or have finer controls for rendering.
Check your CPU profiler with automatic rendering on/off :)
Update cube color state
> 16 & 255)},
${(cubeColor >> 8 & 255)},
${(cubeColor & 255)})`,
}}
>
{colorButtonPressed && forceManual ? 'TRIGGER RENDER' : 'Trigger render'}
Turn automatic rendering {forceManual ? 'ON' : 'OFF'}
);
}
}
export default Info;
================================================
FILE: src/examples/ManualRendering/index.js
================================================
import React from 'react';
import React3 from 'react-three-renderer';
import * as THREE from 'three';
import ExampleBase from '../ExampleBase';
import Info from './Info';
class Manual extends ExampleBase {
constructor(props, context) {
super(props, context);
this.cameraPosition = new THREE.Vector3(0, 0, 5);
this._onManualRenderTriggerCreated = (renderTrigger) => {
// assign to variable to be able to reuse the trigger
this._renderTrigger = renderTrigger;
};
this.state = {
cubeColor: 0x00ff00,
forceManual: true,
};
}
componentDidMount() {
// render one frame to show initial scene
this._renderTrigger();
setTimeout(() => {
// this should not be visible!
this.setState({
cubeColor: 0x0000ff,
});
}, 20);
setTimeout(() => {
// render again after updating color
this.setState({
cubeColor: 0xff0000,
}, () => {
this._renderTrigger();
});
}, 1000);
}
_onUpdateColorButtonPress = () => {
this.setState({
cubeColor: Math.floor(Math.random() * 0xffffff),
});
};
_onRenderButtonPress = () => {
this._renderTrigger();
};
_onManualButtonPress = () => {
this.setState({
forceManual: !this.state.forceManual,
});
};
render() {
const {
width,
height,
} = this.props;
const {
forceManual,
cubeColor,
} = this.state;
return ();
}
}
export default Manual;
================================================
FILE: src/examples/Physics/index.js
================================================
import React from 'react';
import React3 from 'react-three-renderer';
import * as THREE from 'three';
import CANNON from 'cannon/src/Cannon';
import Stats from 'stats.js';
import ExampleBase from '../ExampleBase';
class Physics extends ExampleBase {
constructor(props, context) {
super(props, context);
const world = new CANNON.World();
this.world = world;
world.gravity.set(0, 0, 0);
world.broadphase = new CANNON.NaiveBroadphase();
world.solver.iterations = 10;
const shape = new CANNON.Box(new CANNON.Vec3(1, 1, 1));
const mass = 1;
const body = new CANNON.Body({
mass,
});
body.addShape(shape);
body.angularVelocity.set(0, 10, 0);
body.angularDamping = 0.5;
world.addBody(body);
this._onMouseDown = () => {
body.angularVelocity.y += 5;
};
this.cameraPosition = new THREE.Vector3(0, 0, 5);
const timeStep = 1 / 60;
const updatePhysics = () => {
// Step the physics world
world.step(timeStep);
// Copy coordinates from Cannon.js to Three.js
this.setState({
// need to call new THREE.* in order to ensure an update goes through
meshPosition: new THREE.Vector3().copy(body.position),
meshQuaternion: new THREE.Quaternion().copy(body.quaternion),
});
};
this._onAnimate = () => {
updatePhysics();
this.stats.update();
};
this.state = {
meshPosition: new THREE.Vector3(),
meshQuaternion: new THREE.Quaternion(),
};
}
componentWillUnmount() {
delete this.world;
delete this.stats;
}
componentDidMount() {
const {
container,
} = this.refs;
this.stats = new Stats();
this.stats.domElement.style.position = 'absolute';
this.stats.domElement.style.top = '0px';
container.appendChild(this.stats.domElement);
}
render() {
const {
width,
height,
} = this.props;
const {
meshPosition,
meshQuaternion,
} = this.state;
return ();
}
}
export default Physics;
================================================
FILE: src/examples/Physics/mousePick/PickableMesh.js
================================================
import React from 'react';
import * as THREE from 'three';
import PropTypes from 'prop-types';
class PickableMesh extends React.Component {
static propTypes = {
position: PropTypes.instanceOf(THREE.Vector3).isRequired,
quaternion: PropTypes.instanceOf(THREE.Quaternion).isRequired,
meshes: PropTypes.arrayOf(PropTypes.instanceOf(THREE.Mesh)).isRequired,
bodyIndex: PropTypes.number.isRequired,
onMouseDown: PropTypes.func.isRequired,
};
componentDidMount() {
const {
mesh,
} = this.refs;
const {
bodyIndex,
meshes,
} = this.props;
mesh.userData._bodyIndex = bodyIndex;
meshes.push(mesh);
}
componentWillUnmount() {
const {
mesh,
} = this.refs;
const {
meshes,
} = this.props;
meshes.splice(meshes.indexOf(mesh), 1);
}
_onMouseDown = (event, intersection) => {
event.preventDefault();
this.props.onMouseDown(this.refs.mesh.userData._bodyIndex, intersection);
};
render() {
const {
position,
quaternion,
} = this.props;
return (
);
}
}
export default PickableMesh;
================================================
FILE: src/examples/Physics/mousePick.js
================================================
import React from 'react';
import React3 from 'react-three-renderer';
import * as THREE from 'three';
import CANNON from 'cannon/src/Cannon';
import MouseInput from '../inputs/MouseInput';
import ExampleBase from '../ExampleBase';
import Stats from 'stats.js';
import PickableMesh from './mousePick/PickableMesh';
const backVector = new THREE.Vector3(0, 0, -1);
const dragPlane = new THREE.Plane();
class PhysicsMousePick extends ExampleBase {
constructor(props, context) {
super(props, context);
const N = 100;
this._raycaster = new THREE.Raycaster();
this.fog = new THREE.Fog(0x001525, 10, 40);
const d = 20;
this.lightPosition = new THREE.Vector3(d, d, d);
this.lightTarget = new THREE.Vector3(0, 0, 0);
this.groundQuaternion = new THREE.Quaternion()
.setFromAxisAngle(new THREE.Vector3(1, 0, 0), -Math.PI / 2);
this.cameraPosition = new THREE.Vector3(10, 2, 0);
this.cameraQuaternion = new THREE.Quaternion()
.setFromAxisAngle(new THREE.Vector3(0, 1, 0), Math.PI / 2);
const world = new CANNON.World();
const bodies = [];
const meshRefs = [];
let constrainedBody;
let pivot;
const initCannon = () => {
world.quatNormalizeSkip = 0;
world.quatNormalizeFast = false;
world.gravity.set(0, -10, 0);
world.broadphase = new CANNON.NaiveBroadphase();
const mass = 5;
const boxShape = new CANNON.Box(new CANNON.Vec3(0.25, 0.25, 0.25));
for (let i = 0; i < N; ++i) {
const boxBody = new CANNON.Body({
mass,
});
boxBody.addShape(boxShape);
boxBody.position.set(
-2.5 + Math.random() * 5,
2.5 + Math.random() * 5,
-2.5 + Math.random() * 5);
world.addBody(boxBody);
bodies.push(boxBody);
meshRefs.push((mesh) => {
if (mesh) {
mesh.userData._bodyIndex = i;
this.meshes.push(mesh);
}
});
}
const groundShape = new CANNON.Plane();
const groundBody = new CANNON.Body({ mass: 0 });
groundBody.addShape(groundShape);
groundBody.quaternion.setFromAxisAngle(new CANNON.Vec3(1, 0, 0), -Math.PI / 2);
// WAIT A MINUTE I CAN CREATE A REACT RENDERER FOR CANNON
// patience is a virtue
// breathe in breathe out breathe in breathe out
// let's finish this one first
world.addBody(groundBody);
const shape = new CANNON.Sphere(0.1);
const jointBody = new CANNON.Body({ mass: 0 });
jointBody.addShape(shape);
jointBody.collisionFilterGroup = 0;
jointBody.collisionFilterMask = 0;
world.addBody(jointBody);
this.jointBody = jointBody;
};
initCannon();
const timeStep = 1 / 60;
const updatePhysics = () => {
// Step the physics world
world.step(timeStep);
};
const _getMeshStates = () => bodies
.map(({ position, quaternion }, bodyIndex) => ({
position: new THREE.Vector3().copy(position),
quaternion: new THREE.Quaternion().copy(quaternion),
ref: meshRefs[bodyIndex],
}));
this._onAnimate = () => {
updatePhysics();
this.setState({
meshStates: _getMeshStates(),
});
this.stats.update();
};
this._addMouseConstraint = ({ x, y, z }, bodyIndex) => {
// The cannon body constrained by the mouse joint
constrainedBody = bodies[bodyIndex];
// Vector to the clicked point, relative to the body
const v1 = new CANNON.Vec3(x, y, z).vsub(constrainedBody.position);
// Apply anti-quaternion to vector to transform it into the local body coordinate system
const antiRot = constrainedBody.quaternion.inverse();
pivot = antiRot.vmult(v1); // pivot is not in local body coordinates
// Move the cannon click marker particle to the click position
this.jointBody.position.set(x, y, z);
// Create a new constraint
// The pivot for the jointBody is zero
this.mouseConstraint = new CANNON
.PointToPointConstraint(
constrainedBody,
pivot,
this.jointBody,
new CANNON.Vec3(0, 0, 0)
);
// Add the t to world
world.addConstraint(this.mouseConstraint);
this.world = world;
};
this.state = {
clickMarkerVisible: false,
clickMarkerPosition: new THREE.Vector3(),
meshStates: _getMeshStates(),
};
this.meshes = [];
}
_setClickMarker(x, y, z) {
return {
clickMarkerPosition: new THREE.Vector3(x, y, z),
clickMarkerVisible: true,
};
}
componentDidMount() {
const {
mouseInput,
container,
} = this.refs;
this.stats = new Stats();
this.stats.domElement.style.position = 'absolute';
this.stats.domElement.style.top = '0px';
container.appendChild(this.stats.domElement);
if (!mouseInput.isReady()) {
const {
scene,
camera,
} = this.refs;
mouseInput.ready(scene, container, camera);
mouseInput.restrictIntersections(this.meshes);
mouseInput.setActive(false);
}
}
componentDidUpdate(newProps) {
const {
mouseInput,
} = this.refs;
const {
width,
height,
} = this.props;
if (width !== newProps.width || height !== newProps.height) {
mouseInput.containerResized();
}
}
componentWillUnmount() {
delete this.world;
delete this.stats;
}
_onMeshMouseDown = (bodyIndex, intersection) => {
const {
camera,
} = this.refs;
const pos = intersection.point;
this.setState({
// Set marker on contact point
...this._setClickMarker(pos.x, pos.y, pos.z),
});
dragPlane.setFromNormalAndCoplanarPoint(backVector.clone()
.applyQuaternion(camera.quaternion), pos);
this._addMouseConstraint(pos, bodyIndex);
window.addEventListener('mousemove', this._onMouseMove, false);
window.addEventListener('mouseup', this._onMouseUp, false);
};
_onMouseUp = () => {
window.removeEventListener('mousemove', this._onMouseMove, false);
window.removeEventListener('mouseup', this._onMouseUp, false);
this.setState({
clickMarkerVisible: false,
});
this.world.removeConstraint(this.mouseConstraint);
this.mouseConstraint = false;
};
_onMouseMove = (event) => {
const {
mouseInput,
} = this.refs;
const ray:THREE.Ray = mouseInput.getCameraRay(new THREE.Vector2(event.clientX, event.clientY));
const pos = dragPlane.intersectLine(
new THREE.Line3(ray.origin, ray.origin
.clone()
.add(ray.direction
.clone()
.multiplyScalar(10000))));
if (pos) {
this.setState({
... this._setClickMarker(pos.x, pos.y, pos.z),
});
// Move the joint body to a new position
this.jointBody.position.set(pos.x, pos.y, pos.z);
this.mouseConstraint.update();
}
};
render() {
const {
width,
height,
} = this.props;
const {
clickMarkerVisible,
clickMarkerPosition,
meshStates,
} = this.state;
const d = 20;
const cubeMeshes = meshStates.map(({ position, quaternion }, i) =>
( ));
return ();
}
}
export default PhysicsMousePick;
================================================
FILE: src/examples/Simple/index.js
================================================
import React from 'react';
import React3 from 'react-three-renderer';
import * as THREE from 'three';
class Simple extends React.Component {
static propTypes = {
width: React.PropTypes.number.isRequired,
height: React.PropTypes.number.isRequired,
};
constructor(props, context) {
super(props, context);
this.cameraPosition = new THREE.Vector3(0, 0, 5);
// construct the position vector here, because if we use 'new' within render,
// React will think that things have changed when they have not.
this.state = {
cubeRotation: new THREE.Euler(),
};
this._onAnimate = () => {
// we will get this callback every frame
// pretend cubeRotation is immutable.
// this helps with updates and pure rendering.
// React will be sure that the rotation has now updated.
this.setState({
cubeRotation: new THREE.Euler(
this.state.cubeRotation.x + 0.1,
this.state.cubeRotation.y + 0.1,
0
),
});
};
}
render() {
const {
width,
height,
} = this.props;
// or you can use:
// width = window.innerWidth
// height = window.innerHeight
return (
);
}
}
export default Simple;
================================================
FILE: src/examples/WebGLCameraExample/Info.js
================================================
import React from 'react';
import PropTypes from 'prop-types';
class Info extends React.Component {
static propTypes = {
pause: PropTypes.func.isRequired,
frame: PropTypes.func.isRequired,
};
render() {
return (
three.js - cameras
O orthographic
P perspective
Pause
Frame
);
}
}
export default Info;
================================================
FILE: src/examples/WebGLCameraExample/PointCloud.js
================================================
import React from 'react';
import * as THREE from 'three';
class PointCloud extends React.Component {
constructor(props, context) {
super(props, context);
this.pointCloudVertices = [];
for (let i = 0; i < 10000; i++) {
const vertex = new THREE.Vector3();
vertex.x = THREE.Math.randFloatSpread(2000);
vertex.y = THREE.Math.randFloatSpread(2000);
vertex.z = THREE.Math.randFloatSpread(2000);
this.pointCloudVertices.push(vertex);
}
}
shouldComponentUpdate() {
return false;
}
render() {
return (
);
}
}
export default PointCloud;
================================================
FILE: src/examples/WebGLCameraExample/index.js
================================================
import React from 'react';
import ReactDOM from 'react-dom';
import * as THREE from 'three';
import ExampleBase from './../ExampleBase';
import React3 from 'react-three-renderer';
import Info from './Info';
import PointCloud from './PointCloud';
import TrackballControls from '../../ref/trackball';
const perspectiveCameraName = 'perspectiveCamera';
const orthographicCameraName = 'orthographicCamera';
const mainCameraName = 'mainCamera';
const perspectiveCameraRotation = new THREE.Euler(0, Math.PI, 0);
const orthographicCameraRotation = new THREE.Euler(0, Math.PI, 0);
const spherePosition = new THREE.Vector3(0, 0, 150);
class WebGLCameraExample extends ExampleBase {
constructor(props, context) {
super(props, context);
const r = Date.now() * 0.0005;
this.state = {
... this.state,
meshPosition: new THREE.Vector3(Math.cos(r), Math.sin(r), Math.sin(r)).multiplyScalar(700),
childPosition: new THREE.Vector3(70 * Math.cos(2 * r), 150, 70 * Math.sin(r)),
activeCameraName: perspectiveCameraName,
paused: false,
mainCameraPosition: new THREE.Vector3(0, 0, 2500),
};
}
componentDidMount() {
document.addEventListener('keydown', this._onKeyDown, false);
const controls = new TrackballControls(this.refs.mainCamera,
ReactDOM.findDOMNode(this.refs.react3));
controls.rotateSpeed = 1.0;
controls.zoomSpeed = 1.2;
controls.panSpeed = 0.8;
controls.noZoom = false;
controls.noPan = false;
controls.staticMoving = true;
controls.dynamicDampingFactor = 0.3;
controls.addEventListener('change', () => {
this.setState({
mainCameraPosition: this.refs.mainCamera.position,
});
});
this.controls = controls;
}
componentWillUnmount() {
document.removeEventListener('keydown', this._onKeyDown, false);
this.controls.dispose();
delete this.controls;
}
_onKeyDown = (event) => {
switch (event.keyCode) {
default:
break;
case 79: // O
this.setState({
activeCameraName: orthographicCameraName,
});
break;
case 80: // P
this.setState({
activeCameraName: perspectiveCameraName,
});
break;
}
};
_onAnimate = () => {
this.controls.update();
if (this.state.paused) {
return;
}
const r = Date.now() * 0.0005;
this.setState({
r,
meshPosition: new THREE.Vector3(Math.cos(r), Math.sin(r), Math.sin(r)).multiplyScalar(700),
childPosition: new THREE.Vector3(70 * Math.cos(2 * r), 150, 70 * Math.sin(r)),
});
};
_pause = () => {
this.setState({
paused: !this.state.paused,
});
};
_frame = () => {
this.setState({
paused: false,
}, () => {
this._onAnimate();
this.setState({
paused: true,
});
});
};
render() {
const {
width,
height,
} = this.props;
const {
meshPosition,
childPosition,
r,
} = this.state;
const aspectRatio = 0.5 * width / height;
return ();
}
}
export default WebGLCameraExample;
================================================
FILE: src/examples/inputs/MouseInput.js
================================================
import React3 from 'react-three-renderer';
import * as THREE from 'three';
import ReactUpdates from 'react-dom/lib/ReactUpdates';
import SyntheticMouseEvent from 'react-dom/lib/SyntheticMouseEvent';
import Module from 'react-three-renderer/lib/Module';
import PropTypes from 'react/lib/ReactPropTypes';
const tempVector2 = new THREE.Vector2();
const listenerCallbackNames = {
mousedown: 'onMouseDown',
mouseup: 'onMouseUp',
};
const mouseEvents = [
'onMouseEnter',
'onMouseLeave',
'onMouseDown',
'onMouseUp',
'onClick',
];
const boolProps = {
ignorePointerEvents: false,
};
class MouseInput extends Module {
constructor() {
super();
this._isReady = false;
this._active = true;
this._restrictIntersections = false;
this._objectsToIntersect = null;
this._restrictedIntersectionRecursive = false;
this._patchedDescriptors = [];
}
// noinspection JSUnusedGlobalSymbols
setup(react3RendererInstance) {
super.setup(react3RendererInstance);
const Object3DDescriptor = react3RendererInstance.threeElementDescriptors.object3D.constructor;
Object.values(react3RendererInstance.threeElementDescriptors).forEach(elementDescriptor => {
if (elementDescriptor instanceof Object3DDescriptor) {
mouseEvents.forEach(eventName => {
elementDescriptor.hasEvent(eventName);
});
Object.keys(boolProps).forEach(propName => {
elementDescriptor.hasProp(propName, {
type: PropTypes.bool,
update(threeObject, value, hasProp) {
if (hasProp) {
threeObject.userData[propName] = value;
} else {
threeObject.userData[propName] = boolProps[propName];
}
},
default: boolProps[propName],
});
});
this._patchedDescriptors.push(elementDescriptor);
}
});
}
isReady() {
return this._isReady;
}
setActive(active) {
this._active = active;
}
restrictIntersections(objects, recursive = false) {
this._restrictIntersections = true;
this._objectsToIntersect = objects;
this._restrictedIntersectionRecursive = recursive;
}
ready(scene, container, camera) {
this._isReady = true;
this._scene = scene;
this._container = container;
this._camera = camera;
this._raycaster = new THREE.Raycaster();
this._mouse = new THREE.Vector2();
this._onMouseMove = (event) => {
this._mouse.set(event.clientX, event.clientY);
if (!this._active) {
this._updateEnterLeave();
}
};
this._containerRect = this._container.getBoundingClientRect();
this._hoverObjectMap = {};
document.addEventListener('mousemove', this._onMouseMove, false);
this._intersectionsForClick = null;
this._caughtListenersCleanupFunctions = [];
Object.keys(listenerCallbackNames).forEach(eventName => {
let boundListener;
const listenerCallbackName = listenerCallbackNames[eventName];
switch (eventName) {
case 'mousedown':
boundListener = this._onMouseDown.bind(this, listenerCallbackName);
break;
case 'mouseup':
boundListener = this._onMouseUp.bind(this, listenerCallbackName);
break;
default:
break;
}
if (boundListener) {
container.addEventListener(eventName, boundListener, true);
this._caughtListenersCleanupFunctions.push(() => {
container.removeEventListener(eventName, boundListener, true);
});
}
});
}
_onMouseDown = (callbackName, mouseEvent) => {
ReactUpdates.batchedUpdates(() => {
const {
event,
intersections,
} = this._intersectAndDispatch(callbackName, mouseEvent);
if (event.isDefaultPrevented() || event.isPropagationStopped()) {
this._intersectionsForClick = null;
} else {
this._intersectionsForClick = intersections;
}
});
};
_onMouseUp = (callbackName, mouseEvent) => {
ReactUpdates.batchedUpdates(() => {
const {
event,
intersections,
} = this._intersectAndDispatch(callbackName, mouseEvent);
if (!(event.isDefaultPrevented() || event.isPropagationStopped())) {
if (this._intersectionsForClick === null) {
return;
}
// intersect current intersections with the intersections for click
// call xzibit ASAP we have a good one son
// it wasn't that good
const intersectionUUIDMap = this._intersectionsForClick.reduce((map, intersection) => {
map[intersection.object.uuid] = intersection;
return map;
}, {});
for (let i = 0; i < intersections.length; ++i) {
if (event.isDefaultPrevented() || event.isPropagationStopped()) {
return;
}
const intersection = intersections[i];
const object = intersection.object;
const uuid = object.uuid;
if (intersectionUUIDMap[uuid]) {
// oh boy oh boy here we go, we got a clicker
React3.eventDispatcher
.dispatchEvent(object, 'onClick',
this._createSyntheticMouseEvent('click', event), intersection);
}
}
}
});
this._intersectionsForClick = null;
};
_createSyntheticMouseEvent(eventType, prototype) {
return SyntheticMouseEvent.getPooled(null, null,
new MouseEvent(eventType, prototype), prototype.target);
}
_intersectAndDispatch(callbackName, mouseEvent) {
const event = SyntheticMouseEvent.getPooled(null, null, mouseEvent, mouseEvent.target);
const intersections = this._getIntersections(tempVector2.set(event.clientX, event.clientY));
ReactUpdates.batchedUpdates(() => {
for (let i = 0; i < intersections.length; ++i) {
const intersection = intersections[i];
if (event.isDefaultPrevented() || event.isPropagationStopped()) {
return;
}
const object = intersection.object;
React3.eventDispatcher.dispatchEvent(object, callbackName, event, intersection);
}
});
return {
event,
intersections,
};
}
_getIntersections(mouseCoords) {
const relativeMouseCoords = this._getRelativeMouseCoords(mouseCoords);
this._raycaster.setFromCamera(relativeMouseCoords, this._camera);
if (this._restrictIntersections) {
return this._raycaster.intersectObjects(this._objectsToIntersect,
this._restrictedIntersectionRecursive);
}
return this._raycaster.intersectObject(this._scene, true);
}
// noinspection JSUnusedGlobalSymbols
/**
*
* @param {THREE.Vector2} mouseCoords usually an event's clientX and clientY
* @returns {THREE.Ray}
*/
getCameraRay(mouseCoords) {
const relativeMouseCoords = this._getRelativeMouseCoords(mouseCoords);
const originalRay = this._raycaster.ray.clone();
this._raycaster.setFromCamera(relativeMouseCoords, this._camera);
const resultRay = this._raycaster.ray.clone();
this._raycaster.ray.copy(originalRay);
return resultRay;
}
// noinspection JSUnusedGlobalSymbols
intersectObject(mouseCoords, object, recursive = false) {
const relativeMouseCoords = this._getRelativeMouseCoords(mouseCoords);
const originalRay = this._raycaster.ray.clone();
this._raycaster.setFromCamera(relativeMouseCoords, this._camera);
const intersections = this._raycaster.intersectObject(object, recursive);
this._raycaster.ray.copy(originalRay);
return intersections;
}
containerResized() {
this._containerRect = this._container.getBoundingClientRect();
}
update() {
if (!this._isReady) {
return;
}
if (this._active) {
this._updateEnterLeave();
}
}
_updateEnterLeave() {
const intersections = this._getIntersections(this._mouse);
const hoverMapToUpdate = {
...this._hoverObjectMap,
};
const mouseEnterEvent = this._createSyntheticMouseEvent('mouseEnter', {
target: this._container,
clientX: this._mouse.x,
clientY: this._mouse.y,
});
// find first intersection that does not ignore pointer events
for (let depth = 0; depth < intersections.length; ++depth) {
const intersection = intersections[depth];
const object = intersection.object;
if (object.userData && object.userData.ignorePointerEvents) {
continue;
}
const uuid = object.uuid;
if (this._hoverObjectMap[uuid]) {
delete hoverMapToUpdate[uuid];
// just update that intersection
this._hoverObjectMap[uuid].intersection = intersection;
} else {
this._hoverObjectMap[uuid] = {
object,
intersection,
};
if (!(mouseEnterEvent.isDefaultPrevented() || mouseEnterEvent.isPropagationStopped())) {
React3.eventDispatcher.dispatchEvent(object, 'onMouseEnter',
mouseEnterEvent, intersection, depth);
}
}
// we have found the first solid intersection, don't go further
break;
}
const mouseLeaveEvent = this._createSyntheticMouseEvent('mouseLeave', {
target: this._container,
clientX: this._mouse.x,
clientY: this._mouse.y,
});
// delete all unseen uuids in hover map
const unseenUUIDs = Object.keys(hoverMapToUpdate);
for (let i = 0; i < unseenUUIDs.length; ++i) {
const uuid = unseenUUIDs[i];
if (!(mouseLeaveEvent.isDefaultPrevented() || mouseLeaveEvent.isPropagationStopped())) {
React3.eventDispatcher.dispatchEvent(this._hoverObjectMap[uuid].object,
'onMouseLeave', mouseLeaveEvent);
}
delete this._hoverObjectMap[uuid];
}
}
_getRelativeMouseCoords(screenMouseCoords) {
const containerRect = this._containerRect;
const relativeMouseCoords = screenMouseCoords.clone()
.sub(tempVector2.set(containerRect.left, containerRect.top))
.divide(tempVector2.set(containerRect.width, containerRect.height));
// mouse.x = ( event.clientX / window.innerWidth ) * 2 - 1;
// mouse.y = - ( event.clientY / window.innerHeight ) * 2 + 1;
relativeMouseCoords.x = relativeMouseCoords.x * 2 - 1;
relativeMouseCoords.y = -relativeMouseCoords.y * 2 + 1;
return relativeMouseCoords;
}
// noinspection JSUnusedGlobalSymbols
dispose() {
document.removeEventListener('mousemove', this._onMouseMove, false);
this._caughtListenersCleanupFunctions.forEach(cleanupFunction => cleanupFunction());
delete this._caughtListenersCleanupFunctions;
delete this._onMouseMove;
this._patchedDescriptors.forEach(elementDescriptor => {
const allProps = Object.keys(boolProps)
.concat(mouseEvents);
allProps.forEach(propName => {
elementDescriptor.removeProp(propName);
});
});
}
}
export default MouseInput;
================================================
FILE: src/index.jsx
================================================
/* eslint-disable no-undef */
import React from 'react';
import ReactDOM from 'react-dom';
import {
HashRouter as Router,
Route,
} from 'react-router-dom';
import Perf from 'react-addons-perf';
import createHashHistory from 'history/createHashHistory';
import ExampleBrowser from './examples/ExampleBrowser';
const customHistory = createHashHistory();
window.Perf = Perf;
ReactDOM.render(
,
document.getElementById('content'),
);
================================================
FILE: src/ref/trackball.js
================================================
/* eslint-disable */
import * as THREE from 'three';
/**
* @author Eberhard Graether / http://egraether.com/
* @author Mark Lundin / http://mark-lundin.com
* @author Simone Manini / http://daron1337.github.io
* @author Luca Antiga / http://lantiga.github.io
*/
class TrackballControls extends THREE.EventDispatcher {
constructor(object, domElement) {
super();
const _this = this;
const STATE = { NONE: -1, ROTATE: 0, ZOOM: 1, PAN: 2, TOUCH_ROTATE: 3, TOUCH_ZOOM_PAN: 4 };
this.object = object;
this.domElement = ( domElement !== undefined ) ? domElement : document;
// API
this.enabled = true;
this.screen = { left: 0, top: 0, width: 0, height: 0 };
this.rotateSpeed = 1.0;
this.zoomSpeed = 1.2;
this.panSpeed = 0.3;
this.noRotate = false;
this.noZoom = false;
this.noPan = false;
this.staticMoving = false;
this.dynamicDampingFactor = 0.2;
this.minDistance = 0;
this.maxDistance = Infinity;
this.keys = [
65/* A */,
83/* S */,
68/* D */,
];
// internals
this.target = new THREE.Vector3();
const EPS = 0.000001;
const lastPosition = new THREE.Vector3();
let _state = STATE.NONE;
let _prevState = STATE.NONE;
const _eye = new THREE.Vector3();
const _movePrev = new THREE.Vector2();
const _moveCurr = new THREE.Vector2();
const _lastAxis = new THREE.Vector3();
let _lastAngle = 0;
const _zoomStart = new THREE.Vector2();
const _zoomEnd = new THREE.Vector2();
let _touchZoomDistanceStart = 0;
let _touchZoomDistanceEnd = 0;
const _panStart = new THREE.Vector2();
const _panEnd = new THREE.Vector2();
// for reset
this.target0 = this.target.clone();
this.position0 = this.object.position.clone();
this.up0 = this.object.up.clone();
// events
const changeEvent = { type: 'change' };
const startEvent = { type: 'start' };
const endEvent = { type: 'end' };
// methods
this.handleResize = () => {
if (this.domElement === document) {
this.screen.left = 0;
this.screen.top = 0;
this.screen.width = window.innerWidth;
this.screen.height = window.innerHeight;
} else {
const box = this.domElement.getBoundingClientRect();
// adjustments come from similar code in the jquery offset() function
const d = this.domElement.ownerDocument.documentElement;
this.screen.left = box.left + window.pageXOffset - d.clientLeft;
this.screen.top = box.top + window.pageYOffset - d.clientTop;
this.screen.width = box.width;
this.screen.height = box.height;
}
};
this.handleEvent = (event) => {
if (typeof this[event.type] === 'function') {
this[event.type](event);
}
};
const getMouseOnScreen = ( function wrapper() {
const vector = new THREE.Vector2();
return (pageX, pageY) => {
vector.set(
( pageX - _this.screen.left ) / _this.screen.width,
( pageY - _this.screen.top ) / _this.screen.height
);
return vector;
};
}() );
const getMouseOnCircle = ( function wrapper() {
const vector = new THREE.Vector2();
return (pageX, pageY) => {
vector.set(
( ( pageX - _this.screen.width * 0.5 - _this.screen.left ) / ( _this.screen.width * 0.5 ) ),
( ( _this.screen.height + 2 * ( _this.screen.top - pageY ) ) / _this.screen.width ) // screen.width intentional
);
return vector;
};
}() );
this.rotateCamera = ( function wrapper() {
const axis = new THREE.Vector3();
const quaternion = new THREE.Quaternion();
const eyeDirection = new THREE.Vector3();
const objectUpDirection = new THREE.Vector3();
const objectSidewaysDirection = new THREE.Vector3();
const moveDirection = new THREE.Vector3();
let angle;
return function rotateCamera() {
moveDirection.set(_moveCurr.x - _movePrev.x, _moveCurr.y - _movePrev.y, 0);
angle = moveDirection.length();
if (angle) {
_eye.copy(_this.object.position).sub(_this.target);
eyeDirection.copy(_eye).normalize();
objectUpDirection.copy(_this.object.up).normalize();
objectSidewaysDirection.crossVectors(objectUpDirection, eyeDirection).normalize();
objectUpDirection.setLength(_moveCurr.y - _movePrev.y);
objectSidewaysDirection.setLength(_moveCurr.x - _movePrev.x);
moveDirection.copy(objectUpDirection.add(objectSidewaysDirection));
axis.crossVectors(moveDirection, _eye).normalize();
angle *= _this.rotateSpeed;
quaternion.setFromAxisAngle(axis, angle);
_eye.applyQuaternion(quaternion);
_this.object.up.applyQuaternion(quaternion);
_lastAxis.copy(axis);
_lastAngle = angle;
} else if (!_this.staticMoving && _lastAngle) {
_lastAngle *= Math.sqrt(1.0 - _this.dynamicDampingFactor);
_eye.copy(_this.object.position).sub(_this.target);
quaternion.setFromAxisAngle(_lastAxis, _lastAngle);
_eye.applyQuaternion(quaternion);
_this.object.up.applyQuaternion(quaternion);
}
_movePrev.copy(_moveCurr);
};
}() );
this.zoomCamera = () => {
let factor;
if (_state === STATE.TOUCH_ZOOM_PAN) {
factor = _touchZoomDistanceStart / _touchZoomDistanceEnd;
_touchZoomDistanceStart = _touchZoomDistanceEnd;
_eye.multiplyScalar(factor);
} else {
factor = 1.0 + ( _zoomEnd.y - _zoomStart.y ) * _this.zoomSpeed;
if (factor !== 1.0 && factor > 0.0) {
_eye.multiplyScalar(factor);
if (_this.staticMoving) {
_zoomStart.copy(_zoomEnd);
} else {
_zoomStart.y += ( _zoomEnd.y - _zoomStart.y ) * this.dynamicDampingFactor;
}
}
}
};
this.panCamera = ( function wrapper() {
const mouseChange = new THREE.Vector2();
const objectUp = new THREE.Vector3();
const pan = new THREE.Vector3();
return function panCamera() {
mouseChange.copy(_panEnd).sub(_panStart);
if (mouseChange.lengthSq()) {
mouseChange.multiplyScalar(_eye.length() * _this.panSpeed);
pan.copy(_eye).cross(_this.object.up).setLength(mouseChange.x);
pan.add(objectUp.copy(_this.object.up).setLength(mouseChange.y));
_this.object.position.add(pan);
_this.target.add(pan);
if (_this.staticMoving) {
_panStart.copy(_panEnd);
} else {
_panStart.add(mouseChange.subVectors(_panEnd, _panStart).multiplyScalar(_this.dynamicDampingFactor));
}
}
};
}() );
this.checkDistances = () => {
if (!_this.noZoom || !_this.noPan) {
if (_eye.lengthSq() > _this.maxDistance * _this.maxDistance) {
_this.object.position.addVectors(_this.target, _eye.setLength(_this.maxDistance));
_zoomStart.copy(_zoomEnd);
}
if (_eye.lengthSq() < _this.minDistance * _this.minDistance) {
_this.object.position.addVectors(_this.target, _eye.setLength(_this.minDistance));
_zoomStart.copy(_zoomEnd);
}
}
};
this.update = () => {
_eye.subVectors(_this.object.position, _this.target);
if (!_this.noRotate) {
_this.rotateCamera();
}
if (!_this.noZoom) {
_this.zoomCamera();
}
if (!_this.noPan) {
_this.panCamera();
}
_this.object.position.addVectors(_this.target, _eye);
_this.checkDistances();
_this.object.lookAt(_this.target);
if (lastPosition.distanceToSquared(_this.object.position) > EPS) {
_this.dispatchEvent(changeEvent);
lastPosition.copy(_this.object.position);
}
};
this.reset = () => {
_state = STATE.NONE;
_prevState = STATE.NONE;
_this.target.copy(_this.target0);
_this.object.position.copy(_this.position0);
_this.object.up.copy(_this.up0);
_eye.subVectors(_this.object.position, _this.target);
_this.object.lookAt(_this.target);
_this.dispatchEvent(changeEvent);
lastPosition.copy(_this.object.position);
};
// listeners
function keydown(event) {
if (_this.enabled === false) return;
window.removeEventListener('keydown', keydown);
_prevState = _state;
if (_state !== STATE.NONE) {
return;
}
if (event.keyCode === _this.keys[STATE.ROTATE] && !_this.noRotate) {
_state = STATE.ROTATE;
} else if (event.keyCode === _this.keys[STATE.ZOOM] && !_this.noZoom) {
_state = STATE.ZOOM;
} else if (event.keyCode === _this.keys[STATE.PAN] && !_this.noPan) {
_state = STATE.PAN;
}
}
function keyup() {
if (_this.enabled === false) return;
_state = _prevState;
window.addEventListener('keydown', keydown, false);
}
function mousemove(event) {
if (_this.enabled === false) return;
event.preventDefault();
event.stopPropagation();
if (_state === STATE.ROTATE && !_this.noRotate) {
_movePrev.copy(_moveCurr);
_moveCurr.copy(getMouseOnCircle(event.pageX, event.pageY));
} else if (_state === STATE.ZOOM && !_this.noZoom) {
_zoomEnd.copy(getMouseOnScreen(event.pageX, event.pageY));
} else if (_state === STATE.PAN && !_this.noPan) {
_panEnd.copy(getMouseOnScreen(event.pageX, event.pageY));
}
}
function mouseup(event) {
if (_this.enabled === false) return;
event.preventDefault();
event.stopPropagation();
_state = STATE.NONE;
document.removeEventListener('mousemove', mousemove);
document.removeEventListener('mouseup', mouseup);
_this.dispatchEvent(endEvent);
}
function mousedown(event) {
if (_this.enabled === false) return;
event.preventDefault();
event.stopPropagation();
if (_state === STATE.NONE) {
_state = event.button;
}
if (_state === STATE.ROTATE && !_this.noRotate) {
_moveCurr.copy(getMouseOnCircle(event.pageX, event.pageY));
_movePrev.copy(_moveCurr);
} else if (_state === STATE.ZOOM && !_this.noZoom) {
_zoomStart.copy(getMouseOnScreen(event.pageX, event.pageY));
_zoomEnd.copy(_zoomStart);
} else if (_state === STATE.PAN && !_this.noPan) {
_panStart.copy(getMouseOnScreen(event.pageX, event.pageY));
_panEnd.copy(_panStart);
}
document.addEventListener('mousemove', mousemove, false);
document.addEventListener('mouseup', mouseup, false);
_this.dispatchEvent(startEvent);
}
function mousewheel(event) {
if (_this.enabled === false) return;
event.preventDefault();
event.stopPropagation();
let delta = 0;
if (event.wheelDelta) {
// WebKit / Opera / Explorer 9
delta = event.wheelDelta / 40;
} else if (event.detail) {
// Firefox
delta = -event.detail / 3;
}
_zoomStart.y += delta * 0.01;
_this.dispatchEvent(startEvent);
_this.dispatchEvent(endEvent);
}
function touchstart(event) {
if (_this.enabled === false) return;
switch (event.touches.length) {
case 1:
_state = STATE.TOUCH_ROTATE;
_moveCurr.copy(getMouseOnCircle(event.touches[0].pageX, event.touches[0].pageY));
_movePrev.copy(_moveCurr);
break;
case 2:
_state = STATE.TOUCH_ZOOM_PAN;
const dx = event.touches[0].pageX - event.touches[1].pageX;
const dy = event.touches[0].pageY - event.touches[1].pageY;
_touchZoomDistanceEnd = _touchZoomDistanceStart = Math.sqrt(dx * dx + dy * dy);
const x = ( event.touches[0].pageX + event.touches[1].pageX ) / 2;
const y = ( event.touches[0].pageY + event.touches[1].pageY ) / 2;
_panStart.copy(getMouseOnScreen(x, y));
_panEnd.copy(_panStart);
break;
default:
_state = STATE.NONE;
}
_this.dispatchEvent(startEvent);
}
function touchmove(event) {
if (_this.enabled === false) return;
event.preventDefault();
event.stopPropagation();
switch (event.touches.length) {
case 1:
_movePrev.copy(_moveCurr);
_moveCurr.copy(getMouseOnCircle(event.touches[0].pageX, event.touches[0].pageY));
break;
case 2:
const dx = event.touches[0].pageX - event.touches[1].pageX;
const dy = event.touches[0].pageY - event.touches[1].pageY;
_touchZoomDistanceEnd = Math.sqrt(dx * dx + dy * dy);
const x = ( event.touches[0].pageX + event.touches[1].pageX ) / 2;
const y = ( event.touches[0].pageY + event.touches[1].pageY ) / 2;
_panEnd.copy(getMouseOnScreen(x, y));
break;
default:
_state = STATE.NONE;
}
}
function touchend(event) {
if (_this.enabled === false) return;
switch (event.touches.length) {
default:
// no touches
break;
case 1:
_moveCurr.copy(getMouseOnCircle(event.touches[0].pageX, event.touches[0].pageY));
_movePrev.copy(_moveCurr);
break;
case 2:
_touchZoomDistanceStart = _touchZoomDistanceEnd = 0;
const x = ( event.touches[0].pageX + event.touches[1].pageX ) / 2;
const y = ( event.touches[0].pageY + event.touches[1].pageY ) / 2;
_panEnd.copy(getMouseOnScreen(x, y));
_panStart.copy(_panEnd);
break;
}
_state = STATE.NONE;
_this.dispatchEvent(endEvent);
}
function contextmenu(event) {
event.preventDefault();
}
this.dispose = () => {
this.domElement.removeEventListener('contextmenu', contextmenu, false);
this.domElement.removeEventListener('mousedown', mousedown, false);
this.domElement.removeEventListener('mousewheel', mousewheel, false);
this.domElement.removeEventListener('DOMMouseScroll', mousewheel, false); // firefox
this.domElement.removeEventListener('touchstart', touchstart, false);
this.domElement.removeEventListener('touchend', touchend, false);
this.domElement.removeEventListener('touchmove', touchmove, false);
document.removeEventListener('mousemove', mousemove, false);
document.removeEventListener('mouseup', mouseup, false);
window.removeEventListener('keydown', keydown, false);
window.removeEventListener('keyup', keyup, false);
};
this.domElement.addEventListener('contextmenu', contextmenu, false);
this.domElement.addEventListener('mousedown', mousedown, false);
this.domElement.addEventListener('mousewheel', mousewheel, false);
this.domElement.addEventListener('DOMMouseScroll', mousewheel, false); // firefox
this.domElement.addEventListener('touchstart', touchstart, false);
this.domElement.addEventListener('touchend', touchend, false);
this.domElement.addEventListener('touchmove', touchmove, false);
window.addEventListener('keydown', keydown, false);
window.addEventListener('keyup', keyup, false);
this.handleResize();
// force an update at start
this.update();
}
}
export default TrackballControls;
/* eslint-enable */
================================================
FILE: webpack.config.babel.js
================================================
/* eslint-disable import/no-extraneous-dependencies */
import path from 'path';
import webpack from 'webpack';
import pluginsWithoutUglify from './config/webpackPluginsWithoutUglify';
import packageJson from './package.json';
const outPath = path.join(__dirname, 'pages');
const plugins = pluginsWithoutUglify.concat([
new webpack.optimize.UglifyJsPlugin({
compress: {
warnings: false,
},
mangle: true,
}),
]);
const babelLoaderConfigShared = {
test: /\.jsx?$/,
loader: 'babel-loader',
query: {
...packageJson.babel,
cacheDirectory: true,
},
};
export default {
entry: {
app: [
'./src/index.jsx',
],
advanced: [
'./src/examples/AdvancedExample/index.js',
],
},
output: {
path: outPath,
filename: path.join('js', 'bundle-[name].js'),
},
module: {
loaders: [
{
loader: 'json-loader',
test: /\.json$/,
},
{
exclude: /node_modules/,
...babelLoaderConfigShared,
},
{
include: /react-three-renderer[\\/]src/,
...babelLoaderConfigShared,
},
],
},
resolve: {
extensions: ['.js', '.jsx'],
alias: {
// use the source files
'react-three-renderer': path.join(
__dirname, 'node_modules', 'react-three-renderer', 'src'),
},
},
devServer: {
contentBase: path.join(__dirname, 'assets'),
// noInfo: true, // --no-info option
hot: true,
inline: true,
stats: { colors: true },
},
plugins,
};