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
================================================
<!doctype html>
<html>
<head>
<style>
@font-face {
font-family: 'inconsolata';
src: url('files/inconsolata.woff') format('woff');
font-weight: normal;
font-style: normal;
}
* {
box-sizing: border-box;
}
html {
height: 100%;
}
body {
background-color: #ffffff;
margin: 0px;
height: 100%;
color: #555;
font-family: 'inconsolata';
font-size: 15px;
line-height: 18px;
overflow: hidden;
}
h1 {
margin-top: 30px;
margin-bottom: 40px;
margin-left: 20px;
font-size: 25px;
font-weight: normal;
}
h2 {
font-size: 20px;
font-weight: normal;
}
a {
color: #2194CE;
text-decoration: none;
}
#panel {
position: fixed;
left: 0px;
width: 310px;
height: 100%;
overflow: auto;
background: #fafafa;
}
#panel #content {
padding: 0px 20px;
}
#panel #content .link {
color: #2194CE;
text-decoration: none;
cursor: pointer;
}
#panel #content .selected {
color: #ff0000;
}
#panel #content .link:hover {
text-decoration: underline;
}
#viewer {
position: absolute;
border: 0px;
left: 310px;
width: calc(100% - 310px);
height: 100%;
overflow: hidden;
}
#button {
position: fixed;
bottom: 20px;
right: 20px;
padding: 8px;
color: #fff;
background-color: #555;
opacity: 0.7;
}
#button:hover {
cursor: pointer;
opacity: 1;
}
/* mobile */
#expandButton {
display: none;
position: absolute;
right: 20px;
top: 12px;
width: 32px;
height: 32px;
}
#expandButton span {
height: 2px;
background-color: #2194CE;
width: 16px;
position: absolute;
left: 8px;
top: 10px;
}
#expandButton span:nth-child(1) {
top: 16px;
}
#expandButton span:nth-child(2) {
top: 22px;
}
@media all and ( max-width: 640px ) {
h1 {
margin-top: 20px;
margin-bottom: 20px;
}
#panel {
position: absolute;
left: 0;
top: 0;
height: 480px;
width: 100%;
right: 0;
z-index: 100;
overflow: hidden;
border-bottom: 1px solid #dedede;
}
#content {
position: absolute;
left: 0;
top: 60px;
right: 0;
bottom: 0;
font-size: 17px;
line-height: 22px;
overflow: auto;
}
#viewer {
position: absolute;
left: 0;
top: 56px;
width: 100%;
height: calc(100% - 56px);
overflow: hidden,
}
#expandButton {
display: block;
}
#panel.collapsed {
height: 56px;
}
}
</style>
</head>
<body>
<div>
<div style="padding: 20px;">
<canvas id="canvas" width="800" height="600"></canvas>
<br/>
<br/>
<a href="https://github.com/toxicFork/react-three-renderer-example/blob/master/src/examples/AdvancedExample/index.js">View
Source</a>
</div>
</div>
</body>
<script type="text/javascript" charset="utf-8" src="js/bundle-commons.js"></script>
<script type="text/javascript" charset="utf-8" src="js/bundle-advanced.js"></script>
<script type="text/javascript">
var sc_project = 10775601;
var sc_invisible = 1;
var sc_security = "74e09d45";
var sc_https = 1;
var sc_remove_link = 1;
var scJsHost = (("https:" == document.location.protocol) ?
"https://secure." : "http://www.");
document.write("<sc" + "ript type='text/javascript' src='" +
scJsHost +
"statcounter.com/counter/counter.js'></" + "script>");
</script>
<noscript>
<div class="statcounter"><img class="statcounter"
src="http://c.statcounter.com/10775601/0/74e09d45/1/"
alt="stats"></div>
</noscript>
</html>
================================================
FILE: assets/index.html
================================================
<!doctype html>
<html>
<head>
<style>
@font-face {
font-family: 'inconsolata';
src: url('files/inconsolata.woff') format('woff');
font-weight: normal;
font-style: normal;
}
* {
box-sizing: border-box;
}
html {
height: 100%;
}
body {
background-color: #ffffff;
margin: 0px;
height: 100%;
color: #555;
font-family: 'inconsolata';
font-size: 15px;
line-height: 18px;
overflow: hidden;
}
h1 {
margin-top: 30px;
margin-bottom: 40px;
margin-left: 20px;
font-size: 25px;
font-weight: normal;
}
h2 {
font-size: 20px;
font-weight: normal;
}
a {
color: #2194CE;
text-decoration: none;
}
#panel {
position: fixed;
left: 0px;
width: 310px;
height: 100%;
overflow: auto;
background: #fafafa;
}
#panel #content {
padding: 0px 20px;
}
#panel #content .link {
color: #2194CE;
display: block;
text-decoration: none;
cursor: pointer;
}
#panel #content .selected {
color: #ff0000;
}
#panel #content .link:hover {
text-decoration: underline;
}
#viewer {
position: absolute;
border: 0px;
left: 310px;
width: calc(100% - 310px);
height: 100%;
overflow: hidden;
}
#button {
position: fixed;
bottom: 20px;
right: 20px;
padding: 8px;
color: #fff;
background-color: #555;
opacity: 0.7;
}
#button:hover {
cursor: pointer;
opacity: 1;
}
/* mobile */
#expandButton {
display: none;
position: absolute;
right: 20px;
top: 12px;
width: 32px;
height: 32px;
}
#expandButton span {
height: 2px;
background-color: #2194CE;
width: 16px;
position: absolute;
left: 8px;
top: 10px;
}
#expandButton span:nth-child(1) {
top: 16px;
}
#expandButton span:nth-child(2) {
top: 22px;
}
@media all and ( max-width: 640px ) {
h1 {
margin-top: 20px;
margin-bottom: 20px;
}
#panel {
position: absolute;
left: 0;
top: 0;
height: 480px;
width: 100%;
right: 0;
z-index: 100;
overflow: hidden;
border-bottom: 1px solid #dedede;
}
#content {
position: absolute;
left: 0;
top: 60px;
right: 0;
bottom: 0;
font-size: 17px;
line-height: 22px;
overflow: auto;
}
#viewer {
position: absolute;
left: 0;
top: 56px;
width: 100%;
height: calc(100% - 56px);
overflow: hidden,
}
#expandButton {
display: block;
}
#panel.collapsed {
height: 56px;
}
}
</style>
</head>
<body>
<div id="content"></div>
</body>
<script type="text/javascript" charset="utf-8" src="js/bundle-commons.js"></script>
<script type="text/javascript" charset="utf-8" src="js/bundle-app.js"></script>
<script type="text/javascript">
var sc_project = 10775601;
var sc_invisible = 1;
var sc_security = "74e09d45";
var sc_https = 1;
var sc_remove_link = 1;
var scJsHost = (("https:" == document.location.protocol) ?
"https://secure." : "http://www.");
document.write("<sc" + "ript type='text/javascript' src='" +
scJsHost +
"statcounter.com/counter/counter.js'></" + "script>");
</script>
<noscript>
<div class="statcounter"><img class="statcounter"
src="http://c.statcounter.com/10775601/0/74e09d45/1/"
alt="stats"></div>
</noscript>
</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(<react3
width={width}
height={height}
onRecreateCanvas={onRecreateCanvas}
context="3d"
antialias
mainCamera="camera"
>
<scene>
<perspectiveCamera
name="camera"
fov={75}
aspect={width / height}
near={0.1}
far={1000}
position={cameraPosition}
/>
<mesh
rotation={cubeRotation}
>
<boxGeometry
width={1}
height={1}
depth={1}
/>
<meshBasicMaterial
color={0xff0000}
/>
</mesh>
</scene>
</react3>, 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 (<parametricGeometry
ref={this._geometryRef}
parametricFunction={Cloth.clothFunction}
slices={cloth.w}
stacks={cloth.h}
dynamic
/>);
}
}
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 (<div
style={{
textAlign: 'center',
padding: 10,
zIndex: 10,
width: '100%',
position: 'absolute',
color: '#000',
}}
>
<a
href="http://threejs.org"
style={{
color: '#0080ff',
}}
>three.js</a> - Simple Cloth Simulation<br/>
Verlet integration with Constrains relaxation<br/>
Toggle: <a onClick={toggleRotate} style={linkStyle}>Camera{rotating ? '*' : null}</a> |
<span> <a onClick={toggleWind} style={linkStyle}>Wind{winding ? '*' : null}</a></span> |
<span> <a onClick={toggleSphere} style={linkStyle}>Ball{balling ? '*' : null}</a></span> |
<span> <a onClick={togglePins} style={linkStyle}>Pins</a></span> |
<span> Time between frames (ms): <input
onChange={onFrameChange}
value={minTimePerFrame}
type="number"
style={{ width: 40 }}
min="0"
/> </span>
<br/>
<span>Note: add some time between frames (e.g. 60ms)
if you would like to inspect the scene through
react devtools, because updating every frame kills the addon.</span>
</div>);
}
}
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 (<object3D>
<resources>
<boxGeometry
resourceId="poleGeometry"
width={5}
height={375}
depth={5}
/>
<boxGeometry
resourceId="boxGeometry"
width={10}
height={10}
depth={10}
/>
<meshPhongMaterial
resourceId="poleMaterial"
color={Number.parseInt(this.state.poleMaterialColor, 16)}
specular={Number.parseInt(this.state.poleMaterialSpecular, 16)}
shininess={this.state.poleMaterialShininess}
/>
</resources>
{this.state.sidePolePositions.map((position, i) =>
(<mesh
key={i}
position={position}
receiveShadow
castShadow
>
<geometryResource
resourceId="poleGeometry"
/>
<materialResource
resourceId="poleMaterial"
/>
</mesh>))}
<mesh
position={this.state.topPolePosition}
receiveShadow
castShadow
>
<boxGeometry
width={255}
height={5}
depth={5}
/>
<materialResource
resourceId="poleMaterial"
/>
</mesh>
<object3D>
{ this.state.subResource ? <resources>
{this.state.subResource ? <meshPhongMaterial
resourceId="poleMaterial"
color={0x00ff00}
specular={0x111111}
shininess={100}
/> : null}
{
<sphereGeometry
resourceId="boxGeometry"
radius={20}
/> }
</resources> : null }
{this.state.boxPositions.map((position, i) =>
(<mesh
key={i}
position={position}
receiveShadow
castShadow
>
<geometryResource
resourceId="boxGeometry"
/>
<materialResource
resourceId="poleMaterial"
/>
</mesh>))}
</object3D>
</object3D>);
}
}
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 (<mesh
castShadow
receiveShadow
visible={visible}
position={position}
>
<sphereGeometry
radius={ballSize}
widthSegments={20}
heightSegments={20}
/>
<meshPhongMaterial
color={Number.parseInt(this.state.color, 16)}
/>
</mesh>);
}
}
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 (<object3D>
<resources>
<texture
url="textures/patterns/circuit_pattern.png"
wrapS={THREE.RepeatWrapping}
wrapT={THREE.RepeatWrapping}
anisotropy={16}
resourceId="clothTexture"
/>
</resources>
<ambientLight
color={Number.parseInt(ambientLightColor, 16)}
/>
<directionalLight
color={Number.parseInt(directionalLightColor, 16)}
intensity={1.75}
position={this.directionalLightPosition}
lookAt={this.lightTarget}
castShadow
shadowMapWidth={1024}
shadowMapHeight={1024}
shadowCameraLeft={-shadowCameraSize}
shadowCameraRight={shadowCameraSize}
shadowCameraTop={shadowCameraSize}
shadowCameraBottom={-shadowCameraSize}
shadowCameraFar={1000}
/>
<mesh
castShadow
receiveShadow
>
<ClothGeometry
ref={this.props.clothRef}
cloth={this.props.cloth}
/>
<meshPhongMaterial
alphaTest={0.5}
color={0xffffff}
specular={0x030303}
emissive={0x111111}
shininess={10}
side={THREE.DoubleSide}
>
<textureResource
resourceId="clothTexture"
/>
</meshPhongMaterial>
<shaderMaterial
slot="customDepthMaterial"
fragmentShader={frag}
vertexShader={vert}
>
<uniforms>
<uniform
name="texture"
type="t"
>
<textureResource
resourceId="clothTexture"
/>
</uniform>
</uniforms>
</shaderMaterial>
</mesh>
{ /* <arrowHelper
direction={this.arrowDirection}
origin={this.arrowOrigin}
length={this.arrowLength}
color={0xff0000}
position={this.arrowPosition}
/> */ }
<mesh
position={this.groundPosition}
rotation={this.groundRotation}
receiveShadow
>
<planeBufferGeometry
width={20000}
height={20000}
/>
<meshPhongMaterial
color={0xffffff}
specular={0x111111}
>
<texture
url="textures/terrain/grasslight-big.jpg"
wrapS={THREE.RepeatWrapping}
wrapT={THREE.RepeatWrapping}
repeat={this.groundRepeat}
anisotropy={16}
/>
</meshPhongMaterial>
</mesh>
<Poles/>
</object3D>);
}
}
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 (<div ref={this._containerRef}>
<Info
toggleRotate={this._toggleRotate}
toggleWind={this._toggleWind}
toggleSphere={this._toggleSphere}
togglePins={this._togglePins}
rotating={this.state.rotate}
winding={this.state.wind}
balling={this.state.sphere}
onFrameChange={this._onFrameChange}
minTimePerFrame={minTimePerFrame}
/>
<React3
canvasRef={this._react3Ref}
width={width}
height={height}
antialias
pixelRatio={window.devicePixelRatio}
clearColor={this.fog.color}
gammaInput
gammaOutput
shadowMapEnabled
shadowMapDebug
mainCamera="mainCamera"
onAnimate={this._onAnimate}
>
<scene fog={this.fog}>
<perspectiveCamera
name="mainCamera"
fov={30}
aspect={width / height}
ref={this._mainCameraRef}
position={this.state.cameraPosition}
near={1}
far={10000}
lookAt={this.state.rotate ? this.scenePosition : null}
/>
<StaticWorld
clothRef={this._clothRef}
cloth={this.cloth}
/>
<Sphere
ref={this._sphereRef}
visible={this.state.sphere}
position={this.state.ballPosition}
/>
</scene>
</React3>
</div>);
}
}
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 (<mesh
position={position}
quaternion={quaternion}
scale={meshScale}
castShadow
>
<geometryResource
resourceId="cubeGeo"
/>
<materialResource
resourceId="cubeMaterial"
/>
</mesh>);
}
}
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 (<div
style={{
position: 'absolute',
top: 0,
color: 'white',
width: '100%',
textAlign: 'center',
background: 'rgba(1,1,1,0.75)',
}}
>
<div>{title}</div>
<label>Bodies: <select
value={numBodies}
onChange={this._onBodiesSelectChange}
>
{[10, 50, 100, 200, 300, 500, 1000, 1500, 2000, 2500, 3000]
.map(val => <option value={val} key={val}>{val}</option>)}
</select>
</label>
</div>);
}
render() {
const {
width,
height,
} = this.props;
const {
meshStates,
} = this.state;
const d = 20;
const cubeMeshes = meshStates.map(({ position, quaternion }, i) =>
(<RotatingCube
key={i}
position={position}
quaternion={quaternion}
bodyIndex={i}
meshes={this.meshes}
/>));
return (<div
ref="container"
>
{this._getInputBox('Rotating Cubes - Through React')}
<React3
antialias
mainCamera="camera"
width={width}
height={height}
onAnimate={this._onAnimate}
clearColor={this.fog.color}
gammaInput
gammaOutput
shadowMapEnabled
>
<resources>
<boxGeometry
resourceId="cubeGeo"
width={0.5}
height={0.5}
depth={0.5}
widthSegments={10}
heightSegments={10}
/>
<meshPhongMaterial
resourceId="cubeMaterial"
color={0x888888}
/>
</resources>
<scene
ref="scene"
fog={this.fog}
>
<perspectiveCamera
name="camera"
fov={30}
aspect={width / height}
near={0.5}
far={10000}
position={this.cameraPosition}
quaternion={this.cameraQuaternion}
ref="camera"
/>
<ambientLight
color={0x666666}
/>
<directionalLight
color={0xffffff}
intensity={1.75}
castShadow
shadowMapWidth={1024}
shadowMapHeight={1024}
shadowCameraLeft={-d}
shadowCameraRight={d}
shadowCameraTop={d}
shadowCameraBottom={-d}
shadowCameraFar={3 * d}
shadowCameraNear={d}
position={this.lightPosition}
lookAt={this.lightTarget}
/>
<mesh
castShadow
receiveShadow
quaternion={this.groundQuaternion}
>
<planeBufferGeometry
width={100}
height={100}
widthSegments={1}
heightSegments={1}
/>
<meshLambertMaterial
color={0x777777}
/>
</mesh>
{cubeMeshes}
</scene>
</React3>
</div>);
}
}
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) => (<RotatingCube
key={i}
position={position}
quaternion={quaternion}
ref={ref}
meshes={this.meshes}
/>));
return (<div
ref="container"
>
{this._getInputBox('Rotating Cubes - Direct Updates')}
<React3
antialias
mainCamera="camera"
width={width}
height={height}
onAnimate={this._onAnimate}
clearColor={this.fog.color}
gammaInput
gammaOutput
shadowMapEnabled
>
<resources>
<boxGeometry
resourceId="cubeGeo"
width={0.5}
height={0.5}
depth={0.5}
widthSegments={10}
heightSegments={10}
/>
<meshPhongMaterial
resourceId="cubeMaterial"
color={0x888888}
/>
</resources>
<scene
ref="scene"
fog={this.fog}
>
<perspectiveCamera
name="camera"
fov={30}
aspect={width / height}
near={0.5}
far={10000}
position={this.cameraPosition}
quaternion={this.cameraQuaternion}
ref="camera"
/>
<ambientLight
color={0x666666}
/>
<directionalLight
color={0xffffff}
intensity={1.75}
castShadow
shadowMapWidth={1024}
shadowMapHeight={1024}
shadowCameraLeft={-d}
shadowCameraRight={d}
shadowCameraTop={d}
shadowCameraBottom={-d}
shadowCameraFar={3 * d}
shadowCameraNear={d}
position={this.lightPosition}
lookAt={this.lightTarget}
/>
<mesh
castShadow
receiveShadow
quaternion={this.groundQuaternion}
>
<planeBufferGeometry
width={100}
height={100}
widthSegments={1}
heightSegments={1}
/>
<meshLambertMaterial
color={0x777777}
/>
</mesh>
{cubeMeshes}
</scene>
</React3>
</div>);
}
}
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 (<group>
{this.cubePositions.map((cubePosition, index) => {
const onCreate = this._onCubeCreate.bind(this, index);
return (<DraggableCube
key={index}
mouseInput={mouseInput}
camera={camera}
initialPosition={cubePosition}
onCreate={onCreate}
onMouseEnter={this._onCubeMouseEnter}
onMouseLeave={this._onCubeMouseLeave}
onDragStart={this._onCubeDragStart}
onDragEnd={this._onCubeDragEnd}
cursor={cursor}
/>);
})}
</group>);
}
}
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 (<group
position={position}
rotation={rotation}
scale={scale}
>
<mesh
castShadow
receiveShadow
onMouseEnter={this._onMouseEnter}
onMouseDown={this._onMouseDown}
onMouseLeave={this._onMouseLeave}
ref={this._ref}
>
<geometryResource
resourceId="boxGeometry"
/>
<meshLambertMaterial
color={color}
/>
</mesh>
{hoverHighlight ? <mesh
ignorePointerEvents
>
<geometryResource
resourceId="boxGeometry"
/>
<materialResource
resourceId="highlightMaterial"
/>
</mesh> : null}
</group>);
}
}
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 (<div
ref="container"
style={style}
>
<React3
width={width}
height={height}
antialias
pixelRatio={window.devicePixelRatio}
mainCamera="mainCamera"
onAnimate={this._onAnimate}
sortObjects={false}
shadowMapEnabled
shadowMapType={THREE.PCFShadowMap}
clearColor={0xf0f0f0}
>
<module
ref="mouseInput"
descriptor={MouseInput}
/>
<resources>
<boxGeometry
resourceId="boxGeometry"
width={40}
height={40}
depth={40}
/>
<meshBasicMaterial
resourceId="highlightMaterial"
color={0xffff00}
wireframe
/>
</resources>
<scene ref="scene">
<perspectiveCamera
fov={70}
aspect={width / height}
near={1}
far={10000}
name="mainCamera"
ref="camera"
position={cameraPosition}
rotation={cameraRotation}
/>
<ambientLight
color={0x505050}
/>
<spotLight
color={0xffffff}
intensity={1.5}
position={this.lightPosition}
lookAt={this.lightTarget}
castShadow
shadowCameraNear={200}
shadowCameraFar={10000}
shadowCameraFov={50}
shadowBias={-0.00022}
shadowMapWidth={2048}
shadowMapHeight={2048}
/>
<AllCubes
mouseInput={mouseInput}
camera={camera}
onCubesMounted={this._onCubesMounted}
onHoverStart={this._onHoverStart}
onHoverEnd={this._onHoverEnd}
onDragStart={this._onDragStart}
onDragEnd={this._onDragEnd}
cursor={this._cursor}
/>
</scene>
</React3>
</div>);
}
}
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 (
<div>
<div id="panel" className="collapsed">
<h1><a href="https://github.com/toxicFork/react-three-renderer/">react-three-renderer</a> / examples</h1>
<div id="content">
<div>
<h2>webgl</h2>
{examples.map((example, index) => {
if (example.separator) {
return (<h2 key={index}>{example.name}</h2>);
}
if (example.advanced) {
return (<div key={index}>
<a href={example.page} target="blank">{example.name}</a> (new tab)
</div>);
}
return (<NavLink
to={`/${example.slug}`}
key={index}
className="link"
activeClassName="selected"
>
{example.name}
</NavLink>);
})}
</div>
</div>
</div>
<ExampleViewer example={activeExample} />
</div>
);
};
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 = (<ExampleComponent width={size.width} height={size.height} />);
sourceButton = (<div key="src" id="button">
<a
href={`https://github.com/toxicFork/react-three-renderer-example/blob/master/src/examples/${url}.js`}
target="_blank"
>
View source
</a>
</div>);
}
return (
<div id="viewer">
{exampleContent}
{sourceButton}
</div>
);
};
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 (<div ref="container">
<React3
width={width}
height={height}
antialias
pixelRatio={window.devicePixelRatio}
mainCamera="mainCamera"
onAnimate={this._onAnimate}
>
<resources>
<texture
resourceId="texture"
url="textures/UV_Grid_Sm.jpg"
wrapS={THREE.RepeatWrapping}
wrapT={THREE.RepeatWrapping}
anisotropy={16}
/>
<meshLambertMaterial
resourceId="material"
side={THREE.DoubleSide}
>
<textureResource
resourceId="texture"
/>
</meshLambertMaterial>
</resources>
<scene>
<perspectiveCamera
fov={45}
aspect={width / height}
near={1}
far={2000}
lookAt={this.scenePosition}
name="mainCamera"
position={new THREE.Vector3(
Math.cos(timer) * 800,
400,
Math.sin(timer) * 800
)}
/>
<ambientLight
color={0x404040}
/>
<directionalLight
color={0xffffff}
position={this.directionalLightPosition}
lookAt={this.scenePosition}
/>
<mesh
position={this.objectPositions[0]}
rotation={objectRotation}
>
<sphereGeometry
radius={75}
widthSegments={20}
heightSegments={10}
/>
<materialResource
resourceId="material"
/>
</mesh>
<mesh
position={this.objectPositions[1]}
rotation={objectRotation}
>
<icosahedronGeometry
radius={75}
detail={1}
/>
<materialResource
resourceId="material"
/>
</mesh>
<mesh
position={this.objectPositions[2]}
rotation={objectRotation}
>
<octahedronGeometry
radius={75}
detail={2}
/>
<materialResource
resourceId="material"
/>
</mesh>
<mesh
position={this.objectPositions[3]}
rotation={objectRotation}
>
<tetrahedronGeometry
radius={75}
detail={0}
/>
<materialResource
resourceId="material"
/>
</mesh>
<mesh
position={this.objectPositions[4]}
rotation={objectRotation}
>
<planeBufferGeometry
width={100}
height={100}
widthSegments={4}
heightSegments={4}
/>
<materialResource
resourceId="material"
/>
</mesh>
<mesh
position={this.objectPositions[5]}
rotation={objectRotation}
>
<boxGeometry
width={100}
height={100}
depth={100}
widthSegments={4}
heightSegments={4}
depthSegments={4}
/>
<materialResource
resourceId="material"
/>
</mesh>
<mesh
position={this.objectPositions[6]}
rotation={objectRotation}
>
<circleGeometry
radius={50}
segments={20}
thetaStart={0}
thetaLength={Math.PI * 2}
/>
<materialResource
resourceId="material"
/>
</mesh>
<mesh
position={this.objectPositions[7]}
rotation={objectRotation}
>
<ringGeometry
innerRadius={10}
outerRadius={50}
thetaSegments={20}
phiSegments={5}
thetaStart={0}
thetaLength={Math.PI * 2}
/>
<materialResource
resourceId="material"
/>
</mesh>
<mesh
position={this.objectPositions[8]}
rotation={objectRotation}
>
<cylinderGeometry
radiusTop={25}
radiusBottom={75}
height={100}
radialSegments={40}
heightSegments={5}
/>
<materialResource
resourceId="material"
/>
</mesh>
<mesh
position={this.objectPositions[9]}
rotation={objectRotation}
>
<latheGeometry
points={this.lathePoints}
segments={20}
/>
<materialResource
resourceId="material"
/>
</mesh>
<mesh
position={this.objectPositions[10]}
rotation={objectRotation}
>
<torusGeometry
radius={50}
tube={20}
radialSegments={20}
tubularSegments={20}
/>
<materialResource
resourceId="material"
/>
</mesh>
<mesh
position={this.objectPositions[11]}
rotation={objectRotation}
>
<torusKnotGeometry
radius={50}
tube={10}
radialSegments={50}
tubularSegments={20}
/>
<materialResource
resourceId="material"
/>
</mesh>
<axisHelper
position={this.objectPositions[12]}
size={50}
rotation={objectRotation}
/>
<arrowHelper
dir={this.arrowDir}
origin={this.arrowOrigin}
length={50}
position={this.objectPositions[13]}
rotation={objectRotation}
/>
</scene>
</React3>
</div>);
}
}
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 (<shape resourceId={resourceId}>
<moveTo
x={0}
y={0}
/>
<lineTo
x={0}
y={width}
/>
<lineTo
x={length}
y={width}
/>
<lineTo
x={length}
y={0}
/>
<lineTo
x={0}
y={0}
/>
</shape>);
}
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 (
<resources>
<texture
resourceId="texture"
url="textures/UV_Grid_Sm.jpg"
wrapS={THREE.RepeatWrapping}
wrapT={THREE.RepeatWrapping}
repeat={this.textureRepeat}
/>
<meshBasicMaterial
resourceId="hoverMaterial"
color={0xff0000}
side={THREE.DoubleSide}
/>
<meshPhongMaterial
resourceId="phongMaterial"
side={THREE.DoubleSide}
>
<textureResource
resourceId="texture"
/>
</meshPhongMaterial>
<shape
resourceId="california"
points={this.californiaPts}
/>
<shape resourceId="triangle">
<moveTo
x={80}
y={20}
/>
<lineTo
x={40}
y={80}
/>
<lineTo
x={120}
y={80}
/>
<lineTo
x={80}
y={20}
/>
</shape>
<shape resourceId="heart">
<moveTo
x={x + 25}
y={y + 25}
/>
<bezierCurveTo
cp1X={x + 25}
cp1Y={y + 25}
cp2X={x + 20}
cp2Y={y}
aX={x}
aY={y}
/>
<bezierCurveTo
cp1X={x - 30}
cp1Y={y}
cp2X={x - 30}
cp2Y={y + 35}
aX={x - 30}
aY={y + 35}
/>
<bezierCurveTo
cp1X={x - 30}
cp1Y={y + 55}
cp2X={x - 10}
cp2Y={y + 77}
aX={x + 25}
aY={y + 95}
/>
<bezierCurveTo
cp1X={x + 60}
cp1Y={y + 77}
cp2X={x + 80}
cp2Y={y + 55}
aX={x + 80}
aY={y + 35}
/>
<bezierCurveTo
cp1X={ x + 80}
cp1Y={y + 35}
cp2X={x + 80}
cp2Y={y}
aX={x + 50}
aY={y}
/>
<bezierCurveTo
cp1X={x + 35}
cp1Y={y}
cp2X={x + 25}
cp2Y={y + 25}
aX={x + 25}
aY={y + 25}
/>
</shape>
<Rect
resourceId="square"
width={sqLength}
length={sqLength}
/>
<Rect
resourceId="rect"
width={rectWidth}
length={rectLength}
/>
{((function roundedRect(rectX, rectY,
roundedRectWidth, roundedRectHeight,
radius) {
return (<shape resourceId="roundedRect">
<moveTo
x={rectX}
y={rectY + radius}
/>
<lineTo
x={rectX}
y={rectY + roundedRectHeight - radius}
/>
<quadraticCurveTo
cpX={rectX}
cpY={rectY + roundedRectHeight}
x={rectX + radius}
y={rectY + roundedRectHeight}
/>
<lineTo
x={rectX + roundedRectWidth - radius}
y={rectY + roundedRectHeight}
/>
<quadraticCurveTo
cpX={rectX + roundedRectWidth}
cpY={rectY + roundedRectHeight}
x={rectX + roundedRectWidth}
y={rectY + roundedRectHeight - radius}
/>
<lineTo
x={rectX + roundedRectWidth}
y={rectY + radius}
/>
<quadraticCurveTo
cpX={rectX + roundedRectWidth}
cpY={rectY}
x={rectX + roundedRectWidth - radius}
y={rectY}
/>
<lineTo
x={rectX + radius}
y={rectY}
/>
<quadraticCurveTo
cpX={rectX}
cpY={rectY}
x={rectX}
y={rectY + radius}
/>
</shape>);
})(0, 0, 50, 50, 20))}
<shape
resourceId="track"
>
<moveTo
x={40}
y={40}
/>
<lineTo
x={40}
y={160}
/>
<absArc
x={60}
y={160}
radius={20}
startAngle={Math.PI}
endAngle={0}
clockwise
/>
<lineTo
x={80}
y={40}
/>
<absArc
x={60}
y={40}
radius={20}
startAngle={2 * Math.PI}
endAngle={Math.PI}
clockwise
/>
</shape>
{((function circleShape() {
const circleRadius = 40;
return (<shape resourceId="circle">
<moveTo
x={0}
y={circleRadius}
/>
<quadraticCurveTo
cpX={circleRadius}
cpY={circleRadius}
x={circleRadius}
y={0}
/>
<quadraticCurveTo
cpX={circleRadius}
cpY={-circleRadius}
x={0}
y={-circleRadius}
/>
<quadraticCurveTo
cpX={-circleRadius}
cpY={-circleRadius}
x={-circleRadius}
y={0}
/>
<quadraticCurveTo
cpX={-circleRadius}
cpY={circleRadius}
x={0}
y={circleRadius}
/>
</shape>);
})())}
<shape resourceId="arc">
<moveTo
x={50}
y={10}
/>
<absArc
x={10}
y={10}
radius={40}
startAngle={0}
endAngle={Math.PI * 2}
clockwise={false}
/>
<hole>
<moveTo
x={20}
y={10}
/>
<absArc
x={10}
y={10}
radius={10}
startAngle={0}
endAngle={Math.PI * 2}
clockwise
/>
</hole>
</shape>
<shape resourceId="fish">
<moveTo
x={x}
y={y}
/>
<quadraticCurveTo
cpX={x + 50}
cpY={y - 80}
x={x + 90}
y={y - 10}
/>
<quadraticCurveTo
cpX={x + 100}
cpY={y - 10}
x={x + 115}
y={y - 40}
/>
<quadraticCurveTo
cpX={x + 115}
cpY={y}
x={x + 115}
y={y + 40}
/>
<quadraticCurveTo
cpX={x + 100}
cpY={y + 10}
x={x + 90}
y={y + 10}
/>
<quadraticCurveTo
cpX={x + 50}
cpY={y + 80}
x={x}
y={y}
/>
</shape>
<shape resourceId="smiley">
<moveTo
x={80}
y={40}
/>
<absArc
x={40}
y={40}
radius={40}
startAngle={0}
endAngle={Math.PI * 2}
clockwise={false}
/>
<hole key="eye1">
<moveTo
x={35}
y={20}
/>
<absEllipse
x={25}
y={20}
xRadius={10}
yRadius={10}
startAngle={0}
endAngle={Math.PI * 2}
clockwise
/>
</hole>
<hole key="eye2">
<moveTo
x={65}
y={20}
/>
<absArc
x={55}
y={20}
radius={10}
startAngle={0}
endAngle={Math.PI * 2}
clockwise
/>
</hole>
<hole key="mouth">
<moveTo
x={20}
y={40}
/>
<quadraticCurveTo
cpX={40}
cpY={60}
x={60}
y={40}
/>
<bezierCurveTo
cp1X={70}
cp1Y={45}
cp2X={70}
cp2Y={50}
aX={60}
aY={60}
/>
<quadraticCurveTo
cpX={40}
cpY={80}
x={20}
y={60}
/>
<quadraticCurveTo
cpX={5}
cpY={50}
x={20}
y={40}
/>
</hole>
</shape>
{((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 (<shape resourceId="spline">
<moveTo
x={0}
y={0}
/>
<splineThru
points={splinePoints}
/>
</shape>);
})())}
</resources>);
}
}
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 (<group>
<mesh
// flat shape with texture
position={new THREE.Vector3(x, y, z - 175)}
rotation={rotation}
scale={scale}
>
<shapeGeometryResource
resourceId={resourceId}
type="shape"
/>
<materialResource
resourceId={'phongMaterial'}
/>
</mesh>
<mesh
// flat shape
position={new THREE.Vector3(x, y, z - 125)}
rotation={rotation}
scale={scale}
>
<shapeGeometryResource
resourceId={resourceId}
type="shape"
/>
<meshPhongMaterial
color={color}
side={THREE.DoubleSide}
/>
</mesh>
<mesh
// 3d shape
position={new THREE.Vector3(x, y, z - 75)}
rotation={rotation}
scale={scale}
>
<extrudeGeometry
settings={extrudeSettings}
>
<shapeResource
resourceId={resourceId}
/>
</extrudeGeometry>
<meshPhongMaterial
color={color}
/>
</mesh>
<line
// solid line
position={new THREE.Vector3(x, y, z - 25)}
rotation={rotation}
scale={scale}
>
<shapeGeometryResource
resourceId={resourceId}
type="points"
/>
<lineBasicMaterial
color={color}
// wireframe
/>
</line>
<points
// vertices from real points
position={new THREE.Vector3(x, y, z + 25)}
rotation={rotation}
scale={scale}
>
<shapeGeometryResource
resourceId={resourceId}
type="points"
/>
<pointsMaterial
color={color}
size={4}
// wireframe
/>
</points>
<line
// line from equidistance sampled points
position={new THREE.Vector3(x, y, z + 75)}
rotation={rotation}
scale={scale}
>
<shapeGeometryResource
resourceId={resourceId}
type="spacedPoints"
divisions={50}
/>
<lineBasicMaterial
color={color}
linewidth={3}
// wireframe
/>
</line>
<points
// equidistance sampled points
position={new THREE.Vector3(x, y, z + 125)}
rotation={rotation}
scale={scale}
>
<shapeGeometryResource
resourceId={resourceId}
type="spacedPoints"
divisions={50}
/>
<pointsMaterial
color={color}
size={4}
// wireframe
/>
</points>
</group>);
}
}
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 (<group>
<Shape
resourceId="california"
x={-300}
y={-100}
z={0}
color={0xf08000}
rx={0}
ry={0}
rz={0}
s={1}
/>
<Shape
resourceId="triangle"
color={0x8080f0}
x={-180}
y={0}
z={0}
rx={0}
ry={0}
rz={0}
s={1}
/>
<Shape
resourceId="roundedRect"
x={-150}
y={150}
z={0}
color={0x008000}
rx={0}
ry={0}
rz={0}
s={1}
/>
<Shape
resourceId="track"
x={200}
y={-100}
z={0}
color={0x008080}
rx={0}
ry={0}
rz={0}
s={1}
/>
<Shape
resourceId="square"
color={0x0040f0}
x={150}
y={100}
z={0}
rx={0}
ry={0}
rz={0}
s={1}
/>
<Shape
resourceId="heart"
color={0xf00000}
x={60}
y={100}
z={0}
rx={0}
ry={0}
rz={Math.PI}
s={1}
/>
<Shape
resourceId="circle"
color={0x00f000}
x={120}
y={250}
z={0}
rx={0}
ry={0}
rz={0}
s={1}
/>
<Shape
resourceId="fish"
color={0x404040}
x={-60}
y={200}
z={0}
rx={0}
ry={0}
rz={0}
s={1}
/>
<Shape
resourceId="smiley"
x={-200}
y={250}
z={0}
color={0xf000f0}
rx={0}
ry={0}
rz={Math.PI}
s={1}
/>
<Shape
resourceId="arc"
color={0x804000}
x={150}
y={0}
z={0}
rx={0}
ry={0}
rz={0}
s={1}
/>
<Shape
resourceId="spline"
color={0x808080}
x={-50}
y={-100}
z={0}
rx={0}
ry={0}
rz={0}
s={1}
/>
</group>);
}
}
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 (<div ref="container">
<div
style={{
color: 'black',
position: 'absolute',
top: '10px',
width: '100%',
textAlign: 'center',
}}
>
Simple procedurally generated 3D shapes<br/>
Drag to spin
</div>
<React3
width={width}
height={height}
antialias
pixelRatio={window.devicePixelRatio}
mainCamera="mainCamera"
clearColor={0xf0f0f0}
onAnimate={this._onAnimate}
>
<scene ref="scene">
<perspectiveCamera
name="mainCamera"
ref="camera"
fov={50}
aspect={width / height}
near={1}
far={1000}
position={this.cameraPosition}
>
<pointLight
color={0xffffff}
intensity={0.8}
/>
</perspectiveCamera>
<Resources/>
<group
position={this.groupPosition}
rotation={groupRotation}
>
<Shapes/>
</group>
</scene>
</React3>
</div>);
}
}
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 (<div
style={{
position: 'absolute',
textAlign: 'center',
top: 0,
width: '100%',
padding: 5,
color: 'white',
zIndex: 100,
}}
>
<h2>Manual rendering</h2>
If automatic rendering is off, the canvas will re-render only
when you press the "Trigger render" button.<br/>
This way you can save battery life
or have finer controls for rendering.<br/>
Check your CPU profiler with automatic rendering on/off :)<br/>
<br/>
<button onClick={this._onColorButtonPress}>Update cube color state</button>
<span
style={{
width: 15,
minHeight: 15,
marginLeft: 5,
display: 'inline-block',
background: `rgb(
${(cubeColor >> 16 & 255)},
${(cubeColor >> 8 & 255)},
${(cubeColor & 255)})`,
}}
> </span>
<br/>
<button
onClick={this._onTriggerPress}
style={triggerButtonStyle}
disabled={!forceManual}
>{colorButtonPressed && forceManual ? 'TRIGGER RENDER' : 'Trigger render'}
</button>
<br/>
<button onClick={this._manualButtonPress}>
Turn automatic rendering {forceManual ? 'ON' : 'OFF'}
</button>
</div>);
}
}
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 (<div>
<div>
<Info
onUpdateColorButtonPress={this._onUpdateColorButtonPress}
onRenderButtonPress={this._onRenderButtonPress}
onManualButtonPress={this._onManualButtonPress}
forceManual={forceManual}
cubeColor={cubeColor}
/>
</div>
<React3
width={width}
height={height}
forceManualRender={forceManual}
onManualRenderTriggerCreated={this._onManualRenderTriggerCreated}
mainCamera="camera"
>
<scene>
<perspectiveCamera
name="camera"
fov={75}
aspect={width / height}
near={0.1}
far={1000}
position={this.cameraPosition}
/>
<mesh>
<boxGeometry
width={1}
height={1}
depth={1}
/>
<meshBasicMaterial
color={cubeColor}
/>
</mesh>
</scene>
</React3></div>);
}
}
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 (<div
ref="container"
onMouseDown={this._onMouseDown}
><React3
antialias
mainCamera="camera"
width={width}
height={height}
onAnimate={this._onAnimate}
>
<scene>
<perspectiveCamera
name="camera"
fov={75}
aspect={width / height}
near={1}
far={100}
position={this.cameraPosition}
/>
<mesh
position={meshPosition}
quaternion={meshQuaternion}
>
<boxGeometry
width={2}
height={2}
depth={2}
/>
<meshBasicMaterial
color={0x00ff00}
wireframe
/>
</mesh>
</scene>
</React3>
</div>);
}
}
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 (<mesh
position={position}
quaternion={quaternion}
ref="mesh"
castShadow
onMouseDown={this._onMouseDown}
>
<geometryResource
resourceId="cubeGeo"
/>
<materialResource
resourceId="cubeMaterial"
/>
</mesh>);
}
}
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) =>
(<PickableMesh
key={i}
position={position}
quaternion={quaternion}
bodyIndex={i}
meshes={this.meshes}
onMouseDown={this._onMeshMouseDown}
/>));
return (<div
ref="container"
>
<React3
antialias
mainCamera="camera"
width={width}
height={height}
onAnimate={this._onAnimate}
clearColor={this.fog.color}
gammaInput
gammaOutput
shadowMapEnabled
>
<module
ref="mouseInput"
descriptor={MouseInput}
/>
<resources>
<boxGeometry
resourceId="cubeGeo"
width={0.5}
height={0.5}
depth={0.5}
widthSegments={10}
heightSegments={10}
/>
<meshPhongMaterial
resourceId="cubeMaterial"
color={0x888888}
/>
</resources>
<scene
ref="scene"
fog={this.fog}
>
<perspectiveCamera
name="camera"
fov={30}
aspect={width / height}
near={0.5}
far={10000}
position={this.cameraPosition}
quaternion={this.cameraQuaternion}
ref="camera"
/>
<ambientLight
color={0x666666}
/>
<directionalLight
color={0xffffff}
intensity={1.75}
castShadow
shadowMapWidth={1024}
shadowMapHeight={1024}
shadowCameraLeft={-d}
shadowCameraRight={d}
shadowCameraTop={d}
shadowCameraBottom={-d}
shadowCameraFar={3 * d}
shadowCameraNear={d}
position={this.lightPosition}
lookAt={this.lightTarget}
/>
<mesh
castShadow
receiveShadow
quaternion={this.groundQuaternion}
>
<planeBufferGeometry
width={100}
height={100}
widthSegments={1}
heightSegments={1}
/>
<meshLambertMaterial
color={0x777777}
/>
</mesh>
{cubeMeshes}
<mesh // click marker
visible={clickMarkerVisible}
position={clickMarkerPosition}
>
<sphereGeometry
radius={0.2}
widthSegments={8}
heightSegments={8}
/>
<meshLambertMaterial
color={0x772211}
/>
</mesh>
</scene>
</React3>
</div>);
}
}
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 (<React3
mainCamera="camera" // this points to the perspectiveCamera below
width={width}
height={height}
onAnimate={this._onAnimate}
>
<scene>
<perspectiveCamera
name="camera"
fov={75}
aspect={width / height}
near={0.1}
far={1000}
position={this.cameraPosition}
/>
<mesh
rotation={this.state.cubeRotation}
>
<boxGeometry
width={1}
height={1}
depth={1}
/>
<meshBasicMaterial
color={0x00ff00}
/>
</mesh>
</scene>
</React3>);
}
}
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 (<div
style={{
position: 'absolute',
textAlign: 'center',
top: 0,
width: '100%',
padding: 5,
color: 'white',
zIndex: 100,
}}
>
<a
href="http://threejs.org"
style={{
color: '#0080ff',
}}
>three.js</a> - cameras<br/>
<b
style={{
color: 'lightgreen',
}}
>O</b> orthographic
<b
style={{
color: 'lightgreen',
}}
>P</b> perspective <br/>
<button onClick={this.props.pause}>Pause</button>
<button onClick={this.props.frame}>Frame</button>
</div>);
}
}
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 (<points>
<geometry vertices={this.pointCloudVertices}/>
<pointsMaterial
color={0x888888}
/>
</points>);
}
}
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 (<div>
<Info
pause={this._pause}
frame={this._frame}
/>
<React3
ref="react3"
width={width}
height={height}
antialias
onAnimate={this._onAnimate}
>
<viewport
x={0}
y={0}
width={width / 2}
height={height}
cameraName={this.state.activeCameraName}
/>
<viewport
x={width / 2}
y={0}
width={width / 2}
height={height}
cameraName={mainCameraName}
/>
<scene>
<perspectiveCamera
ref="mainCamera"
name={mainCameraName}
fov={50}
aspect={aspectRatio}
near={1}
far={10000}
position={this.state.mainCameraPosition}
/>
<object3D
lookAt={meshPosition}
>
<perspectiveCamera
name={perspectiveCameraName}
fov={35 + 30 * Math.sin(0.5 * r)}
aspect={aspectRatio}
near={150}
far={meshPosition.length()}
rotation={perspectiveCameraRotation}
/>
<orthographicCamera
name={orthographicCameraName}
left={0.5 * width / -2}
right={0.5 * width / 2}
top={height / 2}
bottom={height / -2}
near={150}
far={meshPosition.length()}
rotation={orthographicCameraRotation}
/>
<mesh
position={spherePosition}
>
<sphereGeometry
radius={5}
widthSegments={16}
heightSegments={8}
/>
<meshBasicMaterial
color={0x0000ff}
wireframe
/>
</mesh>
</object3D>
<cameraHelper
cameraName={this.state.activeCameraName}
/>
<object3D
position={meshPosition}
>
<mesh>
<sphereGeometry
radius={100}
widthSegments={16}
heightSegments={8}
/>
<meshBasicMaterial
color={0xffffff}
wireframe
/>
</mesh>
<mesh
position={childPosition}
>
<sphereGeometry
radius={50}
widthSegments={16}
heightSegments={8}
/>
<meshBasicMaterial
color={0x00ff00}
wireframe
/>
</mesh>
</object3D>
{
<PointCloud/>
}
</scene>
</React3>
</div>);
}
}
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(
<Router history={customHistory}>
<div>
<Route path="/:slug?" component={ExampleBrowser} />
</div>
</Router>,
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,
};
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
SYMBOL INDEX (148 symbols across 32 files)
FILE: src/examples/AdvancedExample/index.js
function onRecreateCanvas (line 20) | function onRecreateCanvas() {
function animate (line 25) | function animate() {
FILE: src/examples/AnimationCloth/Cloth.js
function plane (line 15) | function plane(width, height) {
constant DAMPING (line 25) | const DAMPING = 0.03;
constant DRAG (line 26) | const DRAG = 1 - DAMPING;
constant MASS (line 27) | const MASS = 0.1;
class Particle (line 35) | class Particle {
method constructor (line 36) | constructor(x, y, z, mass) {
method addForce (line 48) | addForce(force) {
method integrate (line 55) | integrate(timesQ) {
class Cloth (line 68) | class Cloth {
method constructor (line 72) | constructor(w = 10, h = 10) {
FILE: src/examples/AnimationCloth/ClothGeometry.jsx
class ClothGeometry (line 8) | class ClothGeometry extends React.Component {
method componentDidMount (line 13) | componentDidMount() {
method render (line 25) | render() {
FILE: src/examples/AnimationCloth/Info.js
class Info (line 5) | class Info extends React.Component {
method render (line 18) | render() {
FILE: src/examples/AnimationCloth/Poles.js
class Poles (line 7) | class Poles extends React.Component {
method constructor (line 8) | constructor(props, context) {
method render (line 30) | render() {
FILE: src/examples/AnimationCloth/Sphere.js
class Sphere (line 11) | class Sphere extends React.Component {
method constructor (line 17) | constructor(props, context) {
method render (line 27) | render() {
FILE: src/examples/AnimationCloth/StaticWorld.js
class StaticWorld (line 15) | class StaticWorld extends React.Component {
method constructor (line 21) | constructor(props, context) {
method render (line 56) | render() {
FILE: src/examples/AnimationCloth/index.jsx
constant GRAVITY (line 19) | const GRAVITY = 981 * 1.4;
constant TIMESTEP (line 22) | const TIMESTEP = 18 / 1000;
constant TIMESTEP_SQ (line 23) | const TIMESTEP_SQ = TIMESTEP * TIMESTEP;
function satisfyConstrains (line 28) | function satisfyConstrains(p1, p2, distance) {
class AnimationCloth (line 42) | class AnimationCloth extends ExampleBase {
method constructor (line 43) | constructor(props, context) {
method componentDidMount (line 94) | componentDidMount() {
method componentWillUnmount (line 123) | componentWillUnmount() {
method _simulate (line 145) | _simulate(time) {
method render (line 330) | render() {
FILE: src/examples/Benchmark/RotatingCube.js
class RotatingCube (line 8) | class RotatingCube extends React.Component {
method render (line 14) | render() {
FILE: src/examples/Benchmark/RotatingCubes.js
class RotatingCubes (line 11) | class RotatingCubes extends ExampleBase {
method constructor (line 12) | constructor(props, context) {
method _getMeshStates (line 42) | _getMeshStates() {
method _updateGraphics (line 57) | _updateGraphics() {
method _updatePhysics (line 63) | _updatePhysics() {
method componentDidMount (line 82) | componentDidMount() {
method componentWillUnmount (line 95) | componentWillUnmount() {
method _createBodies (line 99) | _createBodies() {
method _createBody (line 108) | _createBody() {
method _getInputBox (line 142) | _getInputBox(title) {
method render (line 167) | render() {
FILE: src/examples/Benchmark/RotatingCubesDirectUpdates.js
class RotatingCubesDirectUpdates (line 8) | class RotatingCubesDirectUpdates extends RotatingCubes {
method _getMeshStates (line 9) | _getMeshStates() {
method _bodyRef (line 19) | _bodyRef(index, body) {
method _updateGraphics (line 28) | _updateGraphics() {
method _createBody (line 41) | _createBody(i) {
method render (line 49) | render() {
FILE: src/examples/DraggableCubes/AllCubes.js
class AllCubes (line 11) | class AllCubes extends React.Component {
method constructor (line 25) | constructor(props, context) {
method componentDidMount (line 49) | componentDidMount() {
method render (line 112) | render() {
FILE: src/examples/DraggableCubes/DraggableCube.js
class DraggableCube (line 15) | class DraggableCube extends React.Component {
method constructor (line 32) | constructor(props, context) {
method componentWillUnmount (line 72) | componentWillUnmount() {
method render (line 176) | render() {
FILE: src/examples/DraggableCubes/index.js
class DraggableCubes (line 18) | class DraggableCubes extends ExampleBase {
method constructor (line 19) | constructor(props, context) {
method componentDidMount (line 45) | componentDidMount() {
method componentDidUpdate (line 102) | componentDidUpdate(newProps) {
method componentWillUnmount (line 124) | componentWillUnmount() {
method _onAnimateInternal (line 133) | _onAnimateInternal() {
method render (line 166) | render() {
FILE: src/examples/ExampleBase.js
class ExampleBase (line 5) | class ExampleBase extends React.Component {
FILE: src/examples/Geometries/index.js
class Geometries (line 11) | class Geometries extends ExampleBase {
method constructor (line 12) | constructor(props, context) {
method componentDidMount (line 57) | componentDidMount() {
method componentWillUnmount (line 66) | componentWillUnmount() {
method _onAnimateInternal (line 70) | _onAnimateInternal() {
method render (line 80) | render() {
FILE: src/examples/GeometryShapes/Rect.js
function Rect (line 4) | function Rect(props) {
FILE: src/examples/GeometryShapes/Resources.js
class Resources (line 6) | class Resources extends React.Component {
method shouldComponentUpdate (line 7) | shouldComponentUpdate() {
method render (line 11) | render() {
FILE: src/examples/GeometryShapes/Shape.js
class Shape (line 16) | class Shape extends React.Component {
method render (line 31) | render() {
FILE: src/examples/GeometryShapes/Shapes.js
class Shapes (line 4) | class Shapes extends React.Component {
method shouldComponentUpdate (line 5) | shouldComponentUpdate() {
method render (line 9) | render() {
FILE: src/examples/GeometryShapes/index.js
class GeometryShapes (line 14) | class GeometryShapes extends ExampleBase {
method constructor (line 15) | constructor(props, context) {
method componentDidMount (line 33) | componentDidMount() {
method componentWillUnmount (line 48) | componentWillUnmount() {
method _onAnimateInternal (line 137) | _onAnimateInternal() {
method render (line 150) | render() {
FILE: src/examples/ManualRendering/Info.js
class Info (line 5) | class Info extends React.Component {
method constructor (line 14) | constructor(props, context) {
method render (line 46) | render() {
FILE: src/examples/ManualRendering/index.js
class Manual (line 9) | class Manual extends ExampleBase {
method constructor (line 10) | constructor(props, context) {
method componentDidMount (line 26) | componentDidMount() {
method render (line 63) | render() {
FILE: src/examples/Physics/index.js
class Physics (line 10) | class Physics extends ExampleBase {
method constructor (line 11) | constructor(props, context) {
method componentWillUnmount (line 61) | componentWillUnmount() {
method componentDidMount (line 67) | componentDidMount() {
method render (line 80) | render() {
FILE: src/examples/Physics/mousePick.js
class PhysicsMousePick (line 17) | class PhysicsMousePick extends ExampleBase {
method constructor (line 18) | constructor(props, context) {
method _setClickMarker (line 162) | _setClickMarker(x, y, z) {
method componentDidMount (line 169) | componentDidMount() {
method componentDidUpdate (line 194) | componentDidUpdate(newProps) {
method componentWillUnmount (line 209) | componentWillUnmount() {
method render (line 272) | render() {
FILE: src/examples/Physics/mousePick/PickableMesh.js
class PickableMesh (line 6) | class PickableMesh extends React.Component {
method componentDidMount (line 16) | componentDidMount() {
method componentWillUnmount (line 31) | componentWillUnmount() {
method render (line 49) | render() {
FILE: src/examples/Simple/index.js
class Simple (line 5) | class Simple extends React.Component {
method constructor (line 11) | constructor(props, context) {
method render (line 39) | render() {
FILE: src/examples/WebGLCameraExample/Info.js
class Info (line 5) | class Info extends React.Component {
method render (line 11) | render() {
FILE: src/examples/WebGLCameraExample/PointCloud.js
class PointCloud (line 4) | class PointCloud extends React.Component {
method constructor (line 5) | constructor(props, context) {
method shouldComponentUpdate (line 21) | shouldComponentUpdate() {
method render (line 25) | render() {
FILE: src/examples/WebGLCameraExample/index.js
class WebGLCameraExample (line 24) | class WebGLCameraExample extends ExampleBase {
method constructor (line 25) | constructor(props, context) {
method componentDidMount (line 40) | componentDidMount() {
method componentWillUnmount (line 65) | componentWillUnmount() {
method render (line 123) | render() {
FILE: src/examples/inputs/MouseInput.js
class MouseInput (line 30) | class MouseInput extends Module {
method constructor (line 31) | constructor() {
method setup (line 45) | setup(react3RendererInstance) {
method isReady (line 75) | isReady() {
method setActive (line 79) | setActive(active) {
method restrictIntersections (line 83) | restrictIntersections(objects, recursive = false) {
method ready (line 90) | ready(scene, container, camera) {
method _createSyntheticMouseEvent (line 205) | _createSyntheticMouseEvent(eventType, prototype) {
method _intersectAndDispatch (line 210) | _intersectAndDispatch(callbackName, mouseEvent) {
method _getIntersections (line 235) | _getIntersections(mouseCoords) {
method getCameraRay (line 254) | getCameraRay(mouseCoords) {
method intersectObject (line 269) | intersectObject(mouseCoords, object, recursive = false) {
method containerResized (line 283) | containerResized() {
method update (line 287) | update() {
method _updateEnterLeave (line 297) | _updateEnterLeave() {
method _getRelativeMouseCoords (line 363) | _getRelativeMouseCoords(screenMouseCoords) {
method dispose (line 380) | dispose() {
FILE: src/ref/trackball.js
class TrackballControls (line 12) | class TrackballControls extends THREE.EventDispatcher {
method constructor (line 13) | constructor(object, domElement) {
Condensed preview — 51 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (172K chars).
[
{
"path": ".eslintrc",
"chars": 448,
"preview": "// Use this file as a starting point for your project's .eslintrc.\n// Copy this file, and add rule overrides as needed.\n"
},
{
"path": ".gitignore",
"chars": 62,
"preview": "/node_modules\n.idea/dictionaries/\n.idea/uiDesigner.xml\n.idea/\n"
},
{
"path": ".gitmodules",
"chars": 118,
"preview": "[submodule \"pages\"]\n\tpath = pages\n\turl = git@github.com:toxicFork/react-three-renderer-example.git\n\tbranch = gh-pages\n"
},
{
"path": "CONTRIBUTORS.md",
"chars": 144,
"preview": "* [@toxicFork](https://github.com/toxicFork)\n * Original Author\n* [@vkammerer](https://github.com/vkammerer)\n * Added in"
},
{
"path": "README.md",
"chars": 379,
"preview": "react-three-renderer-example\n============================\n\nExamples for [react-three-renderer](https://github.com/toxicF"
},
{
"path": "assets/advanced.html",
"chars": 4950,
"preview": "<!doctype html>\n<html>\n<head>\n <style>\n @font-face {\n font-family: 'inconsolata';\n src: "
},
{
"path": "assets/index.html",
"chars": 4694,
"preview": "<!doctype html>\n<html>\n<head>\n <style>\n @font-face {\n font-family: 'inconsolata';\n src: "
},
{
"path": "config/webpackCommonsChunkPluginConfig.js",
"chars": 295,
"preview": "/* eslint-disable import/no-extraneous-dependencies */\n\nimport webpack from 'webpack';\nimport path from 'path';\n\n// noin"
},
{
"path": "config/webpackPluginsWithoutUglify.js",
"chars": 350,
"preview": "/* eslint-disable import/no-extraneous-dependencies */\n\nimport webpack from 'webpack';\n\nimport commonsChunkPluginConfig "
},
{
"path": "gulpfile.babel.js",
"chars": 4782,
"preview": "/* eslint-disable import/no-extraneous-dependencies */\n\nimport gulp from 'gulp';\nimport webpack from 'webpack';\nimport W"
},
{
"path": "package.json",
"chars": 2270,
"preview": "{\n \"name\": \"react-three-renderer-example\",\n \"version\": \"1.0.0\",\n \"description\": \"An example showing how to use the re"
},
{
"path": "src/examples/AdvancedExample/AdvancedComponent.js",
"chars": 0,
"preview": ""
},
{
"path": "src/examples/AdvancedExample/index.js",
"chars": 1349,
"preview": "// see advanced.html :)\n\nimport React from 'react';\nimport React3Renderer from 'react-three-renderer/lib/React3Renderer'"
},
{
"path": "src/examples/AnimationCloth/Cloth.js",
"chars": 2987,
"preview": "/*\n * Cloth Simulation using a relaxed constrains solver\n */\n\n// Suggested Readings\n\n// Advanced Character Physics by Th"
},
{
"path": "src/examples/AnimationCloth/ClothGeometry.jsx",
"chars": 806,
"preview": "import React from 'react';\nimport PropTypes from 'prop-types';\n\nimport PureRenderMixin from 'react/lib/ReactComponentWit"
},
{
"path": "src/examples/AnimationCloth/Info.js",
"chars": 2027,
"preview": "import React from 'react';\n\nimport PropTypes from 'prop-types';\n\nclass Info extends React.Component {\n static propTypes"
},
{
"path": "src/examples/AnimationCloth/Poles.js",
"chars": 2907,
"preview": "import React from 'react';\n\nimport * as THREE from 'three';\n\nimport PureRenderMixin from 'react/lib/ReactComponentWithPu"
},
{
"path": "src/examples/AnimationCloth/Sphere.js",
"chars": 988,
"preview": "import React from 'react';\n\nimport * as THREE from 'three';\n\nimport PureRenderMixin from 'react/lib/ReactComponentWithPu"
},
{
"path": "src/examples/AnimationCloth/StaticWorld.js",
"chars": 4452,
"preview": "import React from 'react';\nimport * as THREE from 'three';\nimport PropTypes from 'prop-types';\n\nimport PureRenderMixin f"
},
{
"path": "src/examples/AnimationCloth/index.js",
"chars": 53,
"preview": "import jsx from './index.jsx';\n\nmodule.exports = jsx;"
},
{
"path": "src/examples/AnimationCloth/index.jsx",
"chars": 9118,
"preview": "import React from 'react';\nimport * as THREE from 'three';\nimport Stats from 'stats.js';\n\nimport React3 from 'react-thre"
},
{
"path": "src/examples/AnimationCloth/shaders/depth.frag",
"chars": 480,
"preview": "uniform sampler2D texture;\nvarying vec2 vUV;\n\nvec4 pack_depth( const in float depth ) {\n const vec4 bit_shift = vec4( 2"
},
{
"path": "src/examples/AnimationCloth/shaders/depth.vert",
"chars": 164,
"preview": "varying vec2 vUV;\n\nvoid main() {\n vUV = 0.75 * uv;\n\n vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );\n\n gl_"
},
{
"path": "src/examples/Benchmark/RotatingCube.js",
"chars": 748,
"preview": "import React from 'react';\nimport * as THREE from 'three';\n\nimport PropTypes from 'prop-types';\n\nconst meshScale = new T"
},
{
"path": "src/examples/Benchmark/RotatingCubes.js",
"chars": 6307,
"preview": "import React from 'react';\nimport React3 from 'react-three-renderer';\nimport * as THREE from 'three';\n\nimport ExampleBas"
},
{
"path": "src/examples/Benchmark/RotatingCubesDirectUpdates.js",
"chars": 3533,
"preview": "import React from 'react';\nimport React3 from 'react-three-renderer';\nimport * as THREE from 'three';\n\nimport RotatingCu"
},
{
"path": "src/examples/DraggableCubes/AllCubes.js",
"chars": 2898,
"preview": "import React from 'react';\nimport DraggableCube from './DraggableCube';\nimport * as THREE from 'three';\n\nimport PureRend"
},
{
"path": "src/examples/DraggableCubes/DraggableCube.js",
"chars": 4991,
"preview": "import React from 'react';\nimport PropTypes from 'prop-types';\n\nimport * as THREE from 'three';\nimport PureRenderMixin f"
},
{
"path": "src/examples/DraggableCubes/index.js",
"chars": 5705,
"preview": "import React from 'react';\n\nimport PureRenderMixin from 'react/lib/ReactComponentWithPureRenderMixin';\n\nimport * as THRE"
},
{
"path": "src/examples/ExampleBase.js",
"chars": 249,
"preview": "import React from 'react';\n\nimport PropTypes from 'prop-types';\n\nclass ExampleBase extends React.Component {\n static pr"
},
{
"path": "src/examples/ExampleBrowser.js",
"chars": 3737,
"preview": "import React from 'react';\nimport { NavLink } from 'react-router-dom';\nimport ExampleViewer from './ExampleViewer';\n\nimp"
},
{
"path": "src/examples/ExampleViewer.js",
"chars": 869,
"preview": "import React from 'react';\nimport sizeMe from 'react-sizeme';\n\nconst ExampleViewer = ({ example, size }) => {\n let sour"
},
{
"path": "src/examples/Geometries/index.js",
"chars": 8183,
"preview": "import React from 'react';\n\nimport * as THREE from 'three';\nimport Stats from 'stats.js';\n\nimport React3 from 'react-thr"
},
{
"path": "src/examples/GeometryShapes/Rect.js",
"chars": 619,
"preview": "import React from 'react';\nimport PropTypes from 'react/lib/ReactPropTypes';\n\nfunction Rect(props) {\n const {\n width"
},
{
"path": "src/examples/GeometryShapes/Resources.js",
"chars": 10930,
"preview": "import React from 'react';\nimport * as THREE from 'three';\n\nimport Rect from './Rect';\n\nclass Resources extends React.Co"
},
{
"path": "src/examples/GeometryShapes/Shape.js",
"chars": 3739,
"preview": "import React from 'react';\nimport * as THREE from 'three';\nimport PropTypes from 'react/lib/ReactPropTypes';\n\nimport Pur"
},
{
"path": "src/examples/GeometryShapes/Shapes.js",
"chars": 2223,
"preview": "import React from 'react';\nimport Shape from './Shape';\n\nclass Shapes extends React.Component {\n shouldComponentUpdate("
},
{
"path": "src/examples/GeometryShapes/index.js",
"chars": 5535,
"preview": "import React from 'react';\n\nimport * as THREE from 'three';\nimport Stats from 'stats.js';\n\nimport React3 from 'react-thr"
},
{
"path": "src/examples/ManualRendering/Info.js",
"chars": 2519,
"preview": "import React from 'react';\n\nimport PropTypes from 'prop-types';\n\nclass Info extends React.Component {\n static propTypes"
},
{
"path": "src/examples/ManualRendering/index.js",
"chars": 2512,
"preview": "import React from 'react';\nimport React3 from 'react-three-renderer';\nimport * as THREE from 'three';\n\nimport ExampleBas"
},
{
"path": "src/examples/Physics/index.js",
"chars": 2818,
"preview": "import React from 'react';\nimport React3 from 'react-three-renderer';\nimport * as THREE from 'three';\nimport CANNON from"
},
{
"path": "src/examples/Physics/mousePick/PickableMesh.js",
"chars": 1406,
"preview": "import React from 'react';\nimport * as THREE from 'three';\n\nimport PropTypes from 'prop-types';\n\nclass PickableMesh exte"
},
{
"path": "src/examples/Physics/mousePick.js",
"chars": 9939,
"preview": "import React from 'react';\nimport React3 from 'react-three-renderer';\nimport * as THREE from 'three';\nimport CANNON from"
},
{
"path": "src/examples/Simple/index.js",
"chars": 1900,
"preview": "import React from 'react';\nimport React3 from 'react-three-renderer';\nimport * as THREE from 'three';\n\nclass Simple exte"
},
{
"path": "src/examples/WebGLCameraExample/Info.js",
"chars": 917,
"preview": "import React from 'react';\n\nimport PropTypes from 'prop-types';\n\nclass Info extends React.Component {\n static propTypes"
},
{
"path": "src/examples/WebGLCameraExample/PointCloud.js",
"chars": 732,
"preview": "import React from 'react';\nimport * as THREE from 'three';\n\nclass PointCloud extends React.Component {\n constructor(pro"
},
{
"path": "src/examples/WebGLCameraExample/index.js",
"chars": 5946,
"preview": "import React from 'react';\nimport ReactDOM from 'react-dom';\n\nimport * as THREE from 'three';\nimport ExampleBase from '."
},
{
"path": "src/examples/inputs/MouseInput.js",
"chars": 10961,
"preview": "import React3 from 'react-three-renderer';\nimport * as THREE from 'three';\nimport ReactUpdates from 'react-dom/lib/React"
},
{
"path": "src/index.jsx",
"chars": 565,
"preview": "/* eslint-disable no-undef */\n\nimport React from 'react';\nimport ReactDOM from 'react-dom';\nimport {\n HashRouter as Rou"
},
{
"path": "src/ref/trackball.js",
"chars": 15552,
"preview": "/* eslint-disable */\n\nimport * as THREE from 'three';\n\n/**\n * @author Eberhard Graether / http://egraether.com/\n * @auth"
},
{
"path": "webpack.config.babel.js",
"chars": 1520,
"preview": "/* eslint-disable import/no-extraneous-dependencies */\n\nimport path from 'path';\nimport webpack from 'webpack';\nimport p"
}
]
About this extraction
This page contains the full source code of the toxicFork/react-three-renderer-example GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 51 files (157.6 KB), approximately 41.1k tokens, and a symbol index with 148 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.