Source: configurator/block_instance.js

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 }