src/lib/descriptors/Light/LightDescriptorBase.js
import * as THREE from 'three';
import PropTypes from 'react/lib/ReactPropTypes';
import warning from 'fbjs/lib/warning';
import Object3DDescriptor from '../Object/Object3DDescriptor';
import propTypeInstanceOf from '../../utils/propTypeInstanceOf';
const updateLightTargetFromQuaternion = (() => {
const lightPositionVector = new THREE.Vector3();
const forward = new THREE.Vector3();
return (light) => {
light.updateMatrixWorld();
lightPositionVector.setFromMatrixPosition(light.matrixWorld);
// rotate forward to match the rotation
// then set the target position
light.target.position.copy(forward.set(0, 0, 1)
.applyQuaternion(light.quaternion)
.add(lightPositionVector));
light.target.updateMatrixWorld();
};
})();
class LightDescriptorBase extends Object3DDescriptor {
static defaultShadowCameraNear = 0.5;
static defaultShadowCameraFar = 500;
static defaultShadowBias = 0;
constructor(react3Instance) {
super(react3Instance);
this.removeProp('receiveShadow');
this._hasDirection = false;
if (process.env.NODE_ENV !== 'production') {
this._warnedAboutLightMaterialUpdate = false;
}
this.hasProp('updatesRefreshAllMaterials', {
type: PropTypes.bool,
updateInitial: true,
update(threeObject, updatesRefreshAllMaterials) {
threeObject.userData._updatesRefreshAllMaterials = updatesRefreshAllMaterials;
},
default: false,
});
this.hasProp('shadowBias', {
type: PropTypes.number,
updateInitial: true,
update(threeObject, value, hasProp) {
if (hasProp) {
threeObject.shadow.bias = value;
}
},
default: LightDescriptorBase.defaultShadowBias,
});
this.hasProp('shadowDarkness', {
type: PropTypes.number,
simple: true,
default: 0.5,
});
this.hasProp('shadowMapWidth', {
type: PropTypes.number,
updateInitial: true,
update(threeObject, value, hasProp) {
if (hasProp) {
threeObject.shadow.mapSize.x = value;
}
},
default: 512,
});
this.hasProp('shadowMapHeight', {
type: PropTypes.number,
updateInitial: true,
update(threeObject, value, hasProp) {
if (hasProp) {
threeObject.shadow.mapSize.y = value;
}
},
default: 512,
});
this.hasProp('shadowCameraNear', {
type: PropTypes.number,
updateInitial: true,
update(threeObject, value, hasProp) {
if (hasProp) {
threeObject.shadow.camera.near = value;
}
},
default: LightDescriptorBase.defaultShadowCameraNear,
});
this.hasProp('shadowCameraFar', {
type: PropTypes.number,
updateInitial: true,
update(threeObject, value, hasProp) {
if (hasProp) {
threeObject.shadow.camera.far = value;
}
},
default: LightDescriptorBase.defaultShadowCameraFar,
});
this.hasProp('castShadow', {
override: true,
type: PropTypes.bool,
update: this.triggerRemount,
default: false,
});
}
hasDirection() {
this._hasDirection = true;
// recreate the props to use target
this.removeProp('position');
this.removeProp('rotation');
this.removeProp('quaternion');
this.removeProp('lookAt');
this.removeProp('matrix');
this.hasProp('position', {
type: propTypeInstanceOf(THREE.Vector3),
update(threeObject, position) {
threeObject.position.copy(position);
if (threeObject.userData._lookAt) {
threeObject.lookAt(threeObject.userData._lookAt);
}
threeObject.userData._needsDirectionUpdate = true;
},
default: new THREE.Vector3(),
});
this.hasProp('rotation', {
type: propTypeInstanceOf(THREE.Euler),
update(light, rotation) {
light.rotation.copy(rotation);
light.userData._needsDirectionUpdate = true;
},
default: new THREE.Euler(),
});
this.hasProp('quaternion', {
type: propTypeInstanceOf(THREE.Quaternion),
update(light, quaternion) {
light.quaternion.copy(quaternion);
light.userData._needsDirectionUpdate = true;
},
default: new THREE.Quaternion(),
});
this.hasProp('matrix', {
type: propTypeInstanceOf(THREE.Matrix4),
update(light, matrix) {
light.matrix.copy(matrix);
light.matrix.decompose(
light.position,
light.quaternion,
light.scale);
light.userData._needsDirectionUpdate = true;
},
default: new THREE.Matrix4(),
});
this.hasProp('lookAt', {
type: propTypeInstanceOf(THREE.Vector3),
update(threeObject, lookAt) {
threeObject.userData._lookAt = lookAt;
if (lookAt) {
threeObject.lookAt(lookAt);
threeObject.userData._needsDirectionUpdate = true;
}
},
default: undefined,
});
}
completePropertyUpdates(threeObject) {
super.completePropertyUpdates(threeObject);
if (threeObject.userData._needsDirectionUpdate) {
threeObject.userData._needsDirectionUpdate = false;
updateLightTargetFromQuaternion(threeObject);
}
}
hasColor(colorType = 'color', defaultValue = 0xffffff) {
this.hasProp(colorType, {
type: PropTypes.oneOfType([
propTypeInstanceOf(THREE.Color),
PropTypes.number,
PropTypes.string,
]),
update(threeObject, newColor) {
threeObject.color.set(newColor);
},
default: defaultValue,
});
}
applyInitialProps(threeObject, props) {
super.applyInitialProps(threeObject, props);
if (props.hasOwnProperty('castShadow')) {
threeObject.castShadow = props.castShadow;
}
if (this._hasDirection) {
threeObject.userData._needsDirectionUpdate = false;
if (props.position || props.lookAt || props.rotation || props.quaternion) {
updateLightTargetFromQuaternion(threeObject);
}
}
}
unmount(threeObject) {
this.updateAllMaterials(threeObject);
super.unmount(threeObject);
}
setParent(threeObject, parentObject3d) {
super.setParent(threeObject, parentObject3d);
this.updateAllMaterials(threeObject);
}
updateAllMaterials(threeObject) {
const rootInstance = threeObject.userData.markup._rootInstance;
if (rootInstance && !rootInstance._willUnmount) {
if (process.env.NODE_ENV !== 'production') {
if (!this._warnedAboutLightMaterialUpdate
&& !threeObject.userData._updatesRefreshAllMaterials) {
const owner = threeObject.userData.react3internalComponent._currentElement._owner;
const elementType = threeObject.userData.react3internalComponent._elementType;
warning(this._warnedAboutLightMaterialUpdate,
LightDescriptorBase.getDynamicWarningMessage(elementType, owner));
this._warnedAboutLightMaterialUpdate = true;
}
}
rootInstance.allMaterialsNeedUpdate();
}
}
}
if (process.env.NODE_ENV !== 'production') {
LightDescriptorBase.getDynamicWarningMessage = (elementType, owner) =>
`<${elementType}/> has been updated which triggered a refresh of all materials.
This is a potentially expensive operation.
This can happen when you add or remove a light, or add or remove any component
before any lights without keys e.g.
<object3d>
{/* new or removed component here */}
<ambientLight/>
</object3d>, or update some properties of lights.
If you would like to add components, you should either add the components
after the lights (recommended), e.g.
<object3d>
<ambientLight/>
{/* new or removed component here */}
</object3d>, or add a 'key' property to the lights e.g.
<object3d>
{/* new or removed component here */}
<ambientLight key="light"/>
</object3d>.
If you have modified a light's properties e.g. toggled castShadow,
the materials need to be rebuilt as well.
To acknowledge and remove this message, please add the property 'updatesRefreshAllMaterials'
to <${elementType}/> inside the render() of ${(owner && owner.getName()) || 'a component'}.
For more information, visit https://github.com/mrdoob/threejs/wiki/Updates .`;
}
module.exports = LightDescriptorBase;