src/lib/descriptors/Geometry/GeometryWithShapesDescriptor.js
import * as THREE from 'three';
import PropTypes from 'react/lib/ReactPropTypes';
import invariant from 'fbjs/lib/invariant';
import GeometryDescriptorBase from './GeometryDescriptorBase';
import ShapeResourceReference from '../../Resources/ShapeResourceReference';
import propTypeInstanceOf from '../../utils/propTypeInstanceOf';
class GeometryWithShapesDescriptor extends GeometryDescriptorBase {
constructor(react3RendererInstance) {
super(react3RendererInstance);
this.hasProp('shapes', {
type: PropTypes.arrayOf(propTypeInstanceOf(THREE.Shape)),
updateInitial: true,
update: (threeObject, shapes) => {
threeObject.userData._shapesFromProps = shapes || [];
threeObject.userData._needsToRefreshGeometry = true;
},
default: [],
});
const optionNames = [
'curveSegments',
'material',
'UVGenerator',
];
const optionTypes = [
PropTypes.number,
PropTypes.number,
PropTypes.shape({
generateTopUV: PropTypes.func,
generateSideWallUV: PropTypes.func,
}),
];
optionNames.forEach((propName, i) => {
this.hasProp(propName, {
type: optionTypes[i],
update: (threeObject, value) => {
if (value === undefined) {
delete threeObject.userData._options[propName];
} else {
threeObject.userData._options[propName] = value;
}
threeObject.userData._needsToRefreshGeometry = true;
},
default: undefined,
});
});
}
completePropertyUpdates(threeObject) {
if (threeObject.userData._needsToRefreshGeometry) {
this.refreshGeometry(threeObject);
threeObject.userData._needsToRefreshGeometry = false;
}
}
construct() {
return new THREE.BufferGeometry();
}
getOptions(props) {
const options = {};
[
'curveSegments',
'material',
'UVGenerator',
].forEach((propName) => {
if (props.hasOwnProperty(propName)) {
options[propName] = props[propName];
}
});
return options;
}
applyInitialProps(threeObject, props) {
super.applyInitialProps(threeObject, props);
threeObject.userData._shapeCache = [];
threeObject.userData._options = this.getOptions(props);
threeObject.userData._resourceListenerCleanupFunctions = [];
threeObject.userData._needsToRefreshGeometry = false;
if (!props.children) {
// will use shapes only from props
this.refreshGeometry(threeObject);
}
}
addChildren(threeObject, children) {
if (process.env.NODE_ENV !== 'production') {
invariant(children.filter(this._invalidChild).length === 0, 'shape-based geometry children' +
' can only be shapes!');
} else {
invariant(children.filter(this._invalidChild).length === 0, false);
}
const shapeCache = [];
children.forEach((child) => {
if (child instanceof ShapeResourceReference) {
const shapeIndex = shapeCache.length;
const resourceListener = (shape) => {
threeObject.userData._shapeCache[shapeIndex] = shape;
this.refreshGeometry(threeObject);
};
resourceListener.target = child;
const cleanupFunction = () => {
child.userData.events.removeListener('resource.set', resourceListener);
threeObject.userData._resourceListenerCleanupFunctions
.splice(threeObject.userData
._resourceListenerCleanupFunctions.indexOf(cleanupFunction), 1);
};
threeObject.userData._resourceListenerCleanupFunctions.push(cleanupFunction);
child.userData.events.on('resource.set', resourceListener);
child.userData.events.once('dispose', () => {
cleanupFunction();
});
shapeCache.push(null);
} else {
shapeCache.push(child);
}
});
threeObject.userData._shapeCache = shapeCache;
this.refreshGeometry(threeObject);
}
addChild(threeObject) {
// new shape was added
// TODO optimize
this.triggerRemount(threeObject);
}
moveChild(threeObject) {
// a shape was moved
// TODO optimize
this.triggerRemount(threeObject);
}
removeChild(threeObject) {
// shape was removed
// TODO optimize
this.triggerRemount(threeObject);
}
_invalidChild = child => !(
child instanceof THREE.Shape ||
child instanceof ShapeResourceReference
);
unmount(geometry) {
geometry.userData._resourceListenerCleanupFunctions.forEach((listener) => {
listener();
});
delete geometry.userData._options;
delete geometry.userData._resourceListenerCleanupFunctions;
delete geometry.userData._shapesFromProps;
return super.unmount(geometry);
}
}
module.exports = GeometryWithShapesDescriptor;