import { BuildableComponent } from '../component/buildable_component.js';
import { Reporter } from '../../reporter/reporter.js';
import { UUIDRegex, checkPropTypes } from '../../lib.js';
import { MaterialCategory } from './material_category.js';
import { WrappedMaterial } from './wrapped_material.js';
import { WrappedImage } from '../image/wrapped_image.js';
import { Project } from '../../project.js';
/**
* Material set
* Component is not auto-linked and uses the Package to formulate the set of materials
* from material categories and material references. As "MaterialSet" is loaded after
* "Material" in the Package, this isn't a problem. Otherwise, building depends
* on Package change events.
*/
class MaterialSet extends BuildableComponent {
/**
* @param {Reporter} reporter
* @param {Object} settings
* @param {UUID} [settings.id]
* @param {string} settings.name
* @param {WrappedImage} [settings.thumbnail]
* @param {Array<MaterialCategory>} [settings.categories]
* @param {Array<WrappedMaterial>} [settings.materials]
*/
constructor(reporter, settings) {
// this component should not be auto linked in parent class
super(reporter, settings, { autoLinkDependencies: false });
checkPropTypes(
settings,
{
name: 'string',
},
{
categories: val => {
return Array.isArray(val) && val.every(entry => entry instanceof MaterialCategory)
},
materials: val => {
return Array.isArray(val) && val.every(entry => entry instanceof WrappedMaterial)
},
id: UUIDRegex,
}
);
// this.exportName = 'materialSets';
this._dependencies.main = {};
// material set should not have dependencies
// or the number of alternative loading paths
// will explode (as ComponentLoader will "see"
// this as alternatives)
// thinking further about this, it is obvious
// that building the set should not lead to
// building every material in it. It is almost
// always undesirable to build "every" material
for ( let material of settings.materials || [] ) {
this.addExportDependency( `material-${material.id}`, material );
}
for ( let category of settings.categories || [] ) {
this.addExportDependency( `category-${category.name}`, category );
}
// no!
// copyProps({
// from: settings,
// into: this._dependencies.main,
// optional: [ 'categories', 'materials' ]
// });
if ( settings.thumbnail ) {
this._dependencies.UI = [ settings.thumbnail ];
}
this.collectMaterials();
// immediately build the "first version" with only the
// directly referenced materials (so, without the categories)
// this._build('main');
}
static _exportName = {
singular: 'materialSet',
plural: 'materialSets'
};
_onTreeSet() {
const component = this;
this._tree.on(
'change',
({ cls = [] }) => {
if ( cls.includes( WrappedMaterial ) || cls.includes( MaterialCategory ) ) {
component.report({ msg: 'Materials or material categories changed in package. Rebuilding.' });
component._rebuild();
}
},
this.id
);
this._rebuild();
}
_onTreeUnset() {
if (this._tree.hasListener('change', this.id)) {
this._tree.removeListener('change', this.id);
}
}
/**
* All materials that are in this set. (Available after building)
* @type {Array<WrappedMaterial>}
*/
allMaterials = [];
allMaterialsIndexed = {};
/**
* Check whether a material is in the set
* @param {WrappedMaterial} material
* @returns {boolean}
*/
has(material) {
return !! this.allMaterialsIndexed[material.id];
}
/**
* Number of materials in set
* @returns {number}
*/
size(quality = 'medium') {
return this.allMaterials.length;
}
collectMaterials() {
const materialsInSet = this._tree
? this._tree.materials
.filter(mat =>
(mat.settings.categories || []).some(cat => (this.settings.categories || []).includes(cat))
||
this._settings.materials.indexOf( mat ) > -1
)
: this._settings.materials || []
;
this.allMaterials = materialsInSet;
this.allMaterialsIndexed = {};
for ( let material of materialsInSet ) {
this.allMaterialsIndexed[ material.id ] = material;
}
this.report({ msg: `size ${materialsInSet.length} materials` });
}
/**
* @protected
*/
async _build(part, quality) {
switch (part) {
case 'UI':
if (this._settings.thumbnail) {
this._setContent('UI', quality, this._settings.thumbnail.content.main[quality]);
}
else {
this._setContent('UI', quality, Project.defaultImages.missing.cloneNode(true));
}
break;
case 'main':
this.collectMaterials();
this._setContent('main', quality, this.allMaterials);
break;
default:
this._setContent(part, quality, null);
break;
}
return this;
}
}
export { MaterialSet };