Source: transform/transform.js

import { Reporter } from '../reporter/reporter.js';
import { Object3D } from '../../node_modules/three/build/three.module.js';
import { InformationSource } from "../reporter/information_source.js";
import { WrappedMesh } from '../package/mesh/wrapped_mesh.js';

/**
 * Temporarily transforms the way a block is displayed.
 */

class Transform extends InformationSource {

    /**
     * @param {Reporter} reporter 
     * @param {Object} settings 
     * @param {Timer} settings.timer
     * @param {Scene} settings.scene
     * @param {Boolean} settings.realtime
     */

    constructor(reporter, settings = {}) {

        super(reporter, settings);

        this.timer = settings.timer;
        this.scene = settings.scene;
        this.realtime = settings.realtime;
    }


    // targets = {};



    /**
     * Objects on which this transform should be applied
     * @type {Array<Object>}
     */

    targets = [];


    addByReference(blockInstance, subComponent) {
        return this.addById(blockInstance.id, subComponent.id);
    }


    /**
     * Object to start applying the transform to
     * @param {UUID} blockInstanceId
     * @param {UUID} [subComponentId]
     */

    addById(blockInstanceId, subComponentId) {

        console.log('add', blockInstanceId)

        if (this.has(blockInstanceId, subComponentId)) {
            throw new Error('Target already being transformed');
        }

        const targetObject3D = Transform.findTargetInScene(
            this.scene,
            blockInstanceId,
            subComponentId
        );

        // console.log(targetObject3D)

        // THREE clones objects through JSON.parse/stringify
        // which exports the referenced PB.Component objects
        const bi = targetObject3D.userData.PB.blockInstance;
        targetObject3D.userData.PB.blockInstance = '';
        const targetObject3DClone = targetObject3D.clone();
        targetObject3D.userData.PB.blockInstance = bi;
        targetObject3DClone.userData.PB.blockInstance = bi;

        const targetData = {
            blockInstanceId,
            subComponentId,
            object3DToTransform: targetObject3D,
            startTime: new Date().getTime(),
            originalPosition: targetObject3D.position.clone(),
            originalQuaternion: targetObject3D.quaternion.clone(),
            originalObject3DClone: targetObject3DClone
        };

        this.targets.push(targetData);

        // call the extending class's onTransformStart method
        if ( typeof this.onTransformStart === 'function' ) {
            this.onTransformStart(targetData);
        }

        if (this.realtime === true) {
            // console.log(typeof (this.onTransformUpdate));
            this.timer.addUpdateRequest(this.id, `${this.label} update method`);
            if (this.targets.length === 1) {
                this.timer.on('update', this._onTimerUpdate.bind(this), this.id);
            }
        } else {
            this.timer.trigger();
        }
    }


    /**
     * Rebuilds the list of Object3D's that need transforming, by
     * finding them in the scene.
     */

    relinkObject3Ds() {

        const objects = [];

        for (let targetData of this.targets) {

            const targetObject3D = Transform.findTargetInScene(this.scene, targetData.blockInstanceId, targetData.subComponentId);

            if ( targetObject3D === null ) {
                console.warn(`Removing ${this.label}, target object not in scene`);
                this.removeById(targetData.blockInstanceId, targetData.subComponentId, false);
            }
            else {

                targetData.object3DToTransform = targetObject3D;

                
                this.onRelink(targetData);
                
            }
        }
    }


    /**
     * @interface
     * @param {Object} targetData
     */
    onRelink(targetData) {}

    static findTargetInScene(scene, blockInstanceId, subComponentId) {

        // variable biId refers to the block instance id
        // we can search the scene for an object with that id
        // as name, because block instances should be unique 
        // in the scene

        const biObject3D = scene.getObjectByName(blockInstanceId);

        if (!biObject3D) {
            // console.log(scene)
            // console.error(blockInstanceId)
            return null;
        }

        if (subComponentId === undefined) {
            return biObject3D;
        }
        else {

            // The variable subComponentId refers to the id of
            // either a positioned mesh or positioned mesh group.
            // These are no necessarily unique within the scene
            // but they are unique within the blockInstance.

            const scObject3D = biObject3D.getObjectByName(subComponentId);

            if (!scObject3D) {
                // console.log(scene)
                // console.log(biObject3D)
                // console.error(blockInstanceId)
                return null;
            }

            return scObject3D;
        }
    }


    /**
     * @param {DOMHighResTimeStamp} DOMHighResTimeStamp (timstamp: decimal number, in milliseconds)
     * @protected
     */

    _onTimerUpdate(DOMHighResTimeStamp) {
        for (let objData of this.targets) {
            this.onTransformUpdate(objData, DOMHighResTimeStamp);
        }
    }


    has(blockInstanceId, subComponentId) {
        return !!this.targets.find(obj => obj.blockInstanceId === blockInstanceId && obj.subComponentId === subComponentId);
    }


    /**
     * @interface
     * @param {Object3D} object3D 
     * @param {DOMHighResTimeStamp} DOMHighResTimeStamp (timstamp: decimal number, in milliseconds)
     */

    // onTransformUpdate;


    /**
     * Object to stop applying the transform to
     * @param {Object3D} objectToRemove
     * @param {Boolean} restore
     */

    removeById(blockInstanceId, subComponentId, restore = true) {

        const targetIndex = this.targets.findIndex(obj => obj.blockInstanceId === blockInstanceId && obj.subComponentId === subComponentId);

        // console.log(this.targets, targetIndex);

        if (targetIndex === -1) {
            throw new Error('Unknown target ' + blockInstanceId);
        }

        if (restore === true) {
            const entry = this.targets[targetIndex];
            const parentIndex = entry.object3DToTransform.parent.children.findIndex(child => child === entry.object3DToTransform);

            const parent = entry.object3DToTransform.parent;

            parent.remove(entry.object3DToTransform);
            parent.add(entry.originalObject3DClone);
            // console.log('restored')
        }

        this.targets.splice(targetIndex, 1);

        console.log(this.targets);

        if (this.timer.hasListener('update', this.id)) {
            if (this.targets.length === 0) {
                this.timer.removeListener('update', this.id);
            }
        }
        else {
            this.timer.trigger();
        }

        // console.log('removed')
    }

    removeAllListeners(restore) {
        // console.error(this.label, 'remove all')

        const toRemove = this.targets.map( td => ([ td.blockInstanceId, td.subComponentId ]));

        for (let [ blockInstanceId, subComponentId ] of toRemove) {
            console.log(this.targets.length, blockInstanceId, subComponentId);
            this.removeById(blockInstanceId, subComponentId, restore);
            console.log(this.targets.length);
        }
        console.log(this.targets);
    }
}

export { Transform }