import { Vector3, Quaternion, Euler } from '../../node_modules/three/build/three.module.js';
import { BlockInstance } from "../configurator/block_instance.js";
import { Connection } from "../configurator/connection.js";
/**
* Find a connection between block instance sets
* @param {Array<BlockInstance>} blockInstanceSetA
* @param {Array<BlockInstance>} blockInstanceSetB
* @param {Array<Connection>} connections
* @returns {(Object|null)} connectionData
*/
const findConnection = (blockInstanceSetA, blockInstanceSetB, connections) => {
let blockInA = null;
let blockInB = null;
let fromSet = null;
let toSet = null;
// find a connection FROM a connectable in set A TO
// a connectable in set B
let connectionToUse = connections.find(connection =>
blockInstanceSetA.includes(connection.from.connectable)
&&
blockInstanceSetB.includes(connection.to.connectable)
);
if (connectionToUse) {
blockInA = connectionToUse.from;
blockInB = connectionToUse.to;
fromSet = blockInstanceSetA;
toSet = blockInstanceSetB;
}
// if no from:A-to:B connection was found, find a
// a connection from a connectable in set B to a connectable in set A
else {
connectionToUse = connections.find(connection =>
blockInstanceSetB.includes(connection.from.connectable)
&&
blockInstanceSetA.includes(connection.to.connectable)
);
if (connectionToUse) {
blockInA = connectionToUse.to;
blockInB = connectionToUse.from;
fromSet = blockInstanceSetB;
toSet = blockInstanceSetA;
}
}
if (!connectionToUse) {
return null
}
else {
return {
connection: connectionToUse,
from: fromSet,
to: toSet,
blockInA,
blockInB
};
}
}
/**
* Calculate the position of the blockInstances and their connectors
* @param {Configuration} configuration
* @returns {Object}
*/
const calculatePlacement = (configuration) => {
// object to store the calculated values in,
// which will eventually be returned
const placement = {}
// check that there are "things to place"
console.assert(Array.isArray(configuration.blockInstances) && configuration.blockInstances.length > 0, 'No block instances in configuration');
// The blockInstance's positions will be calculated one by one. Because it is
// not a given that their order in the configuration's settings array is the
// order in which they are connected, the next "calculable" blockInstance
// is determined for every step, by find an anchor and going working from there
// for the first step, simply use the first block in the array as anchor
// set the first block's position and quaternion to default values
placement[configuration.blockInstances[0].id] = {
position: new Vector3(),
quaternion: new Quaternion(),
type: 'connectable'
};
// remove the first block from the "To-Do list" and add to the "done list"
const uncalculatedBlockInstances = configuration.blockInstances.slice(1, configuration.blockInstances.length);
const calculatedBlockInstances = [configuration.blockInstances[0]];
// calculate the placements
const nrOfPlacementsToCalculate = uncalculatedBlockInstances.length;
for (let i = 0; i < nrOfPlacementsToCalculate; i += 1) {
// find a connection between the set of "calculated" blockInstances
// and the "yet to calculate" ones
// console.log(
// "log!!!!!!",
// i,
// calculatedBlockInstances.map(cbi => cbi.id),
// uncalculatedBlockInstances.map(ucbi => ucbi.block.id),
// configuration.connections
// )
const connectionData = findConnection(
calculatedBlockInstances,
uncalculatedBlockInstances,
configuration.connections
);
if (!connectionData) {
throw new Error(`Unbuildable configuration. Missing connection between placed and unplaced block instances.`);
}
const connection = connectionData.connection;
const anchor = connectionData.blockInA;
const target = connectionData.blockInB;
// sanity checking
console.assert(anchor.connector.quaternion instanceof Quaternion);
console.assert(connection.quaternion instanceof Quaternion);
console.assert(connection.inverseQuaternion instanceof Quaternion);
console.assert(target.connector.quaternion instanceof Quaternion);
// determine the vector from the anchoring blockInstance to the connection point
// by taking the connector position and rotating it according to the
// blockInstance's rotation
const anchorToConnectionVector = anchor.connector.position.clone();
anchorToConnectionVector.applyQuaternion(placement[anchor.connectable.id].quaternion);
// console.debug('anchorToConnectionVector', anchorToConnectionVector);
// Determine the vector from the connection point to the target position
// by taking the inverse vector to the target connector and rotating it
// according to (1) the anchor connector rotation, (2) the connection rotation and
// (3) the inverse target connector rotation
const connectionToTargetVector = target.connector.position.clone().negate();
const connectionToTargetQuaternion = new Quaternion();
connectionToTargetQuaternion.copy(placement[anchor.connectable.id].quaternion);
// console.debug('step 0', new Euler().setFromQuaternion(connectionToTargetQuaternion))
// connection to target quaternion step 1
connectionToTargetQuaternion.multiply(anchor.connector.quaternion);
// console.debug('step 1', new Euler().setFromQuaternion(connectionToTargetQuaternion))
// connection to target quaternion step 2
// the connection rotaion is specified as a quaternion
// between the from-connector to the to-connector, so if the
// anchor connector is the "to-connector" of the connection
// use the inverse quaternion
if (connectionData.from === calculatedBlockInstances) {
connectionToTargetQuaternion.multiply(connection.quaternion);
}
else {
connectionToTargetQuaternion.multiply(connection.inverseQuaternion);
}
// console.debug('step 2', new Euler().setFromQuaternion(connectionToTargetQuaternion))
connectionToTargetQuaternion.multiply(Connection.baseQuaternion);
// console.debug('step 3', new Euler().setFromQuaternion(connectionToTargetQuaternion))
// connection-to-target-quaternion step 3
connectionToTargetQuaternion.multiply(
target.connector.quaternion.clone().invert()
);
// console.debug('step 4', new Euler().setFromQuaternion(connectionToTargetQuaternion))
// console.debug('connectionToTargetQuaternion', connectionToTargetQuaternion);
// apply the connection-to-target-quaternion to the connection-to-target-vector
// the vector now points to the correct position from the connection point
connectionToTargetVector.applyQuaternion(connectionToTargetQuaternion);
// console.debug( 'connectionToTargetVector', connectionToTargetVector)
// determine the final position of the target by taking the position
// of the anchor as the starting position
const targetPosition = placement[anchor.connectable.id].position.clone();
// add the vector to the connection point
targetPosition.add(
anchorToConnectionVector
);
// add the vector from the connection point to the target position
targetPosition.add(
connectionToTargetVector
);
// determine the final quaternion by taking the anchor's quaternion
// as starting point
// const targetQuaternion = new Quaternion();
// targetQuaternion.multiply(placement[anchor.connectable.id].quaternion);
// multiply with the quaternion from the anchor's rotation
// to the target rotation
// targetQuaternion.multiply(connectionToTargetQuaternion);
// add the calculated values to the placement register to return afterwards
// include debug data for testing purposes
placement[target.connectable.id] = {
position: targetPosition,
quaternion: connectionToTargetQuaternion,
debug: {
anchorTo: anchor,
connection,
anchorToConnectionVector,
connectionToTargetVector,
},
type: 'connectable'
};
const connectionPosition = placement[anchor.connectable.id].position.clone().add(anchorToConnectionVector);
placement[connection.id] = {
position: connectionPosition,
// quaternion: connectionToTargetQuaternion, // connection has two quaternions, before and after..
debug: {
anchorTo: anchor,
connection,
anchorToConnectionVector,
connectionToTargetVector,
},
type: 'connection'
};
// do bookkeeping - remove the calculated block from the "To-Do" list
// and add it to the "done" list
calculatedBlockInstances.push(
...uncalculatedBlockInstances.splice(
uncalculatedBlockInstances.indexOf(target.connectable),
1
)
);
}
return placement
}
// const group = new Group();
// group.name = `Build of ${this.label}`;
// // Initialize the configuration build by placing the first blockInstance
// // in the group. This could have been any blockInstance, so the first one is fine.
// console.assert(this.blockInstances[0].content.main[quality] instanceof Object3D);
// //Add Boundingbox tofirst block
// const startBlock = this.blockInstances[0].content.main[quality]
// const startBlockBBox = this.blockInstances[0].block.boundingBox
// console.log( startBlockBBox )
// //startBlock.add( startBlockBBox )
// if( startBlockBBox instanceof Mesh ){
// //const boundingBoxHelper = new Box3Helper( startBlockBBox, 0xff0000 );
// //boundingBoxHelper.layers.set(1)
// startBlock.add( startBlockBBox )
// }
// group.add( startBlock );
// console.log( startBlock )
// //
// // Algo:
// // 1. Place first block
// // 2. Find a connection between a block that has been "placed" and one that is "unplaced"
// // 3. Place that block
// // 4. Go to 2.
// const placedBlockInstances = [this.blockInstances[0]];
// //console.log( this.blockInstances.slice( 1, this.blockInstances.length ) )
// const unplacedBlockInstances = this.blockInstances.slice(1, this.blockInstances.length)
// const nrOfBlocksToPlace = unplacedBlockInstances.length;
// // console.log( 'placedBlockInstances', placedBlockInstances )
// // console.log( 'unplacedBlockInstances', unplacedBlockInstances )
// for (let i = 0; i < nrOfBlocksToPlace; i += 1) {
// // console.log( 'Find next block to place in unplacedBlockInstances', unplacedBlockInstances );
// let placedConnectable = null;
// let unplacedConnectable = null;
// //FIND CONNECTION TO USE
// //get the connections from placed vs not placed blocks
// let connectionToUse = this.connections.find(connection =>
// placedBlockInstances.includes(connection.from.connectable)
// &&
// unplacedBlockInstances.includes(connection.to.connectable)
// );
// //get from -> to connectables
// if (connectionToUse) {
// unplacedConnectable = connectionToUse.from;
// placedConnectable = connectionToUse.to;
// }
// //if not found reverse and get the connecions from not placed vs placed blocks
// else {
// connectionToUse = this.connections.find(connection =>
// unplacedBlockInstances.includes(connection.from.connectable)
// &&
// placedBlockInstances.includes(connection.to.connectable)
// );
// }
// //get from -> to connectables from reverse
// if (connectionToUse) {
// unplacedConnectable = connectionToUse.to; // this needs to match the connection distribution!!
// placedConnectable = connectionToUse.from;
// }
// //if no connections found then give error
// else {
// // console.error({ placedBlockInstances, unplacedBlockInstances, connections: this.connections });
// throw new Error(`Unbuildable configuration. Missing connection between placed and unplaced block instances.`);
// }
// //get the blockInstance of the unplaced block (unplacedConnectable)
// const object3DToPlace = unplacedConnectable.connectable.content.main[quality];
// const object3DToPlaceBBox = unplacedConnectable.connectable.block.boundingBox
// if( object3DToPlaceBBox instanceof Box3 ){
// const boundingBoxHelper = new Box3Helper( object3DToPlaceBBox, 0xff0000 );
// boundingBoxHelper.layers.set(1)
// object3DToPlace.add( boundingBoxHelper )
// }
// console.log( unplacedConnectable )
// //chek if the block is a Object3D
// console.assert(object3DToPlace instanceof Object3D);
// //add the block to the config group
// group.add(object3DToPlace);
// //cube helper
// const geometry = new BoxGeometry( 0.3, 0.3, 0.3 );
// const material = new MeshBasicMaterial( {color: 0x00ff00} );
// const cubeHelper = new Mesh( geometry, material );
// //group.add( cube )
// //--FROM BLOCK
// const placedBlockPosition = placedConnectable.connectable.content.main[quality].position.clone();
// const placedBlockQuaternion = placedConnectable.connectable.content.main[quality].quaternion.clone();
// //--FROM CONNECTOR
// //get the position and quaternion of the placed block connector
// const originalConnectorPosition = placedConnectable.connector.position.clone();
// const originalConnectorQuaternion = placedConnectable.connector.quaternion.clone();
// //rotate & translate the placed connector position & quaternion
// const originalConnectorRotated = originalConnectorPosition.applyQuaternion( placedBlockQuaternion ) //rotate position vector
// const placedFromConnectorPosition = originalConnectorRotated.add( placedBlockPosition ) //translate position
// const placedFromConnectorQuaternion = originalConnectorQuaternion.multiply( placedBlockQuaternion ) //rotate quaternion
// const inverseFromQuaternion = placedFromConnectorQuaternion.multiply( { _w:0, _x:0, _y:1, _z:0 } ) //rotate quaternion 180 degrees
// //HALFWAY POSITION & ROTATION
// //cubeHelper.position.copy( placedFromConnectorPosition )
// object3DToPlace.position.copy( placedFromConnectorPosition ); //endPosition
// object3DToPlace.quaternion.multiply( placedFromConnectorQuaternion ); //endInverseQuaternion
// //--TO CONNECTOR
// //get the local postion and orientation of the unplaced connector
// const unplacedConnectorPosition = unplacedConnectable.connector.position.clone(); //get the toConnector vector
// const invertedConnectorPosition = unplacedConnectorPosition.negate() //invert toConnector vector
// const rotatedToConnectorPosition = invertedConnectorPosition.applyQuaternion( inverseFromQuaternion ) //apply 180 degrees rotation to vector
// //END POSITION & ROTATION
// //cubeHelper.position.add( rotatedToConnectorPosition )
// object3DToPlace.position.add( rotatedToConnectorPosition ); //endPosition
// placedBlockInstances.push(
// ...unplacedBlockInstances.splice(
// unplacedBlockInstances.indexOf(unplacedConnectable.connectable),
// 1
// )
// );
// }
export { calculatePlacement, findConnection }