import { Configuration } from './configuration.js';
import { Reporter } from '../reporter/reporter.js';
import { Package } from '../package/package.js';
import { Group, Vector3, Quaternion } from '../../node_modules/three/build/three.module.js';
import { checkPropTypes } from '../lib.js';
import { Actor } from '../actor/actor.js';
import { BuildableComponent } from '../package/component/buildable_component.js';
import { ConfigurationInfo } from './configuration_info.js';
import { BlockInstance } from './block_instance.js';
/**
* Configurator
* @extends Actor
*/
class Configurator extends Actor {
/**
* @param {Reporter} reporter
* @param {Object} settings
* @param {UUID} [settings.id]
* @param {Package} settings.pkg
* @param {Configuration} [settings.configuration]
* @param {Vector3} [settings.position]
* @param {Quaternion} [settings.quaternion]
*/
constructor(reporter, settings) {
super(reporter, settings);
checkPropTypes(
settings,
{
pkg: Package
},
{
configuration: Configuration,
position: Vector3,
quaternion: Quaternion,
}
);
this.pkg = settings.pkg;
const initialBodyContent = new Group();
initialBodyContent.name = "Configurator Group"
const initialConfig = settings.configuration || new Configuration(
this._reporter,
{
pkg: settings.pkg,
// info: Configuration.generateInfoComponent(this._reporter, this.pkg),
name: 'Configurator autogenerated initial config',
blockInstances: []
}
);
initialConfig
.build()
.then(() => {
this.updateBody(initialConfig.content.main.medium);
})
// initialConfig._setContent('main', this._loadingQuality, initialBodyContent);
this._stateRegister = [
{
configuration: initialConfig,
position: settings.position || new Vector3(),
quaternion: settings.quaternion || new Quaternion(),
}
];
this.body.position.copy(this.state.position);
this.body.quaternion.copy(this.state.quaternion);
}
/**
* @typedef {Object} ConfiguratorState
* @property {Configuration} configuration
* @property {Vector3} position
* @property {Quaternion} quaternion
* @property {string} [description]
*/
/** @type {LoadingQuality} */
_loadingQuality = 'medium';
/** @param {Object} selectedState */
async onStateCursorMoved(selectedState) {
// console.log(selectedState)
if (selectedState.configuration.status.main[this._loadingQuality] !== 'ready') {
await selectedState.configuration.build({ part: 'main', quality: this._loadingQuality, highPriority: true });
}
return this.updateBody(
selectedState.configuration.content.main[this._loadingQuality],
selectedState.quaternion,
selectedState.position,
selectedState.modTransform
);
}
get options() {
const optionObj = { ...this.configuration.options, ...this.configuration.instructions };
if (this._cursor < this._stateRegister.length - 1) {
optionObj.redo = this.redo.bind(this)
}
if (this._cursor > 0) {
optionObj.undo = this.undo.bind(this)
}
return optionObj;
}
/**
* Current configuration
* @type {Configuration}
*/
get configuration() {
// console.log(this._stateRegister)
// console.error(this._cursor)
return this._stateRegister[this._cursor]?.configuration;
}
/**
* Current position
* @type {Vector3}
*/
get position() {
return this._stateRegister[this._cursor].position;
}
/**
* Current quaternion
* @type {Quaternion}
*/
get quaternion() {
return this._stateRegister[this._cursor].quaternion;
}
/**
* @typedef {Object} GenericUpdateResult
* @property {Configuration} configuration - the current configuration
* @property {Vector3} position - current body position
* @property {Quaternion} quaternion - current body quaternion
* @property {import("./configuration.js").Placement} placement
*/
/**
* Applies an insert option to the current configurations
* @param {import("./configuration.js").InsertOption} option
* @returns {Promise<GenericUpdateResult>}
*/
async insert(option) {
const result = this.configuration.insert(option);
await this.update(
result,
`Inserted ${option.block.label} in step ${this.configuration.step}`
);
return { ...this.state };
}
/**
* Applies an insert option to the current configurations
* @param {import("./configuration.js").ExtendOption} option
* @returns {Promise<GenericUpdateResult>}
*/
async extend(option) {
const result = this.configuration.extend(option);
await this.update(
result,
`Extended with ${option.block.label} in step ${this.configuration.step}`
);
return { ...this.state };
}
/**
* @typedef {Object} CutResult
* @property {ConfiguratorState} main - current configuration wo the cut part
* @property {ConfiguratorState} cut - new configuration with the cut block instance
*/
/**
* Applies an insert option to the current configurations
* @param {import("./configuration.js").CutOption} option
* @returns {Promise<CutResult>}
*/
async cut(option) {
const baseCorrection = { position: this.state.position, quaternion: this.state.quaternion };
const result = this.configuration.cut(option);
const cutCorrection = this.calcPosCorrection(result.cut, this.configuration);
// console.log(cutCorrection, this.position)
await this.update(
result.main,
`${this.configuration.label} after cutting out ${option.blockInstance.label} in step ${this.configuration.step}`
);
return {
main: this.state,
cut: {
configuration: result.cut,
position: cutCorrection.position.add(baseCorrection.position),
quaternion: cutCorrection.quaternion.multiply(baseCorrection.quaternion),
description: `Cut out ${option.blockInstance.label} of ${this.configuration.label} step ${this.configuration.step}`
}
};
}
/**
* @typedef {Object} SplitResult
* @property {ConfiguratorState} cut new configurator with the cut block instance
* @property {Array<ConfiguratorState>} split new configurator for each of the split sibling chains
*/
/**
* Applies an insert option to the current configurations
* @param {import("./configuration.js").SplitOption} option
* @returns {Promise<SplitResult>}
*/
async split(option) {
const baseCorrection = { position: this.state.position, quaternion: this.state.quaternion };
const result = this.configuration.split(option);
const cutCorrection = this.calcPosCorrection(result.cut, this.configuration);
const splitConfigurations = result.split.map(config => {
const chainCorrection = this.calcPosCorrection(config, this.configuration)
return {
configuration: config,
position: chainCorrection.position.add(baseCorrection.position),
quaternion: chainCorrection.quaternion.multiply(baseCorrection.quaternion),
description: `Sibling chain of splitting ${this.configuration.label} step ${this.configuration.step}`
};
});
return {
cut: {
configuration: result.cut,
position: cutCorrection.position.add(baseCorrection.position),
quaternion: cutCorrection.quaternion.multiply(baseCorrection.quaternion),
description: `Cut out ${option.blockInstance.label} when splitting ${this.configuration.label} step ${this.configuration.step}`
},
split: splitConfigurations
};
}
async assignMaterial(option) {
const baseCorrection = { position: this.state.position, quaternion: this.state.quaternion };
const newConfig = this.configuration.assignMaterial(option);
await this.update(
newConfig,
`${this.configuration.label} after assigning ${option.material.label} to ${option.blockInstance.label} in step ${this.configuration.step}`
);
return newConfig;
}
async setTheme(theme) {
const newConfig = this.configuration.setTheme(theme);
await this.update(
newConfig,
`${this.configuration.label} after setting ${theme.label} in step ${this.configuration.step}`
);
return newConfig;
}
static get defaultPlacementObject() {
return {
position: new Vector3(),
quaternion: new Quaternion()
};
}
calcPosCorrection(configurationToPlace, anchor) {
const correction = Configurator.defaultPlacementObject;
// 1. figure out rotational correction
/** @type {Array<Array>} */
const quatCorrs = [[new Quaternion(), 0]];
for (let newBI of configurationToPlace.blockInstances) {
if (anchor.placement[newBI.id]) {
// console.log('Persistent BI:', newBI.id, 'old x', this.configuration.placement[newBI.id].position.x, 'new x', configurationToPlace.placement[newBI.id].position.x)
const corQuat = new Quaternion()
.copy(anchor.placement[newBI.id].quaternion)
.conjugate()
.multiply(configurationToPlace.placement[newBI.id].quaternion);
const existingQuatCorr = quatCorrs.find(rotCorrection =>
rotCorrection[0].equals(corQuat)
);
if (existingQuatCorr) {
existingQuatCorr[1] += 1;
}
else {
quatCorrs.push([corQuat, 1]);
}
}
else {
// console.log('New BI:', newBI.id)
}
}
// console.log(quatCorrs);
const quatCorr = quatCorrs.sort((a, b) => b[1] - a[1])[0][0];
// 2. figure out positional correction with applied rot cor
const posCorrs = [[new Vector3(), 0]];
for (let newBI of configurationToPlace.blockInstances) {
if (anchor.placement[newBI.id]) {
const posCor = new Vector3().subVectors(
anchor.placement[newBI.id].position.clone().applyQuaternion(quatCorr),
configurationToPlace.placement[newBI.id].position.clone().applyQuaternion(quatCorr),
);
const existingCorrection = posCorrs.find(positionalCorrection =>
positionalCorrection[0].equals(posCor)
);
if (existingCorrection) {
existingCorrection[1] += 1;
}
else {
posCorrs.push([posCor, 1]);
}
}
}
// console.log(posCorrs);
const posCorr = posCorrs.sort((a, b) => b[1] - a[1])[0][0];
// console.log(posCorr)
correction.position = posCorr;
correction.quaternion = quatCorr;
return correction;
}
/**
* @param {Configuration} newConfiguration
* @param {string} description
* @param {Object} modTransform
* @param {Vector3} modTransform.translation
* @param {Quaternion} modTransform.quaternion
* @returns {Promise<Object>}
*/
async update(newConfiguration, description, modTransform = {}) {
if (!(newConfiguration instanceof Configuration)) {
throw new Error('Configurator update parameter is not an instance of Configuration');
}
if (newConfiguration.pkg !== this.pkg) {
throw new Error('Configuration is of a different package');
}
const correction = this.calcPosCorrection(newConfiguration, this.configuration);
let newState = {
configuration: newConfiguration,
position: correction.position.add(this.position),
quaternion: correction.quaternion.multiply(this.quaternion),
modTransform,
description,
};
const cleanUpState = function({ state, stateRegister, maxStateCount }) {
console.log('Configurator state clean up')
// if the configuration in this state is not in the relevant states anywhere, destroy it
let configurationStillRelevant = false;
for ( let i = 0 ; i < maxStateCount ; i += 1 ) {
if ( stateRegister[ i ].configuration === state.configuration ) {
configurationStillRelevant = true;
}
}
if ( ! configurationStillRelevant ) {
console.log('Destroy old configuration')
state.configuration.destroy();
}
state.configuration = null;
state = null;
}
newState.cleanUp = cleanUpState.bind(this);
await this.addState({
newState,
moveCursor: true,
});
return this.options;
}
/**
* Merge another configuration into this one
* @param {Configuration} configuration
* @returns {Configuration}
*/
merge(configuration, blockInstance, connector,) {
}
async move(newPosition, newQuaternion) {
await this.addState({
newState: {
configuration: this.configuration,
position: newPosition || this.position,
quaternion: newQuaternion || this.quaternion,
description: `Moved configuration`
},
moveCursor: true,
});
return this.options;
}
}
export { Configurator }