import { omit, checkPropTypes, unique, getAssigningComponent, pick } from '../lib.js';
import { Project } from '../project.js'
import { Block } from '../package/block/block.js';
import { Group, Material, Object3D, Vector3, Quaternion, Euler, Math, BoxGeometry, BufferGeometry, Texture, BufferAttribute, MeshBasicMaterial, Mesh, Box3, Box3Helper, Matrix4 } from '../../node_modules/three/build/three.module.js';
import { Connector } from '../package/connector/connector.js';
import { ConnectorType } from '../package/connector/connector_type.js';
import { Reporter } from '../reporter/reporter.js';
import { Package } from '../package/package.js';
import { BlockInstance } from './block_instance.js';
import { ComponentTree } from '../component/component_tree.js';
import { Component } from '../component/component.js';
import { Connectable } from './connectable.js';
import { Connection } from './connection.js';
import { MaterialAssignment } from './material_assignment.js';
import { WrappedMesh } from '../package/mesh/wrapped_mesh.js';
import { MaterialSet } from '../package/material/material_set.js';
import { calculatePlacement } from '../utilities/calculatePlacement.js';
import findConnectedConnectors from '../utilities/findConnectedConnectors.js';
import { findExtendOptions, findInsertOptions, findBlockInstanceOptions, findReplaceOptions } from '../utilities/findConfigOptions.js';
import { WrappedMaterial } from '../package/material/wrapped_material.js';
import { PositionedComponent } from '../package/component/positioned_component.js';
import { getDimensions } from '../utilities/getDimensions.js';
import { drawDimensionsLine } from '../utilities/drawDimensionsLine.js';
import { Theme } from '../package/theme/theme.js';
import { v4 as uuid } from '../../node_modules/uuid/dist/esm-browser/index.js';
import { MeshStandardNodeMaterial } from '../../node_modules/three/examples/jsm/nodes/Nodes.js';
/**
* @typedef {Object} Instruction
* @property {Object} [remove]
* @property {BlockInstance} [remove.blockInstance]
* @property {Array<BlockInstance>} [remove.blockInstanceChildren]
* @property {Array<MaterialAssignment>} [remove.materialAssignments]
* @property {Array<Connection>} [remove.connections]
* @property {Object} [cut]
* @property {Array<BlockInstance>} [remove.blockInstances]
* @property {Array<MaterialAssignment>} [remove.materialAssignments]
* @property {Array<Connection>} [remove.connections]
* @property {Object} [add]
* @property {Block} [add.block]
* @property {Array<Object>} [add.materialAssignments]
* @property {Array<Object>} [add.configurations]
* @property {Array<Object>} [add.splitChainConfigurations]
* @property {Array<Array<Connector>>} [add.connections] - Array of arrays with two connectors that should be connected
*/
/**
* @typedef {Object} ConnectorSummary
* @property {Connector} connector
* @property {Block} block
* @property {BlockInstance} blockInstance
* @property {boolean} connected
* @property {Connection} [connection]
* @property {ConnectionRole} [role]
* @property {Array<ConnectorType>} allowedMateTypes
* @property {Array<Connector>} allowedMates
*/
/**
* @name Configuration#blockInstances
* @type {Array<BlockInstance>}
*/
/**
* @typedef {Object} Placement
* @property {Vector3} position
* @property {Quaternion} quaternion
*/
/**
* @typedef {Object} ConnectInstruction
* @property {Object} from
* @property {(Connectable|string)} from.connectable
* @property {Connector} from.connector
* @property {Object} to
* @property {(Connectable|string)} to.connectable
* @property {Connector} to.connector
*/
/**
* @typedef {Object} InsertOption
* @property {Block} block
* @property {ConnectInstruction} newConnection1
* @property {ConnectInstruction} newConnection2
* @property {Connection} oldConnection
* @property {Placement} placement
*/
/**
* @typedef {Object} ExtendOption
* @property {Block} block
* @property {ConnectInstruction} [connection]
* @property {Placement} placement
*/
/**
* @typedef {Object} CutOption
* @property {BlockInstance} blockInstance
* @property {Array<Connection>} oldConnections
* @property {Connection} [newConnection]
* @property {Placement} placement
*/
/**
* @typedef {Object} SplitOption
* @property {BlockInstance} blockInstance
* @property {Array<Connection>} oldConnections
* @property {Array<Connectable>} siblingChains
* @property {Placement} placement
*/
// bij laden material assignments aanmaken voor impliciete toewijzingen
// themes op default zetten
/**
* Immutable data structure with methods to produce a new configuration
*/
class Configuration extends ComponentTree {
/**
* @param {Reporter} reporter
* @param {Object} settings
* @param {UUID} [settings.id]
* @param {number} [settings.step = 0]
* @param {Object<UUID,WrappedMaterial>} [settings.lastAssignedMaterials] last assigned material per material variant group
* @param {Object<UUID,WrappedMaterial>} [settings.defaultMaterials] default material per material variant group
* @param {string} [settings.creationDate]
* @param {string} [settings.author]
* @param {Package} settings.pkg
* @param {Array<Connection>} [settings.connections]
* @param {Object<string,Theme>} [settings.themes]
* @param {Array<BlockInstance>} settings.blockInstances
* @param {Array<MaterialAssignment>} [settings.materialAssignments]
*/
constructor(reporter, settings) {
// do basic type checking on the settings
// and check that there are no duplicates
settings.step = settings.step || 1;
settings.blockInstances = settings.blockInstances || [];
settings.connections = settings.connections || [];
settings.materialAssignments = settings.materialAssignments || [];
settings.themes = settings.themes || {};
checkPropTypes(
settings,
{
pkg: Package,
},
{
blockInstances: val => {
if (!Array.isArray(val)) {
return 'Not an array';
}
for (let i = 0; i < val.length; i += 1) {
const bi = val[i];
if (!(bi instanceof BlockInstance)) {
return `Entry blockInstances[${i}] is not a BlockInstance, but a ${bi.constructor.name}`;
}
}
if (val.filter(unique).length !== val.length) {
return 'Duplicate blockInstances specified';
}
return true;
},
connections: val => {
if (!Array.isArray(val)) {
return 'Not an array';
}
for (let i = 0; i < val.length; i += 1) {
const conn = val[i];
if (!(conn instanceof Connection)) {
return `Entry connections[${i}] is not a Connection, but a ${conn.constructor.name}`;
}
}
if (val.filter(unique).length !== val.length) {
return 'Duplicate connections specified';
}
return true;
},
materialAssignments: val => {
if (!Array.isArray(val)) {
return 'Value is not an array'
}
for (let i = 0; i < val.length; i += 1) {
const ma = val[i];
if (!(ma instanceof MaterialAssignment)) {
return `Entry materialAssignments[${i}] is not a MaterialAssignment, but a ${ma.constructor.name}`;
}
}
if (val.filter(unique).length !== val.length) {
return 'Duplicate materialAssignments specified';
}
return true;
}
}
);
// if (settings.info === undefined) {
// settings.info = Configuration.generateInfoComponent(reporter, settings.pkg);
// }
const connectors = (settings.blockInstances || [])
.reduce(
(arr, bi) =>
([
...arr,
...((bi.block.connectors || []).map(cntr => ({ connectable: bi, connector: cntr })))
]),
[]
);
// console.log(connectors)
// console.log(settings.pkg.themeGroups)
for (let i = 0, l = settings.pkg.themeGroups.length || 0; i < l; i += 1) {
const themeGroup = settings.pkg.themeGroups[i];
// console.log(i, themeGroup)
const theme = settings.themes[themeGroup.id] || settings.pkg.themes.find(t => t.group === themeGroup);
// console.log('Constructor applying theme', theme.name )
// this.applyTheme(blockInstances, connections, materialAssignments, theme);
const themedConfigConnectorObjects = connectors.filter(cntrObj => cntrObj.connector.themeGroup === themeGroup);
const themedConfigConnectors = themedConfigConnectorObjects.map(obj => obj.connector);
const removedConnectableIds = [];
const connectorObjectsToConnect = [...themedConfigConnectorObjects];
// console.log(connectorObjectsToConnect, themedConfigConnectorObjects)
// free the themed connectors by deleting their mates if they are not
// connected to a connectable with the correct theme
for (let connection of Object.values(settings.connections)) {
// console.log('Check', connection.label);
const fromThemedConnectorObject = themedConfigConnectorObjects.find(tcco => tcco.connector === connection.from.connector && tcco.connectable === connection.from.connectable);
const toThemedConnectorObject = themedConfigConnectorObjects.find(tcco => tcco.connector === connection.to.connector && tcco.connectable === connection.to.connectable);
if (fromThemedConnectorObject) {
if (themedConfigConnectors.includes(connection.to.connector)) {
throw new Error(`Both sides of ${connection.label} have a connector with ${themeGroup.label}: ${connection.to.connector.label}, ${connection.from.connector.label}`);
}
if (connection.to.connector.block.theme === theme) {
// this connector is already connected to a block with the selected theme, leave as is
// console.log(`${connection.label} already connected within theme`)
connectorObjectsToConnect.splice(
connectorObjectsToConnect.indexOf(fromThemedConnectorObject),
1
);
}
else {
// console.log('Constructor delete', connection.label, connection.to.connectable);
// delete settings.connections[connection.id];
settings.connections.splice(
settings.connections.indexOf(connection),
1
);
removedConnectableIds.push(connection.to.connectable.id);
// delete settings.blockInstances[connection.to.connectable.id];
settings.blockInstances.splice(
settings.blockInstances.indexOf(connection.to.connectable),
1
);
}
}
else if (toThemedConnectorObject) {
if (themedConfigConnectors.includes(connection.from.connector)) {
throw new Error(`Both sides of ${connection.label} have a connector with ${themeGroup.label}: ${connection.to.connector.label}, ${connection.from.connector.label}`);
}
if (connection.from.connector.block.theme === theme) {
// this connector is already connected to a block with the selected theme, ignore
// console.log(`${connection.label} already connected within theme`)
connectorObjectsToConnect.splice(
connectorObjectsToConnect.indexOf(toThemedConnectorObject),
1
);
}
else {
// console.log('Constructor delete', connection.label, connection.from.connectable)
// delete settings.connections[connection.id];
settings.connections.splice(
settings.connections.indexOf(connection),
1
);
removedConnectableIds.push(connection.from.connectable.id);
// delete settings.blockInstances[connection.from.connectable.id];
settings.blockInstances.splice(
settings.blockInstances.indexOf(connection.from.connectable),
1
);
}
}
}
settings.materialAssignments = settings.materialAssignments.filter( ma => ! removedConnectableIds.includes( ma.blockInstance.id ))
for (const themedConfigConnectorObject of connectorObjectsToConnect) {
// console.log(themedConfigConnectorObject)
if (removedConnectableIds.includes(themedConfigConnectorObject.connectable.id)) {
throw new Error(`Can't create a new connection for ${themedConfigConnectorObject.connectable.label} - it was removed`)
}
const mateConnector = settings.pkg.connectors.find(cntr => cntr.block.theme === theme && themedConfigConnectorObject.connector.mate.connectors.includes(cntr));
const newBI = new BlockInstance(reporter, { block: mateConnector.block });
// console.log('Added', newBI)
settings.blockInstances.push(newBI);
// const newConnection = this._createClonedComponentConnection(
const newConnection = new Connection(
reporter,
{
from: themedConfigConnectorObject,
to: {
connector: mateConnector,
connectable: newBI
}
},
// settings.blockInstances,
// newBI
);
settings.connections.push(newConnection);
}
}
// call ComponentTree constructor without pkg in the settings
// so it isn't autolinked by BuildableBlock instead, specify it
// as an external tree in the instructions object
super(
reporter,
omit(
['pkg'],
settings
),
{
import: Configuration.importInstructions,
externalTrees: [settings.pkg]
}
);
this.options = {
blocks: [],
blockInstances: [],
materials: []
};
this.instructions = {};
this.clonedAssets = [];
this.clonedComponents = [];
// fucking typescript
if (!Object.getOwnPropertyDescriptor(this, 'blockInstances')) {
/** @type {Array<BlockInstance>} */
this.blockInstances = [];
}
if (!Object.getOwnPropertyDescriptor(this, 'materialAssignments')) {
/** @type {Array<MaterialAssignment>} */
this.materialAssignments = [];
}
if (!Object.getOwnPropertyDescriptor(this, 'connections')) {
/** @type {Array<Connection>} */
this.connections = [];
}
//console.log( settings )
this.pkg = settings.pkg;
if (this.pkg.loader) {
this.loader = this.pkg.loader;
}
// check that every connector is only connected once
this.connectors = connectors;
this.connectedConnectors = findConnectedConnectors(settings.connections);
// calculate the placement (position and rotation) of every block instance
if (Array.isArray(settings.blockInstances) && settings.blockInstances.length > 0) {
this.placement = calculatePlacement(this);
}
else {
this.placement = {};
console.warn('Empty configuration detected, no block instances defined.')
}
// make implicit (default) materials explicit
// materials can be assigned to block instances and positioned components with .configurable = true
this.assignables = this.blockInstances.map(bi => bi.assignables).flat();
this.assignedMaterials = {};
// gather manipulation options for this configuration
this.options = {
extend: [],
insert: [],
replace: [],
cut: [],
split: [],
blocks: [],
blockInstances: [],
materials: [],
materialVariantGroups: [],
};
// connector dedup
// remove connectors that are in the same location, because these are usually "inside" a configuration
// that uses a grid to connect objects
//
// mechanism:
// for every connector
// who's id isn't in the list already
// find all colocated connectors
// and add their ids to the list
// then pass that list to the option finder
// to ignore
const colocatedConnectors = {};
for ( let firstConnectorObject of connectors ) {
//console.log(firstConnectorObject)
const firstConnectablePlacement = this.placement[ firstConnectorObject.connectable.id ];
const firstConnector = firstConnectorObject.connector;
const firstConnectorPos = firstConnectablePlacement.position.clone().add(
firstConnector.position.clone().applyQuaternion(firstConnectablePlacement.quaternion)
);
//console.log('find co-located connectors of connector', firstConnector.id); //, firstConnectablePos, firstConnectorPos);
const coloConns = connectors.filter(
secondConnectorObject => {
// potentially co-located connector
const secondConnector = secondConnectorObject.connector;
if ( secondConnector.id === firstConnector.id ) {
// same connector, next
return false;
}
//console.log('check against', secondConnector.id); //, secondConnector.position, firstConnector.position);
try {
const secondConnectablePlacement = this.placement[ secondConnectorObject.connectable.id ];
const secondConnectorPos = secondConnectablePlacement.position.clone().add(
secondConnector.position.clone().applyQuaternion(secondConnectablePlacement.quaternion)
);
const dist = firstConnectorPos.distanceTo(secondConnectorPos);
//console.log ( 'connectable positions', firstConnectablePlacement, secondConnectablePlacement );
//console.log ( 'connector rel positions', firstConnector.position, secondConnector.position );
//console.log ( 'connector abs positions', firstConnectorPos, secondConnectorPos );
//console.log( 'dist', dist, 'colocated', dist < 0.01);
return dist < 0.01;
} catch ( err ) {
console.error(err);
}
}
);
for ( let coloConn of coloConns ) {
// store a reference to the connector and connectable
colocatedConnectors[ coloConn.connector.id ] = colocatedConnectors[ coloConn.connector.id ] || {};
colocatedConnectors[ coloConn.connector.id ][ coloConn.connectable.id ] = true;
}
}
//console.log( 'clcids', colocatedConnectors )
this.options.extend = findExtendOptions(this, colocatedConnectors);
this.options.insert = findInsertOptions(this);
this.options.extend.forEach(option => this._addBlockAddOption(option, 'extend'));
this.options.insert.forEach(option => this._addBlockAddOption(option, 'insert'));
const { cutOptions, splitOptions } = findBlockInstanceOptions(this);
this.options.cut = cutOptions;
this.options.split = splitOptions;
this.options.cut.forEach(option => this._addBlockInstanceRemoveOption(option, 'cut'));
this.options.split.forEach(option => this._addBlockInstanceRemoveOption(option, 'split'));
for (let bi of this.blockInstances) {
for (let mvg of bi.block.distinctMVGs) {
if (!this.options.materialVariantGroups.includes(mvg)) {
// console.log('new', mvg.slug)
this.options.materialVariantGroups.push(mvg);
// this.options.blockInstancesPerMVG[ mvg.id ] = [];
this.options[mvg.id] = [];
}
this.options[mvg.id].push(bi);
}
}
// this.options.materialVariantGroups = this.options.materialVariantGroups.filter(unique);
const mvgIdCacheKey = this.options.materialVariantGroups.map(mvg => mvg.id).sort().join('-');
if (!Configuration.materialCache[mvgIdCacheKey]) {
const configuration = this;
const materials = this.options.materialVariantGroups.map(mvg => mvg.allMaterials).flat().filter(unique);
Configuration.materialCache[mvgIdCacheKey] = materials;
}
this.options.materials = Configuration.materialCache[mvgIdCacheKey];
this.options.material = material => {
console.assert(material instanceof WrappedMaterial, `${material.label || typeof material} is not an instance of WrappedMaterial`);
const assignables = this.assignables.filter(assignable => assignable.materialVariantGroups.find(mvg => material.materialSets.includes(mvg)));
return assignables.map(assignable => {
const assigningComponent = getAssigningComponent(assignable);
const placement = {
position: new Vector3().copy(this.placement[assignable.blockInstance.id].position),
quaternion: new Quaternion().copy(this.placement[assignable.blockInstance.id].quaternion),
};
if (assignable.positionedMeshGroup) {
placement.position.add(assignable.positionedMeshGroup.position);
placement.quaternion.multiply(assignable.positionedMeshGroup.quaternion);
}
if (assignable.positionedMesh) {
placement.position.add(assignable.positionedMesh.position);
placement.quaternion.multiply(assignable.positionedMesh.quaternion);
}
return {
assignable,
material,
placement
};
});
};
//create map to store the boudingboxes for the blockInstances
this.boundingBoxes = new Map();
//create array to store alle the vertices of all the blockInstances
const boundingBoxVertices = []
//place boundingBox and add boundingBox to the map and its vertices to the vertices array
for (let blockInstance of settings.blockInstances || []) {
const bboxGeometry = blockInstance.boundingBox.clone();
this.clonedAssets.push(bboxGeometry);
const matrix = new Matrix4()
const pos = this.placement[blockInstance.id].position
const qat = this.placement[blockInstance.id].quaternion
const scale = new Vector3(1, 1, 1)
matrix.compose(pos, qat, scale)
bboxGeometry.applyMatrix4(matrix)
blockInstance.boundingBox = bboxGeometry
this.boundingBoxes.set(blockInstance.id, bboxGeometry)
blockInstance.boundingBox.computeBoundingBox()
blockInstance.boundingBox.computeBoundingSphere()
//vertex positions
const vecticePositions = bboxGeometry.attributes.position;
const vector = new Vector3();
for (let i = 0, l = vecticePositions.count; i < l; i++) {
vector.fromBufferAttribute(vecticePositions, i);
const vecClone = vector.clone();
this.clonedAssets.push(vecClone);
boundingBoxVertices.push(vecClone);
}
}
//compute boudningBox for the configuration based on the min / max of all the vertice points from the boundingBoxVertices array
var vectorMin = new Vector3();
var vectorMax = new Vector3();
//get the min and max positions
for (let vertice of boundingBoxVertices) {
vectorMin.min(vertice)
vectorMax.max(vertice)
}
//create axes alligned boundingbox
const boundingBox = new Box3(vectorMin, vectorMax)
//create 8 vertice corner points based on box3
const vertices = new Float32Array([
boundingBox.min.x, boundingBox.min.y, boundingBox.min.z,
boundingBox.min.x, boundingBox.min.y, boundingBox.max.z,
boundingBox.max.x, boundingBox.min.y, boundingBox.max.z,
boundingBox.max.x, boundingBox.min.y, boundingBox.min.z,
boundingBox.min.x, boundingBox.max.y, boundingBox.min.z,
boundingBox.min.x, boundingBox.max.y, boundingBox.max.z,
boundingBox.max.x, boundingBox.max.y, boundingBox.max.z,
boundingBox.max.x, boundingBox.max.y, boundingBox.min.z
]);
//create geometry to store the vertices
const boundingBoxGeometry = new BufferGeometry()
boundingBoxGeometry.name = "boundingBox";
boundingBoxGeometry.visible = false;
boundingBoxGeometry.setAttribute('position', new BufferAttribute(vertices, 3));
boundingBoxGeometry.computeBoundingBox()
boundingBoxGeometry.computeBoundingSphere()
//add the buffergeometry as boudingbox property to the configuration
this.boundingBox = boundingBoxGeometry
//console.log( this.boundingBox )
//console.log( this.boundingBoxes )
//compute dimesion lines
this.dimensions = getDimensions(this)
// console.log(this.dimensions)
const config = this;
}
static _exportName = {
singular: 'configuration',
plural: 'configurations'
};
static importInstructions = [
{
cls: BlockInstance
},
{
cls: Connection
},
{
cls: MaterialAssignment
}
];
static materialCache = {};
// static generateInfoComponent(reporter, pkg) {
// return new ConfigurationInfo(
// reporter,
// {
// name: pkg.info.name + ' Configuration',
// packageId: pkg.id,
// packageURL: pkg.loader.loadingBases[0].url,
// step: 0
// }
// );
// }
/**
* All (wrapped) meshes that are present (at least once) in this configuration
* @type {Array<WrappedMesh>}
*/
allMeshes;
/**
* All material variant groups that are applicable on (at least one mesh in) this configuration
* @type {Array<MaterialSet>}
*/
applicableMatVarGroups;
/**
* All materials that are applicable on (at least one mesh in) this configuration
* @type {Array<Material>}
*/
applicableMaterials;
/**
* Connectors in this configuration
* Array of objects the have the connectable and the connector, as that is the unique combination,
* a connector on its own is not unique.
* @type {Array<Object<Connectable,Connector>>}
*/
connectors;
/** @type {Array<ExtendOption>} */
extendOptions;
/** @type {Array<InsertOption>} */
insertOptions;
/**
* List of components that can be used "on" this configuration
* @type {Object}
*/
options;
/**
* List of how-to-use instructions per component
* @type {Object}
*/
instructions;
assignedMaterials;
_addBlockAddOption(addOption, type) {
const block = addOption.block;
if (this.options.blocks.indexOf(block) === -1) {
this.options.blocks.push(block);
}
if (!this.options[block.id]) {
this.options[block.id] = { extend: [], insert: [] };
}
this.options[block.id][type].push(addOption);
}
_addBlockInstanceRemoveOption(removeOption, type) {
const blockInstance = removeOption.blockInstance;
if (this.options.blockInstances.indexOf(blockInstance) === -1) {
this.options.blockInstances.push(blockInstance);
}
if (!this.options[blockInstance.id]) {
this.options[blockInstance.id] = { cut: [], split: [], replace: [], materialize: [] };
}
this.options[blockInstance.id][type].push(removeOption);
}
preparedComponentClones;
prepareComponentClones() {
// console.log('prepare component clones');
this.preparedComponentClones = null;
this.cloneComponents();
// this.preparedComponentClones = this.cloneComponents();
// console.log('prep done')
}
cloneComponents(equalBlockInstanceIds = true) {
if (this.preparedComponentClones) {
console.log('use prepared component clones');
const preparedComponentClones = this.preparedComponentClones;
this.preparedComponentClones = null;
return preparedComponentClones;
}
let blockInstanceClones = {};
let materialAssignmentClones = {};
let connectionClones = {};
for (let bi of this.blockInstances) {
// when creating a new config, the standard is to re-use the
// block instance ids, so transforms can find the meshes again
// and relink
const clonedBi = bi.clone(equalBlockInstanceIds === true ? { id: bi.id } : {});
// console.log(bi, clonedBi)
blockInstanceClones[bi.id] = clonedBi;
}
for (let ma of this.materialAssignments) {
const newMa = new MaterialAssignment(
this._reporter,
{
...ma.settings,
blockInstance: blockInstanceClones[ma.blockInstance.id]
});
materialAssignmentClones[ma.id] = newMa;
}
for (let cn of this.connections) {
const newCn = new Connection(
this._reporter,
{
from: {
connectable: blockInstanceClones[cn.from.connectable.id],
connector: cn.from.connector
},
to: {
connectable: blockInstanceClones[cn.to.connectable.id],
connector: cn.to.connector
},
quaternion: cn.quaternion
}
);
connectionClones[cn.id] = newCn;
}
return {
blockInstanceClones,
materialAssignmentClones,
connectionClones
}
}
// addBlockInstance()
/**
* @param {Object} newConnectionData
* @param {Object<UUID,BlockInstance>} blockInstanceClones
* @param {BlockInstance} [newBlockInstance]
*/
_createClonedComponentConnection(newConnectionData, blockInstanceClones, newBlockInstance) {
return new Connection(
this._reporter,
{
from: {
connectable: newConnectionData.from.connectable === 'new' ? newBlockInstance : blockInstanceClones[newConnectionData.from.connectable.id],
connector: newConnectionData.from.connector
},
to: {
connectable: newConnectionData.to.connectable === 'new' ? newBlockInstance : blockInstanceClones[newConnectionData.to.connectable.id],
connector: newConnectionData.to.connector
},
});
}
_createNewConfiguration(blockInstances, materialAssignments, connections, settings = {}) {
const newSettings = {};
Object.assign(
newSettings,
this.settings
);
newSettings.id = uuid();
Object.assign(
newSettings,
settings
);
newSettings.blockInstances = blockInstances;
newSettings.materialAssignments = materialAssignments;
newSettings.connections = connections;
newSettings.pkg = this.pkg;
newSettings.step = (Number(newSettings.step) || 0) + 1;
return new Configuration(
this._reporter,
newSettings
);
}
_createLastAssignedMAsForBlockInstance(newBI) {
const newMAs = {};
if (this.lastAssignedMaterials) {
const relevantLAMaterials = pick(newBI.applicableMaterialVariantGroups.map(mvg => mvg.id), this.lastAssignedMaterials);
for (let relevantLAMaterial of Object.values(relevantLAMaterials)) {
const repeatMA = new MaterialAssignment(
this._reporter,
{
blockInstance: newBI,
material: relevantLAMaterial
}
);
newMAs[repeatMA.id] = repeatMA;
}
}
return newMAs;
}
add() {
// for empty config?
}
/**
* @param {ExtendOption} option
*/
extend(option, instructions = {}) {
console.log('extend option', option)
let { blockInstanceClones, materialAssignmentClones, connectionClones } = this.cloneComponents();
// add block
const newBI = new BlockInstance(this._reporter, { block: option.block });
blockInstanceClones[newBI.id] = newBI;
// add connection, if necessary
if (option.connection) {
const newConnection = this._createClonedComponentConnection(option.connection, blockInstanceClones, newBI);
connectionClones[newConnection.id] = newConnection;
}
if (instructions.repeatLastAssignment === true) {
Object.assign(
materialAssignmentClones,
this._createLastAssignedMAsForBlockInstance(newBI)
);
}
// return new config
const nc = this._createNewConfiguration(
Object.values(blockInstanceClones),
Object.values(materialAssignmentClones),
Object.values(connectionClones),
);
return nc
}
insert(option, instructions = {}) {
let { blockInstanceClones, materialAssignmentClones, connectionClones } = this.cloneComponents();
// add block
const newBI = new BlockInstance(this._reporter, { block: option.block });
blockInstanceClones[newBI.id] = newBI;
// add 2x connection
const newConnection1 = this._createClonedComponentConnection(option.newConnection1, blockInstanceClones, newBI);
connectionClones[newConnection1.id] = newConnection1;
const newConnection2 = this._createClonedComponentConnection(option.newConnection2, blockInstanceClones, newBI);
connectionClones[newConnection2.id] = newConnection2;
// remove old connection
delete connectionClones[option.oldConnection.id];
if (instructions.repeatLastAssignment === true) {
Object.assign(
materialAssignmentClones,
this._createLastAssignedMAsForBlockInstance(newBI)
);
}
// return new config
return this._createNewConfiguration(
Object.values(blockInstanceClones),
Object.values(materialAssignmentClones),
Object.values(connectionClones),
);
}
replace() {
// remove block
// remove old connections
// add block
// add new connections
}
copy(equalBlockInstanceIds = true) {
let {
blockInstanceClones,
materialAssignmentClones,
connectionClones
} = this.cloneComponents(equalBlockInstanceIds);
return this._createNewConfiguration(
Object.values(blockInstanceClones),
Object.values(materialAssignmentClones),
Object.values(connectionClones)
);
}
_cut(option) {
let { blockInstanceClones, materialAssignmentClones, connectionClones } = this.cloneComponents();
const cutConfigurationComponents = {
blockInstances: [],
materialAssignments: [],
connections: []
};
// cut block
cutConfigurationComponents.blockInstances.push(blockInstanceClones[option.blockInstance.id]);
delete blockInstanceClones[option.blockInstance.id];
// cut block children
for (let child of option.blockInstance.children) {
cutConfigurationComponents.blockInstances.push(blockInstanceClones[child.id]);
delete blockInstanceClones[child.id];
}
// cut block and childrens' material assignments
for (let ma of [...option.blockInstance.materialAssignments, ...(option.blockInstance.childMaterialAssignments || [])]) {
cutConfigurationComponents.materialAssignments.push(materialAssignmentClones[ma.id]);
delete materialAssignmentClones[ma.id];
}
// cut child connections
for (let cn of option.blockInstance.childConnections) {
cutConfigurationComponents.connections.push(connectionClones[cn.id]);
delete connectionClones[cn.id];
}
// delete old connections
for (let oldConnection of option.oldConnections) {
delete connectionClones[oldConnection.id];
}
return { cutConfigurationComponents, blockInstanceClones, materialAssignmentClones, connectionClones };
}
cut(option) {
const { cutConfigurationComponents, blockInstanceClones, materialAssignmentClones, connectionClones } = this._cut(option);
// create new connection, if necessary
if (option.newConnection) {
const newConnection = this._createClonedComponentConnection(option.newConnection, blockInstanceClones);
connectionClones[newConnection.id] = newConnection;
}
// create new configuration
return {
main: this._createNewConfiguration(
Object.values(blockInstanceClones),
Object.values(materialAssignmentClones),
Object.values(connectionClones),
),
cut: this._createNewConfiguration(
cutConfigurationComponents.blockInstances,
cutConfigurationComponents.materialAssignments,
cutConfigurationComponents.connections,
)
};
}
split(option) {
const { cutConfigurationComponents, blockInstanceClones, materialAssignmentClones, connectionClones } = this._cut(option);
const chainConfigurations = [];
// console.log(option.siblingChains)
for (let chain of Object.values(option.siblingChains)) {
const chainComponents = chain.reduce(
(obj, connectable) => {
obj.blockInstances.push(connectable, ...(connectable.children || []));
if (connectable.materialAssignments && connectable.materialAssignments.length > 0) {
obj.materialAssignments.push(...connectable.materialAssignments);
}
if (connectable.childMaterialAssignments && connectable.childMaterialAssignments.length > 0) {
obj.materialAssignments.push(...connectable.childMaterialAssignments);
}
if (connectable.childConnections && connectable.childConnections.length > 0) {
obj.connections.push(...connectable.childConnections);
}
const inChainConnections = connectable.equalConnections.filter(conn => !option.oldConnections.includes(conn));
for (let inChainConnection of inChainConnections) {
if (obj.connections.indexOf(inChainConnection) === -1) {
obj.connections.push(inChainConnection);
}
}
if (connectable.childConnections && connectable.childConnections.length > 0) {
obj.connections.push(...connectable.childConnections);
}
return obj;
},
{
blockInstances: [],
materialAssignments: [],
connections: [],
}
);
chainComponents.blockInstances = chainComponents.blockInstances.map(bi => blockInstanceClones[bi.id]);
chainComponents.materialAssignments = chainComponents.materialAssignments.map(ma => materialAssignmentClones[ma.id]);
chainComponents.connections = chainComponents.connections.map(cn => connectionClones[cn.id]);
chainConfigurations.push(
this._createNewConfiguration(
chainComponents.blockInstances,
chainComponents.materialAssignments,
chainComponents.connections,
this.step + 1
)
);
}
return {
cut: this._createNewConfiguration(
cutConfigurationComponents.blockInstances,
cutConfigurationComponents.materialAssignments,
cutConfigurationComponents.connections,
),
split: chainConfigurations
}
}
/**
* Copies another Configuration and links it to this one
* @param {BlockInstance} myBlockInstance
* @param {Connector} myConnector
* @param {BlockInstance} otherBlockInstance
* @param {Connector} otherConnector
*/
merge(myBlockInstance, myConnector, otherBlockInstance, otherConnector) { // mergeOption?
const otherConfiguration = otherBlockInstance.tree;
if (otherConfiguration.pkg !== this.pkg) {
throw new Error(`Can not merge configuration for ${this.pkg.label} with configurator for ${otherConfiguration.pkg.label} `);
}
const myComponentsCloned = this.cloneComponents();
const otherComponentsCloned = otherConfiguration.cloneComponents();
// console.log(myBlockInstance, myConnector, otherBlockInstance, otherConnector)
const newConnection = this._createClonedComponentConnection(
{
from: { connectable: myBlockInstance, connector: myConnector },
to: { connectable: otherBlockInstance, connector: otherConnector }
},
{
...myComponentsCloned.blockInstanceClones,
...otherComponentsCloned.blockInstanceClones
}
);
const allComponents = {
blockInstances: [
...Object.values(myComponentsCloned.blockInstanceClones || {}),
...Object.values(otherComponentsCloned.blockInstanceClones || {}),
],
materialAssignments: [
...Object.values(myComponentsCloned.materialAssignmentClones || {}),
...Object.values(otherComponentsCloned.materialAssignmentClones || {}),
],
connections: [
...Object.values(myComponentsCloned.connectionClones || {}),
...Object.values(otherComponentsCloned.connectionClones || {}),
newConnection
],
};
return this._createNewConfiguration(
allComponents.blockInstances,
allComponents.materialAssignments,
allComponents.connections,
);
}
findMergeOptions(otherConfiguration) {
// compare unconnected connectors on both configurations
}
assignMaterial(option) {
// console.log(option)
let { blockInstanceClones, materialAssignmentClones, connectionClones } = this.cloneComponents();
const newMaSettings = {};
newMaSettings.blockInstance = blockInstanceClones[option.assignable.blockInstance.id]
if (option.assignable.positionedMeshGroup) {
newMaSettings.positionedMeshGroup = option.assignable.positionedMeshGroup; //blockInstanceClones[option.assignable.positionedMeshGroup.id];
}
if (option.assignable.positionedMesh) {
newMaSettings.positionedMesh = option.assignable.positionedMesh; //blockInstanceClones[option.assignable.positionedMesh.id];
}
newMaSettings.material = option.material;
const newMA = new MaterialAssignment(
this._reporter,
newMaSettings
);
// delete old assignment for the assigning component
const assigningComponent = option.assignable.assigningComponent;
for (let [id, ma] of Object.entries(materialAssignmentClones)) {
if (ma.assigningComponent.id === assigningComponent.id) {
if (newMA.affectedComponents.length === ma.affectedComponents.length && !newMA.affectedComponents.find(ac => ma.affectedComponents.includes(ac) === undefined)) {
delete materialAssignmentClones[id];
}
}
}
materialAssignmentClones[newMA.id] = newMA;
const lastAssignedMaterials = this.lastAssignedMaterials || {};
// not every set is necessarily a mvg, but ok
for (let mvg of newMaSettings.material.materialSets) {
lastAssignedMaterials[mvg.id] = newMaSettings.material;
}
// console.log(materialAssignmentClones)
return this._createNewConfiguration(
Object.values(blockInstanceClones),
Object.values(materialAssignmentClones),
Object.values(connectionClones),
{
lastAssignedMaterials
}
);
}
setDefaultMaterial(material) {
let { blockInstanceClones, materialAssignmentClones, connectionClones } = this.cloneComponents();
const defaultMaterials = this.defaultMaterials || {};
// not every set is necessarily a mvg, but ok
for (let mvg of material.materialSets) {
defaultMaterials[mvg.id] = material;
}
return this._createNewConfiguration(
Object.values(blockInstanceClones),
Object.values(materialAssignmentClones),
Object.values(connectionClones),
{
defaultMaterials
}
);
}
clearMaterialAssignments() {
let { blockInstanceClones, materialAssignmentClones, connectionClones } = this.cloneComponents();
return this._createNewConfiguration(
Object.values(blockInstanceClones),
[],
Object.values(connectionClones)
);
}
// setTheme(blockInstances, connections, materialAssignments, theme) {
// const themeGroup = theme.group;
// const themedConfigConnectorObjects = this.connectors.filter(cntrObj => cntrObj.connector.themeGroup === themeGroup);
// const themedConfigConnectors = themedConfigConnectorObjects.map(obj => obj.connector);
// const removedConnectableIds = [];
// const connectorObjectsToConnect = [...themedConfigConnectorObjects];
// // free the themed connectors by deleting their mates if they are not
// // connected to a connectable with the correct theme
// for (let connection of Object.values(this.connections)) {
// const fromThemedConnectorObject = themedConfigConnectorObjects.find(tcco => tcco.connector === connection.from.connector && tcco.connectable === connection.from.connectable);
// const toThemedConnectorObject = themedConfigConnectorObjects.find(tcco => tcco.connector === connection.to.connector && tcco.connectable === connection.to.connectable);
// if (fromThemedConnectorObject) {
// if (themedConfigConnectors.includes(connection.to.connector)) {
// throw new Error(`Both sides of ${connection.label} have a connector with ${themeGroup.label}: ${connection.to.connector.label}, ${connection.from.connector.label}`);
// }
// if (connection.to.connector.block.theme === theme) {
// // this connector is already connected to a block with the selected theme, leave as is
// console.log(`${connection.label} already connected within theme`)
// connectorObjectsToConnect.splice(
// connectorObjectsToConnect.indexOf(fromThemedConnectorObject),
// 1
// );
// }
// else {
// delete connections[connection.id];
// removedConnectableIds.push(blockInstances[connection.to.connectable.id].id);
// delete blockInstances[connection.to.connectable.id];
// }
// }
// else if (toThemedConnectorObject) {
// if (themedConfigConnectors.includes(connection.from.connector)) {
// throw new Error(`Both sides of ${connection.label} have a connector with ${themeGroup.label}: ${connection.to.connector.label}, ${connection.from.connector.label}`);
// }
// if (connection.to.connector.block.theme === theme) {
// // this connector is already connected to a block with the selected theme, ignore
// console.log(`${connection.label} already connected within theme`)
// connectorObjectsToConnect.splice(
// connectorObjectsToConnect.indexOf(toThemedConnectorObject),
// 1
// );
// }
// else {
// delete connections[connection.id];
// removedConnectableIds.push(blockInstances[connection.from.connectable.id].id);
// delete blockInstances[connection.from.connectable.id];
// }
// }
// }
// for (let ma of Object.values(materialAssignments)) {
// if (removedConnectableIds.includes(ma.blockInstance)) {
// delete materialAssignments[ma.id];
// }
// }
// for (const themedConfigConnectorObject of connectorObjectsToConnect) {
// if (removedConnectableIds.includes(themedConfigConnectorObject.connectable.id)) {
// throw new Error(`Can't create a new connection for ${themedConfigConnectorObject.connectable.label} - it was removed`)
// }
// const mateConnector = this.pkg.connectors.find(cntr => cntr.block.theme === theme && themedConfigConnectorObject.connector.mate.connectors.includes(cntr));
// const newBI = new BlockInstance(this._reporter, { block: mateConnector.block });
// blockInstances[newBI.id] = newBI;
// const newConnection = this._createClonedComponentConnection(
// {
// from: themedConfigConnectorObject,
// to: {
// connector: mateConnector,
// connectable: 'new'
// }
// },
// blockInstances,
// newBI
// );
// connections[newConnection.id] = newConnection;
// }
// // return { blockInstances, connections, materialAssignments }
// }
applyTheme(theme) {
if (!(theme instanceof Theme)) {
throw new Error(`${theme} is not a Theme`);
}
//console.log('apply theme', theme.name)
let { blockInstanceClones, materialAssignmentClones, connectionClones } = this.cloneComponents();
// create a new theme settings object without
// changing the current settings
const configThemeSetting = this._settings.themes || {};
const newConfigThemeSetting = {};
Object.assign(newConfigThemeSetting, configThemeSetting);
newConfigThemeSetting[theme.group.id] = theme;
return this._createNewConfiguration(
Object.values(blockInstanceClones),
Object.values(materialAssignmentClones),
Object.values(connectionClones),
{
themes: newConfigThemeSetting
}
);
}
destroy() {
// Always dispose Three.js cloned assets (geometry buffers, etc.)
// to prevent GPU memory leaks. This must happen regardless of
// whether this config is a preset.
for (let asset of this.clonedAssets) {
if (asset.dispose) {
asset.dispose();
}
}
for (let elem of this.blockInstances) {
elem.destroy();
}
for (let elem of this.materialAssignments) {
elem.destroy();
}
for (let elem of this.connections) {
elem.destroy();
}
// Guard: if this config is a package preset, skip full teardown
// (nuking properties) but resources have already been disposed above.
if (this.pkg) {
for (let config of this.pkg.configurations) {
if (this.id === config.id) {
return;
}
}
}
if (super.destroy) {
super.destroy();
}
}
/**
* @param {ComponentPart} part
* @param {LoadingQuality} quality
* @param {Object<string,Component>} dependencies
* @returns {Promise<Component>}
*/
async _build(part, quality, dependencies) {
const configuration = this;
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 Group is the THREE.Object3D that will hold the "sceneable" configuration
const group = new Group();
group.castShadow = true;
group.receiveShadow = true;
const bboxLayer = Project.layerMap.boundingBoxes
/** @type {string} */
group.name = `Build of ${this.label}`;
const defaultMaterials = Object.values(this.defaultMaterials || {}).filter(unique);
const allVertices = []
for (let blockInstance of this.blockInstances) {
console.assert(blockInstance.bestMainContent instanceof Object3D);
// console.log(blockInstance.label, blockInstance.block.label)
// console.log(this.placement)
const obj3D = blockInstance.bestMainContent;
obj3D.position.copy(this.placement[blockInstance.id].position);
obj3D.quaternion.copy(this.placement[blockInstance.id].quaternion);
// console.log(this.materialAssignments.map(ma => ma.blockInstance))
const applicableMaterialAssignments = (this.materialAssignments || []).filter(ma => ma.blockInstance === blockInstance);
// sort from most to least specific
const sortedAssignments = applicableMaterialAssignments
.sort((a, b) => a.affectedComponents.length - b.affectedComponents.length);
const assignfromDefault = [];
for (let defaultMaterial of defaultMaterials) {
assignfromDefault.push({
material: defaultMaterial,
affectedComponents: blockInstance.materializableDependencies
.filter(mc =>
mc instanceof PositionedComponent && mc.component.materialVariantGroup.has(defaultMaterial)
)
})
}
// assign materials to meshes
// keep a list of processed meshes, to prevent double assigning
const materializedComponentIds = [];
for (let ma of [...sortedAssignments, ...assignfromDefault]) {
// console.log( 'apply', ma.label)
for (let ac of ma.affectedComponents) {
// console.log( 'on?', ac.label)
if (!materializedComponentIds.includes(ac.id)) {
// console.log( 'yes')
materializedComponentIds.push(ac.id);
const meshToMaterialize = obj3D.getObjectByName(ac.id);
//console.log( meshToMaterialize )
//hier nog de tiling multiplier toepassen van de mesh
//alle meshes ophalen en de bijbehorende tiling multiplpiers
const posMesh = this._tree.findComponentById( meshToMaterialize.userData.PB.origin )
//console.log( posMesh )
const wrappedMesh = posMesh.dependencies.main.component
//console.log( wrappedMesh )
if ( ! posMesh) {
console.warn('Tree referenced in upcoming error', this._tree);
throw new Error(`Could not find positioned mesh with id ${meshToMaterialize.userData.PB.origin} in tree`)
}
var tilingMultiplier = null;
for (let child of posMesh._allDependencies) {
if (child instanceof WrappedMesh) {
tilingMultiplier = child._tilingMultiplier
}
}
//get the current product maps form the mesh material
const aoMap = meshToMaterialize.material.aoMap;
const aoMApIntensity = meshToMaterialize.material.aoMapIntensity;
var normalMapRepeat = null;
//copy normalMapRepeat because of bug in threejs mapClone function
// console.log( ma.material.content.main[quality].normalMap )
if (ma.material.content.main[quality].normalMap instanceof Texture) {
normalMapRepeat = ma.material.content.main[quality].normalMap.repeat
//console.log( normalMapRepeat )
}
//check if current mesh needs a node material
if( wrappedMesh._normalMap ){
console.log( 'node material')
meshToMaterialize.material = await wrappedMesh.buildMeshMaterial( ma.material.content.main[quality].clone() )
meshToMaterialize.material.needsUpdate = true;
}
else{
//apply new material
meshToMaterialize.material = ma.material.content.main[quality].clone();
}
//apply product maps from previous mesh material
meshToMaterialize.material.aoMap = aoMap
meshToMaterialize.material.aoMapIntensity = aoMApIntensity
if (meshToMaterialize.material.aoMap instanceof Texture) {
meshToMaterialize.material.aoMap.needsUpdate = true;
}
//apply tiling multiplier to maps
for (let mapType of ['bumpMap', 'emissiveMap', 'map', 'specularMap', 'roughnessMap', 'metalnessMap', 'normalMap']) {
if (meshToMaterialize.material[mapType] instanceof Texture) {
const mapClone = meshToMaterialize.material[ mapType ].clone();
// const mapClone = meshToMaterialize.material[mapType];
this.clonedAssets.push(mapClone);
// console.log( mapClone )
mapClone.repeat.x = mapClone.repeat.x * tilingMultiplier
mapClone.repeat.y = mapClone.repeat.y * tilingMultiplier
mapClone.needsUpdate = true;
meshToMaterialize.material[mapType] = mapClone;
// K: Joost removed this, unneccesary?
// meshToMaterialize.material.needsUpdate = true;
}
if (meshToMaterialize.material['normalMap'] instanceof Texture) {
meshToMaterialize.material['normalMap'].repeat = normalMapRepeat;
}
}
meshToMaterialize.material.needsUpdate = true;
//console.log( meshToMaterialize )
}
}
}
// populate assigned material list
this.assignedMaterials[blockInstance.id] = blockInstance.assignables.map(assignable => ({
assignable,
materials: [],
mvg: assignable.materialVariantGroups.reduce((obj, mvg) => Object.assign(obj, { [mvg.id]: null }), {})
})
);
obj3D.traverse(subObj => {
if (subObj.material) {
const assignable = subObj.userData.PB.assignable;
const materialId = subObj.material.userData.PB.origin;
if (!assignable) {
console.error(subObj);
throw new Error(`Missing assignable on Object3D ${subObj.id}`);
}
const material = this._tree.findComponentById(subObj.material.userData.PB.origin);
const assignedMaterialsEntry = this.assignedMaterials[blockInstance.id].find(assMatEntry => assMatEntry.assignable === assignable);
if (!assignedMaterialsEntry) {
console.error(assignable);
throw new Error('Could not find assigned materials list entry');
}
if (assignedMaterialsEntry.materials.indexOf(material) === -1) {
assignedMaterialsEntry.materials.push(material);
for (let set of material.materialSets) {
// the reason for this check, is that material.materialSets
// returns all sets the material is in, not just the ones that
// are material variant groups
// if ( assignedMaterialsEntry.mvg[ set.id ] !== undefined ) {
assignedMaterialsEntry.mvg[set.id] = material;
// }
}
}
}
});
// console.log(this.assignedMaterials[ blockInstance.id ]);
// traverse 3d structure, keep track of actual materials per assignable per mvg
// find obj3D.userData.PB.assignableComponent
//
// for ( let assignable of blockInstance.assignables ) {
// // for every assignable.materialVariantGroups
// let mostCongruentAssignment =
// let definingObj3D = obj3D;
// console.log(definingObj3D)
// if ( assignable.positionedMeshGroup) {
// definingObj3D = definingObj3D.getObjectByName( assignable.positionedMeshGroup.id );
// console.log(definingObj3D)
// }
// if ( assignable.positionedMesh) {
// definingObj3D = definingObj3D.getObjectByName( assignable.positionedMesh.id );
// console.log(definingObj3D)
// }
// console.log(definingObj3D)
// this.assignedMaterials.push({
// ...assignable,
// material: this.tree.findComponentById( definingObj3D.material.userData.origin )
// })
// }
// console.log(this.assignedMaterials)
const transformWrapper = new Object3D();
transformWrapper.castShadow = true;
transformWrapper.receiveShadow = true;
transformWrapper.name = `${blockInstance.label}-transform-wrapper`;
transformWrapper.add(obj3D);
group.add(transformWrapper);
group.userData.PB = {
origin: this.id
};
const bbox = this.boundingBoxes.get(blockInstance.id) //blockInstance.boundingBox.clone()
//group.add(transformer);
//group.userData.origin = this.id;
//blockInstance.boundingBox.computeBoundingBox()
var boxHelper = new Box3Helper(blockInstance.boundingBox.boundingBox, 0xFF0000)
boxHelper.layers.set(bboxLayer)
group.add(boxHelper)
//vertex positions
if( bbox ){
const vPosition = bbox.attributes.position;
const vector = new Vector3();
var boxGeo = new BoxGeometry(0.02, 0.02, 0.02)
var material = new MeshBasicMaterial({ color: 0xFF0000 })
var mesh = new Mesh(boxGeo, material)
mesh.name = "Vertex Helper"
for (let i = 0, l = vPosition.count; i < l; i++) {
vector.fromBufferAttribute(vPosition, i);
//vector.applyMatrix4( child.matrixWorld );
const vertex = mesh.clone()
this.clonedAssets.push(vertex);
vertex.layers.set(bboxLayer)
vertex.position.copy(vector)
group.add(vertex)
//console.log(vector);
}
}
}
// console.log(this.assignedMaterials);
//compute total bbox
var boxHelper = new Box3Helper(this.boundingBox.boundingBox, 0xFF0000)
boxHelper.layers.set(bboxLayer)
group.add(boxHelper)
//draw dimenions lines
const offset = 0.25
//if configuration is not empty then add dimension lines
if (this.boundingBox.boundingBox.max.z !== 0) {
var dimLineFront = drawDimensionsLine(group, this.dimensions.dimFront, offset, "front")
var dimLineLeft = drawDimensionsLine(group, this.dimensions.dimLeft, offset, "left")
//var dimLineHeight = drawDimensionsLine(group, this.dimensions.dimHeight, offset, "height")
//var dimLineBack = drawDimensionsLine( configScene, dimensions.dimBack, offset, "back" )
//var dimLineRight = drawDimensionsLine( configScene, dimensions.dimRight, offset, "right" )
}
this._setContent('main', quality, group);
const config = this;
setTimeout(config.prepareComponentClones.bind(config), 10);
break;
default:
this._setContent(part, quality, null);
break;
}
return this;
}
}
// mega dirty hack
// allows ComponentTree to use Configurator
// window.Configuration = Configuration;
ComponentTree.Configuration = Configuration;
export { Configuration }