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 }