import { Block } from '../package/block/block.js';
import { Reporter } from '../reporter/reporter.js';
import { checkPropTypes, unique } from '../lib.js';
import { Connectable } from './connectable.js';
import { Component } from '../component/component.js';
import { Group, Vector2, Vector3, Box3, BufferGeometry, BufferAttribute, Matrix4, Quaternion } from '../../node_modules/three/build/three.module.js';
import { Project } from '../project.js';
// project.configurators[0].configuration.setBITextureRepeat( project.configurators[0].configuration.blockInstances[0].id, 2,3)
/**
* Block instance
*/
class BlockInstance extends Connectable {
/**
* @param {Reporter} reporter
* @param {Object} settings
* @param {UUID} [settings.id]
* @param {string} [settings.name]
* @param {Block} settings.block
* @param {Vector2} [settings.textureRepeat]
* @param {number} [settings.textureRotation]
* @param {Vector2} [settings.textureOffset]
*/
constructor(reporter, settings) {
checkPropTypes(
settings,
{
block: Block
},
{
textureRepeat: Vector2,
textureOffset: Vector2,
textureRotation: 'number',
scale: Vector3,
}
);
super(
reporter,
settings
);
// this.textureRepeat = new Vector2(10,10);
this.bestMainContent = new Group();
this.bestMainContent.castShadow = true;
this.bestMainContent.receiveShadow = true;
if (!this.block) {
/** @type {Block} */
this.block = settings.block;
}
this.assignableByDefinerId = {};
this.assignables = this.block.subAssignables.map(sa => ({
blockInstance: this,
...sa
}));
if (this.assignable === true) {
this.assignables.push({
blockInstance: this,
assigningComponent: this,
materialVariantGroups: this.applicableMaterialVariantGroups,
name: this.block.name || this.block.slug
});
}
for (let assignable of this.assignables) {
this.assignableByDefinerId[assignable.assigningComponent.id] = assignable;
const biAssId = `BI:${this.slug}`;
assignable.id = assignable.id ? `${biAssId}-${assignable.id}` : biAssId;
}
this.boundingBox = settings.block.boundingBox;
this.boundingBoxSettings; //<- this is currently double??
}
static _exportName = {
singular: 'blockInstance',
plural: 'blockInstances',
}
/** @type {ExportLevel} */
static _exportLevel = 'inline';
static connectorTemplateSetting = 'block';
static treeLock = false;
/**
* @type {Object<UUID,Component>}
*/
assignableByDefinerId;
/**
* Whether a block instance is assignable depends on the block settings
* @type {Boolean}
*/
get assignable() {
return this.block.assignable;
}
/**
* A block instance, like a block, does not have a material
* @type {Boolean}
*/
get materializable() {
return false;
}
_onTreeSet() {
if (!this._tree.findComponentById(this.block.id)) {
throw new Error('Block (template) not in tree');
}
super._onTreeSet();
}
_onTreeUnset() {
super._onTreeUnset();
}
/**
* Set content and update status to "ready"
* @protected
* @method
* @param {ComponentPart} part
* @param {LoadingQuality} quality
* @param {any} content
* @returns {Promise<Array<any>>}
*/
_setContent(part, quality, content) {
// super will do this again
this._content[part][quality] = content;
if (part === 'main') {
while (this.bestMainContent.children.length > 0) {
this.bestMainContent.remove(this.bestMainContent.children[0]);
}
const bestContent = this._content.main.medium;
// const bestContent = this._content.main.high !== null
// ? this._content.main.high
// : this._content.main.medium !== null
// ? this._content.main.medium
// : this._content.main.low !== null
// ? this._content.main.low
// : this.mainPlaceholder !== undefined
// ? this.mainPlaceholder
// : null
// ;
if (bestContent !== null) {
// console.log( bestContent )
this.bestMainContent.add(bestContent)
}
}
return super._setContent(part, quality, content);
}
bestMainContent;
// get materials including defaults from package
get appliedMaterials() {
if (!this._tree) {
return [];
}
else {
const mats = [];
// assignedMaterials also has the default materials from the package
for ( let biAssignments of Object.values(this._tree.assignedMaterials) ){
for ( let biAssignment of biAssignments) {
if ( biAssignment.assignable.blockInstance === this ) {
mats.push(...biAssignment.materials);
}
}
}
return mats.filter(unique);
}
}
// get materials that were assigned (configured)
get assignedMaterials() {
if (!this._tree) {
return [];
}
else {
// materialAssignments only has the actual "assignments", not the default materials
return this._tree.materialAssignments.filter(ma => ma.blockInstance === this);
}
}
// Get new boundingBox Settings based on the scale
get boundingBoxSettings(){
// Use the blockInstance.scale setting to calculate the new dimensions.
//console.log( this.settings, this.scale )
//console.log( this.block.dimensions )
//console.log( this.block.boundingBox )
let x,y,z;
// Calculate the new boundingbox dimeniosn based on the new scale.
if( !this.scale ){
//console.log( "scale is undefined")
x = this.block.dimensions.x
y = this.block.dimensions.y
z = this.block.dimensions.z
}
else{
x = this.block.dimensions.x * this.scale.x
y = this.block.dimensions.y * this.scale.y
z = this.block.dimensions.z * this.scale.z
}
const boxMin = new Vector3( -x/2, -y/2, -z/2 )
const boxMax = new Vector3( x/2, y/2, z/2 )
//Math.round( line.distance() * 100 )
//create boundingbox
const boundingBox = new Box3( boxMin, boxMax )
//create vertices
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
const boundingBoxGeometry = new BufferGeometry()
boundingBoxGeometry.name = "boundingBox";
boundingBoxGeometry.visible = false;
boundingBoxGeometry.setAttribute( 'position', new BufferAttribute( vertices, 3 ) );
const matrix = new Matrix4();
const qat = new Quaternion();
const scale = new Vector3( 1, 1, 1)
matrix.compose( this.block.dimensionsCenter, qat, scale )
boundingBoxGeometry.applyMatrix4( matrix )
//boundingBoxGeometry.computeBoundingBox()
//boundingBoxGeometry.layers.set( Project.layerMap.boundingBox );
this.boundingBox = boundingBoxGeometry
//console.log( this.boundingBox.boundingBox )
//return boundingBoxGeometry
}
async _build(part, quality, dependencies) {
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':
const block = this._settings.block.content.main[quality].block;
const blockClone = block.clone();
blockClone.castShadow = true;
blockClone.receiveShadow = true;
if ( this.scale) {
blockClone.scale.copy(this.scale);
}
// so we can use Object3D.getObjectByName in the scene
// https://threejs.org/docs/#api/en/core/Object3D.getObjectByName
blockClone.name = this.id;
// blockClone.userData.origin = this.id;
blockClone.userData.PB = {
type: 'blockInstance',
origin: this.id,
blockInstance: this,
isAssignable: this.assignable,
assignableComponent: this.assignable === true
? {
blockInstance: this
}
: null
};
// export the cloned block and its connectors
const content = blockClone;
blockClone.traverse(obj3D => {
if (!obj3D.userData) {
obj3D.userData = {};
}
if (!obj3D.userData.PB) {
obj3D.userData.PB = {};
}
if (obj3D.userData.PB.type === 'PositionedMeshGroup') {
obj3D.userData.PB.positionedMeshGroup = obj3D.userData.PB.origin;
}
else {
// could be undefined or a value, but copy it
// the parent here could be the configuration wrapper, which has no PB userdata
obj3D.userData.PB.positionedMeshGroup = obj3D.parent?.userData?.PB?.positionedMeshGroup;
}
if (obj3D.userData.PB.type === 'PositionedMesh') {
obj3D.userData.PB.positionedMesh = obj3D.userData.PB.origin;
obj3D.userData.PB.processed = true;
}
else {
// could be undefined or a value, but copy it
obj3D.userData.PB.positionedMesh = obj3D.parent?.userData?.PB?.positionedMesh;
}
if (obj3D.userData.PB.isAssignable === true) {
// obj3D.userData.PB.assignableComponent = this.assignableByDefinerId[obj3D.userData.PB.origin];
obj3D.userData.PB.assignable = this.assignables.find( assignable =>
assignable.blockInstance === obj3D.userData.PB.blockInstance
&&
assignable.positionedMeshGroup === obj3D.userData.PB.positionedMeshGroup
&&
assignable.positionedMesh === obj3D.userData.PB.positionedMesh
);
}
else {
// obj3D.userData.PB.assignableComponent = obj3D.parent.userData?.PB?.assignableComponent;
obj3D.userData.PB.assignable = obj3D.parent?.userData?.PB?.assignable;
}
})
// after a time out traverse rthe 3d structure
// for every object3D check the assignable of the parent and copy + adapt
this._setContent('main', quality, content);
break;
default:
this._setContent(part, quality, null);
break;
}
return this;
}
}
export { BlockInstance }