diff --git a/src/components/custom-colors.js b/src/components/custom-colors.js
new file mode 100644
index 000000000..f053a4424
--- /dev/null
+++ b/src/components/custom-colors.js
@@ -0,0 +1,66 @@
+/* global AFRAME */
+import { getMaterials } from '../editor/components/components/CustomizeColorWidget';
+const styleParser = AFRAME.utils.styleParser;
+
+AFRAME.registerComponent('custom-colors', {
+ schema: {
+ type: 'string',
+ parse: styleParser.parse,
+ stringify: styleParser.stringify
+ },
+ update() {
+ // If the mesh has not been traversed, duplicate the materials so that we can avoid
+ // accidental shared references, i.e. changing one material changes materials across multiple entities
+ if (!this.hasOrigColor) {
+ const materialMap = new Map();
+ this.el.object3D.traverse((node) => {
+ if (node.material) {
+ if (!materialMap.has(node.material.uuid)) {
+ materialMap.set(node.material.uuid, node.material.clone());
+ }
+ node.material = materialMap.get(node.material.uuid);
+ }
+ });
+ }
+
+ const materials = getMaterials(this.el.object3D);
+ materials.forEach((material) => {
+ if (!material.userData.origColor) {
+ material.userData.origColor = material.color.clone();
+ this.hasOrigColor = true;
+ }
+ if (this.data[material.name] !== undefined) {
+ material.color.set(this.data[material.name]);
+ } else {
+ // Reset to original
+ material.color.set(material.userData.origColor);
+ }
+ });
+ },
+ updateMaterials() {
+ this.update();
+ },
+ resetAndUpdateMaterials() {
+ this.hasOrigColor = false;
+ this.updateMaterials();
+ },
+ init() {
+ this.hasOrigColor = false;
+ this.resetAndUpdateMaterials = this.resetAndUpdateMaterials.bind(this);
+
+ // Models that are components of larger models trigger this event instead of model-loaded.
+ // This also will fire when the selected model is changed.
+ this.el.addEventListener('object3dset', this.resetAndUpdateMaterials);
+ if (this.el.getObject3D('mesh')) {
+ this.update();
+ }
+ },
+ remove() {
+ this.el.removeEventListener('object3dset', this.resetAndUpdateMaterials);
+ const materials = getMaterials(this.el.object3D);
+ materials.forEach((material) => {
+ // Reset to original
+ material.color.set(material.userData.origColor);
+ });
+ }
+});
diff --git a/src/editor/components/components/CommonComponents.js b/src/editor/components/components/CommonComponents.js
index 2fcee3848..b40c459a1 100644
--- a/src/editor/components/components/CommonComponents.js
+++ b/src/editor/components/components/CommonComponents.js
@@ -6,6 +6,7 @@ import { getEntityClipboardRepresentation } from '../../lib/entity';
import Events from '../../lib/Events';
import Clipboard from 'clipboard';
import { saveBlob } from '../../lib/utils';
+import CustomizeColorWidget from './CustomizeColorWidget';
export default class CommonComponents extends React.Component {
static propTypes = {
@@ -46,7 +47,7 @@ export default class CommonComponents extends React.Component {
renderCommonAttributes() {
const entity = this.props.entity;
// return ['position', 'rotation', 'scale', 'visible']
- return ['position', 'rotation', 'scale'].map((componentName) => {
+ const rows = ['position', 'rotation', 'scale'].map((componentName) => {
// if entity has managed-street component, then don't show scale
if (componentName === 'scale' && entity.components['managed-street']) {
return null;
@@ -72,6 +73,12 @@ export default class CommonComponents extends React.Component {
/>
);
});
+
+ // Custom colors are only applicable to entities, not things like intersections or groups.
+ if (entity.hasAttribute('mixin')) {
+ rows.push(