hexa-viewer-communicator
Version:
A simple API for <hexa-viewer>
1,165 lines (1,085 loc) • 39 kB
text/typescript
// Do not delete this as it allows importing the package with other projects
import "regenerator-runtime/runtime.js";
import { Configurator } from "./configurator";
import { EventObservable } from "./event-observable";
import { IConfigurator } from "./interfaces/configurator.interface";
import {
EventObservableTypes,
IEventObservable,
} from "./interfaces/event-observable.interface";
import {
IPostMessage,
IPostMessageOrigin,
MaterializeMeshConfig,
IViewerCommunicator,
MeshData,
IViewerCommunicatorOptions,
ICreateInstanceFromUrlOptions,
Collision,
IImagesByTourResponse,
ILight,
IShadowPlaneOptions,
IHdriOptions,
IAdjustmentsPresetJson,
IBroadcastSceneSummaryOption,
ISceneSummary,
IMeshProps,
JsonToHtmlObject,
EventSelector,
TutorialType,
IExportOptions,
IExpotedModel,
ExportFileType,
IGifGenOptions,
MediaFormat,
IMatcapOptions,
IApplyTextureOptions,
TextureMimeType,
IBoundingBox,
IAnimationOptions,
ICameraControlsStateAnimation,
IMaterialPropsOptions,
ISwapMaterialType,
IDiamondOptions,
} from "./interfaces/viewer-communicator.interface";
import { ImageToVideo } from "./image-to-video";
export class ViewerCommunicator implements IViewerCommunicator {
public configurator: IConfigurator;
private _hexaViewer: HTMLElement;
private _frameID: string;
private _isViewerLoaded: boolean;
private _isModelLoaded: boolean;
private _isAnimateEnterEnd: boolean;
private _isViewerListening: boolean;
// private _onGetMeshesData: Array<Function>;
// private _onCollisions = [] as Array<Function>;
private _onMessageBind: any;
private _meshesData: { [id: string]: MeshData };
private _mesheAnimations: { [id: string]: IAnimationOptions };
private _xrSupport: boolean;
private _eventObservable: IEventObservable;
private _hasDestroyed: boolean;
private _onLoadingProgress: Array<any>;
constructor(options?: IViewerCommunicatorOptions) {
this._hasDestroyed = false;
options = options || {};
this._hexaViewer = options.hexaViewer;
this._isViewerLoaded = false;
this._isModelLoaded = false;
this._isAnimateEnterEnd = false;
this._onLoadingProgress = [];
this.initFrameID();
this.attachEvents();
this.initEventObservable();
this.configurator = new Configurator(this);
// Update the current viewer state.
// In case it's too soon this won't even get to the viewer because he's not listening.
// In case the viewer is already listening and a model has already been loaded the
// await this.onModelLoaded() will return instead of never returning and preventing
// all communicator functionality.
this._updateViewerFullyLoaded();
}
get hexaViewer() {
return this._hexaViewer;
}
set hexaViewer(hv: HTMLElement) {
this.attachInstance(hv);
}
get eventObservable() {
return this._eventObservable;
}
get isModelLoaded() {
return this._isViewerLoaded;
}
get hasDestroyed() {
return this._hasDestroyed;
}
get isViewerListening() {
return this._isViewerListening;
}
private set isViewerListening(value: boolean) {
this._isViewerListening = value;
if (this._isViewerListening)
this._eventObservable.invoke(
EventObservableTypes.ON_VIEWER_LISTENING,
false
);
}
private set isModelLoaded(value: boolean) {
if (value) this._onViewerFullyLoaded();
else this._isViewerLoaded = value;
}
private initEventObservable() {
this._eventObservable = new EventObservable();
}
private attachInstance(hexaViewer: HTMLElement) {
this._hexaViewer = hexaViewer;
this.initFrameID();
}
private initFrameID(elem?: HTMLElement) {
elem = elem || this._hexaViewer;
if (elem) {
this._frameID = elem.getAttribute("frame-id");
if (!this._frameID) {
this._frameID = this.generateUUID();
elem.setAttribute("frame-id", this._frameID);
}
}
}
private attachEvents() {
this._onMessageBind = this.onMessage.bind(this);
self.addEventListener("message", this._onMessageBind, false);
}
private onMessage(event: MessageEvent) {
const obj = this.safeParse(event.data) as IPostMessage;
if (obj) {
if (this._frameID && obj.id && this._frameID !== obj.id) return;
// if (obj.to === IPostMessageOrigin.TOP) {
switch (obj.action) {
case "viewerListening": {
this.isViewerListening = true;
break;
}
case "viewerFullyLoaded": {
this._onViewerFullyLoaded();
break;
}
case "onModelLoaded": {
this._onModelLoaded();
break;
}
case "onAnimateEnterEnd": {
this._onAnimateEnterEnd();
break;
}
case "setMeshesData": {
this._onMeshesData(obj.data);
break;
}
case "setMeshAnimations": {
this._onMeshAnimations(obj.data);
break;
}
case "setCollisions": {
this._eventObservable.invoke(
EventObservableTypes.ON_COLLISIONS,
false,
[obj.data]
);
break;
}
case "setSceneSummary": {
if (obj.data?.autoAdjustScene)
this._eventObservable.invoke(
EventObservableTypes.ON_ADJUDT_SCENE,
true,
[obj.data]
);
this._eventObservable.invoke(
EventObservableTypes.ON_SET_SCENE_SUMMARY,
true,
[obj.data]
);
break;
}
case "setAttachJsonScene": {
this._eventObservable.invoke(
EventObservableTypes.ON_APPLY_PRESET,
true
);
break;
}
case "create_images_by_tour": {
this._eventObservable.invoke(
EventObservableTypes.ON_CREATE_IMAGES_BY_TOUR,
true,
[obj.data]
);
break;
}
case "onLightsSummary": {
this._eventObservable.invoke(
EventObservableTypes.ON_LIGHTS_SUMMARY,
true,
[obj.data]
);
break;
}
case "onConfiguratorSelectDone": {
this._eventObservable.invoke(
EventObservableTypes.ON_CONFIGURATOR_SELECT_DONE,
true,
[obj.data]
);
break;
}
case "onModelInteraction": {
this._eventObservable.invoke(
EventObservableTypes.ON_MODEL_INTERACTION,
false,
[obj.data.type]
);
break;
}
case "setViewerFullyLoaded": {
this._eventObservable.invoke(
EventObservableTypes.ON_SET_VIEWER_FULLY_LOADED,
true,
[obj.data]
);
break;
}
case "setCurrentScreenshot": {
this._eventObservable.invoke(
EventObservableTypes.ON_SCREENSHOT,
true,
[obj.data]
);
break;
}
case "onScreenshotsSequence": {
this._eventObservable.invoke(
EventObservableTypes.ON_SCREENSHOTS_SEQUENCE,
true,
[obj.data.images]
);
break;
}
case "setModel": {
this._eventObservable.invoke(
EventObservableTypes.ON_EXPORT,
true,
[obj.data]
);
break;
}
case "setBoundingBox": {
this._eventObservable.invoke(
EventObservableTypes.ON_BOUNDING_BOX,
true,
[obj.data]
);
break;
}
case "setMaterials": {
this._eventObservable.invoke(
EventObservableTypes.ON_GET_MATERIALS,
true,
[obj.data]
);
break;
}
case "setDiamondEffectOptions": {
this._eventObservable.invoke(
EventObservableTypes.ON_GET_DIAMONDS_OPTIONS,
true,
[obj.data]
);
break;
}
case "setLoadingPercentage": {
this._onLoadingProgress.forEach(f => f(obj.data));
break;
}
}
// }
}
}
private _checkModelLoaded() {
if (
this._isViewerLoaded &&
(this._isModelLoaded || this._isAnimateEnterEnd)
)
this._eventObservable.invoke(
EventObservableTypes.ON_MODEL_LOADED,
true
);
}
private _onAnimateEnterEnd() {
this._isAnimateEnterEnd = true;
this._checkModelLoaded();
this._eventObservable.invoke(
EventObservableTypes.ON_ANIMATE_ENTER_END,
true
);
}
private _onModelLoaded() {
this._isModelLoaded = true;
this._checkModelLoaded();
}
private _onViewerFullyLoaded() {
this._isModelLoaded = true;
this._isViewerLoaded = true;
this._eventObservable.invoke(
EventObservableTypes.ON_VIEWER_LOADED,
true
);
this._checkModelLoaded();
}
private _onMeshesData(obj: { [id: string]: MeshData }) {
this._meshesData = obj;
this.isModelLoaded = true;
this._eventObservable.invoke(
EventObservableTypes.ON_GET_MESHES_DATA,
true,
[this._meshesData]
);
// if (this._meshesData) {
// setTimeout(() =>{
// if (this._meshesData) {
// Object.values(this._meshesData).forEach(md => {
// if (md.rotation) {
// md.rotationDegree = {} as ThreeVector3int;
// md.rotationDegree.x = parseFloat(md.rotation.x.toString()) / (Math.PI / 180);
// md.rotationDegree.y = parseFloat(md.rotation.y.toString()) / (Math.PI / 180);
// md.rotationDegree.z = parseFloat(md.rotation.z.toString()) / (Math.PI / 180);
// }
// });
// }
// });
// }
}
private _onMeshAnimations(obj: { [id: string]: IAnimationOptions }) {
this._mesheAnimations = obj;
this._eventObservable.invoke(
EventObservableTypes.ON_GET_MESHE_ANIMATIONS,
true,
[this._mesheAnimations]
);
}
private _updateViewerFullyLoaded() {
return new Promise((resolve: any, reject: any) => {
this._eventObservable.add(
EventObservableTypes.ON_SET_VIEWER_FULLY_LOADED,
(state: boolean) => {
this._isViewerLoaded = state;
if (this._isViewerLoaded) {
this.onModelLoaded();
this._isViewerListening = true;
this._onViewerFullyLoaded();
}
resolve();
}
);
const msg = {
action: "broadcastViewerFullyLoaded",
to: IPostMessageOrigin.WORKER,
};
this.sendToViewer(msg);
});
}
onModelLoaded() {
return new Promise((resolve: any, reject: any) => {
if (this._isViewerLoaded) resolve();
else
this._eventObservable.add(
EventObservableTypes.ON_VIEWER_LOADED,
resolve
);
});
}
// on model ready is a later event then onModelLoaded
// it will fire after the enter animation is done (in case there is one)
// in case there isn't it will fire next to the onModelLoaded
onModelReady() {
return new Promise((resolve: any, reject: any) => {
if (this._isModelLoaded) resolve();
else
this._eventObservable.add(
EventObservableTypes.ON_MODEL_LOADED,
resolve
);
});
}
onViewerListening() {
return new Promise((resolve: any, reject: any) => {
if (this._isViewerListening) resolve();
else
this._eventObservable.add(
EventObservableTypes.ON_VIEWER_LISTENING,
resolve
);
});
}
onAnimateEnterEnd() {
return new Promise((resolve: any, reject: any) => {
if (this._isAnimateEnterEnd) resolve();
else
this._eventObservable.add(
EventObservableTypes.ON_ANIMATE_ENTER_END,
resolve
);
});
}
getMeshesData(): Promise<{ [id: string]: MeshData }> {
return new Promise((resolve: any, reject: any) => {
this._eventObservable.add(
EventObservableTypes.ON_GET_MESHES_DATA,
resolve
);
const msg = {
action: "getMeshesData",
to: IPostMessageOrigin.WORKER,
};
this.sendToViewer(msg);
});
}
getMeshAnimations(): Promise<{ [id: string]: IAnimationOptions }> {
return new Promise((resolve: any, reject: any) => {
this._eventObservable.add(
EventObservableTypes.ON_GET_MESHE_ANIMATIONS,
resolve
);
const msg = {
action: "broadcastMeshAnimations",
to: IPostMessageOrigin.WORKER,
};
this.sendToViewer(msg);
});
}
getMaterials(): Promise<{ [id: string]: IAnimationOptions }> {
return new Promise((resolve: any, reject: any) => {
this._eventObservable.add(
EventObservableTypes.ON_GET_MATERIALS,
resolve
);
const msg = {
action: "broadcastMaterials",
to: IPostMessageOrigin.WORKER,
};
this.sendToViewer(msg);
});
}
updateMeshAnimations(meshAnimations: { [id: string]: IAnimationOptions }): void {
this.sendToViewer({
action: "updateMeshAnimations",
to: IPostMessageOrigin.WORKER,
value: { meshAnimations }
});
}
private safeParse(p: any) {
if (typeof p === "string") {
try {
return JSON.parse(p);
} catch (e) { }
}
return p;
}
private generateUUID(): string {
var d = new Date().getTime();
if (
window.performance &&
typeof window.performance.now === "function"
) {
d += performance.now();
}
var uuid = "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(
/[xy]/g,
(c) => {
var r = (d + Math.random() * 16) % 16 | 0;
d = Math.floor(d / 16);
return (c === "x" ? r : (r & 0x3) | 0x8).toString(16);
}
);
return uuid;
}
materializeMesh(meshName: string, config: MaterializeMeshConfig) {
const msg = {
action: "materializeMesh",
to: IPostMessageOrigin.WORKER,
value: {
name: meshName,
config: config,
},
};
this.sendToViewer(msg);
}
sendToViewer(msg: IPostMessage) {
// msg.from = IPostMessageOrigin.TOP;
if (this._frameID) msg.id = this._frameID;
self.postMessage(msg, location.origin);
}
// Creates a <hexa-viewer> instance from a viewer URL (typically the resource_default)
async createInstanceFromUrl(
viewerURL: string,
params = {} as any,
options?: ICreateInstanceFromUrlOptions
): Promise<HTMLElement> {
options = options || {};
var hv = document.createElement("hexa-viewer");
var p = this.getUrlParams(viewerURL);
var allP = Object.assign(p, params);
if (typeof allP.server === "undefined") allP.server = "1";
if (typeof allP["frame-id"] === "undefined")
allP["frame-id"] = this.generateUUID();
if (options.enableWebXR) {
if (await this.isWebXrSupported()) allP["offscreen"] = "false";
}
if (options.themeColor) allP["theme-color"] = options.themeColor;
for (let i in allP)
i && hv.setAttribute(i, allP[i] === null ? "" : allP[i]);
this.initFrameID(hv);
return hv;
}
private getUrlParams(url: string) {
let queryString = {} as any,
query = "";
if (url.indexOf("?") > -1) query = url.substring(url.indexOf("?") + 1);
if (!query) return queryString;
query = query.split("+").join(" ");
let vars = query.split("&");
for (let i = 0; i < vars.length; i++) {
let pair = vars[i].split("=");
if (typeof queryString[pair[0]] === "undefined") {
queryString[pair[0]] = pair[1]
? decodeURIComponent(pair[1])
: null;
} else if (typeof queryString[pair[0]] === "string") {
let arr = [queryString[pair[0]], decodeURIComponent(pair[1])];
queryString[pair[0]] = arr;
} else {
queryString[pair[0]].push(decodeURIComponent(pair[1]));
}
}
return queryString;
}
togglePicInPic(state: boolean) {
const msg = {
action: "togglePicInPic",
to: IPostMessageOrigin.MAIN,
value: state,
};
this.sendToViewer(msg);
}
toggleWireframe(state: boolean) {
const msg = {
action: "toggleWireframe",
to: IPostMessageOrigin.WORKER,
value: state,
};
this.sendToViewer(msg);
}
toggleUvMode(state: boolean) {
const msg = {
action: "toggleUvMode",
to: IPostMessageOrigin.WORKER,
value: state,
};
this.sendToViewer(msg);
}
toggleMatcapMode(state: boolean, options?: IMatcapOptions) {
const msg = {
action: "toggleMatcapMode",
data: options,
to: IPostMessageOrigin.WORKER,
value: state,
};
this.sendToViewer(msg);
}
toggleHideBottom(state: boolean) {
const msg = {
action: "toggleHideBottom",
to: IPostMessageOrigin.WORKER,
value: state,
};
this.sendToViewer(msg);
}
isWebXrSupported(): Promise<boolean> {
return new Promise((resolve: any, reject: any) => {
if (typeof this._xrSupport === "boolean") {
resolve(this._xrSupport);
return;
}
const navigatorAny = navigator as any;
if (!navigatorAny.xr || !navigatorAny.xr.isSessionSupported) {
this._xrSupport = false;
resolve(this._xrSupport);
return;
}
try {
navigatorAny.xr.isSessionSupported("immersive-ar").then(
(res: boolean) => {
this._xrSupport = res;
resolve(this._xrSupport);
},
(e: Error) => {
this._xrSupport = false;
resolve(this._xrSupport);
}
);
} catch (e) {
this._xrSupport = false;
resolve(this._xrSupport);
}
});
}
toggleWebXR(state: boolean, invokeWhenReady = true) {
const msg = {
action: "toggleXr",
obj: {
state: state,
value: invokeWhenReady,
showIcon: true,
},
} as IPostMessage;
this.sendToViewer(msg);
}
toggleAR(state: boolean, invokeWhenReady = true) {
const msg = {
action: "toggleAR",
obj: {
state: state,
value: invokeWhenReady
},
} as IPostMessage;
this.sendToViewer(msg);
}
waitForCollisions(): Promise<Array<Collision>> {
return new Promise((resolve: any, reject: any) => {
this._eventObservable.add(
EventObservableTypes.ON_COLLISIONS,
resolve
);
});
}
toggleCollision(collisionMode: boolean, color?: string) {
this.sendToViewer({
action: "toggleCollision",
to: IPostMessageOrigin.WORKER,
value: {
value: collisionMode,
color,
},
});
}
deleteCollision(position: number, count: number) {
this.sendToViewer({
action: "spliceCollision",
to: IPostMessageOrigin.WORKER,
value: {
value: position,
count,
},
});
}
removeAllCollisions() {
this.sendToViewer({
action: "removeAllCollisions",
to: IPostMessageOrigin.WORKER,
});
}
adjustScene() {
return new Promise(async (resolve: any, reject: any) => {
this._eventObservable.add(
EventObservableTypes.ON_ADJUDT_SCENE,
resolve
);
await this.onModelLoaded();
this.sendToViewer({
action: "adjustScene",
to: IPostMessageOrigin.WORKER,
});
});
}
// Not going to work without reloading instance or supporting each parameter on the viewer level
// private applyPresetSrc(params: any): void {
// for (let i in params) {
// this.hexaViewer.setAttribute(i, params[i]);
// }
// }
applyPreset(json: IAdjustmentsPresetJson) {
return new Promise(async (resolve: any, reject: any) => {
this._eventObservable.add(
EventObservableTypes.ON_APPLY_PRESET,
resolve
);
// Make sure model has loaded
await this.onModelLoaded();
this.sendToViewer({
action: "attachJsonScene",
to: IPostMessageOrigin.WORKER,
data: json,
});
// // Make sure all viewer parameters has taken effect
// await this.broadcastSceneSummary();
// if (preset?.preset_json) {
// if (preset.preset_json.shadowPlane)
// this.applyShadowPlane(preset.preset_json.shadowPlane);
// if (preset.preset_json.hdri) {
// if (!preset.preset_json.hdri.type)
// preset.preset_json.hdri.intensity = 0;
// this.applyHDRI(preset.preset_json.hdri);
// }
// if (preset.preset_json.lights)
// await this.setLightsByJson(preset.preset_json.lights);
// if (preset.preset_json.params)
// this.assetAdjustmentsService.applyParams();
// }
});
}
applyHDRI(hdri: IHdriOptions) {
this.sendToViewer({
action: "setHDR",
to: IPostMessageOrigin.WORKER,
value: hdri.type,
options: hdri,
});
}
applyShadowPlane(shadowPlane: IShadowPlaneOptions) {
this.sendToViewer({
action: "togglePlane",
to: IPostMessageOrigin.WORKER,
value: shadowPlane.opacity,
options: {
active: shadowPlane.active,
color: shadowPlane.color,
opacity: shadowPlane.opacity,
physical: shadowPlane.physical,
physicalOptions: shadowPlane.physicalOptions,
reflector: shadowPlane.reflector,
side: shadowPlane.side,
},
});
}
broadcastSceneSummary(
params?: IBroadcastSceneSummaryOption
): Promise<ISceneSummary> {
return new Promise((resolve: any, reject: any) => {
this._eventObservable.add(
EventObservableTypes.ON_SET_SCENE_SUMMARY,
resolve
);
this.sendToViewer({
action: "broadcastSceneSummary",
to: IPostMessageOrigin.WORKER,
value: params,
});
});
}
setLightsByJson(lights: { [id: string]: Array<ILight> }) {
return new Promise((resolve: any, reject: any) => {
this._eventObservable.add(
EventObservableTypes.ON_LIGHTS_SUMMARY,
resolve
);
this.sendToViewer({
action: "setLightsByJson",
value: lights,
to: IPostMessageOrigin.WORKER,
});
});
}
onCreateImagesByTour(): Promise<IImagesByTourResponse> {
return new Promise(async (resolve: any, reject: any) => {
this._eventObservable.add(
EventObservableTypes.ON_CREATE_IMAGES_BY_TOUR,
resolve
);
});
}
setMeshProps(optins: IMeshProps) {
const obj = {
name: optins.mesh.name,
props: {},
} as any;
// if (optins.key === 'rotation') {
// // radians to degrees
// optins.mesh.rotation.x = optins.mesh.rotationDegree.x * (Math.PI / 180);
// optins.mesh.rotation.y = optins.mesh.rotationDegree.y * (Math.PI / 180);
// optins.mesh.rotation.z = optins.mesh.rotationDegree.z * (Math.PI / 180);
// optins.value = optins.mesh.rotation;
// }
obj["props"][optins.key] = optins.value;
if (!this._meshesData[obj.name])
this._meshesData[obj.name] = {} as MeshData;
(this._meshesData[obj.name] as any)[optins.key] = optins.value;
this.sendToViewer({
action: "setMeshProps",
to: IPostMessageOrigin.WORKER,
value: obj,
});
}
appendDynamicElement(
obj: JsonToHtmlObject,
events: Array<EventSelector>,
selectorToAppend = ".body"
) {
this.sendToViewer({
action: "appendElement",
to: IPostMessageOrigin.MAIN,
obj: {
obj,
events,
selectorToAppend,
},
});
}
controlsTutorial(types?: Array<TutorialType>) {
this.sendToViewer({
action: "startZoomTutorial",
to: IPostMessageOrigin.WORKER,
value: types,
});
}
toggleAutoRotate(state: boolean) {
this.sendToViewer({
action: "toggleAutoRotate",
to: IPostMessageOrigin.WORKER,
data: state,
});
}
onModelInteraction(cb?: Function): Promise<string> {
return new Promise((resolve: any, reject: any) => {
this._eventObservable.add(
EventObservableTypes.ON_MODEL_INTERACTION,
(type: string) => {
if (cb) cb(type);
resolve(type);
}
);
});
}
goToInitialCamPos() {
this.sendToViewer({
action: "goToInitialCamPos",
to: IPostMessageOrigin.WORKER,
});
}
getCurrentScreenshot(): Promise<string> {
return new Promise(async (resolve: any, reject: any) => {
this._eventObservable.add(
EventObservableTypes.ON_SCREENSHOT,
resolve
);
// await this.onModelReady();
await this.onAnimateEnterEnd();
this.sendToViewer({
action: "getCurrentScreenshot",
to: IPostMessageOrigin.WORKER,
});
});
}
expotModel(options: IExportOptions): Promise<IExpotedModel> {
return new Promise(async (resolve: any, reject: any) => {
if (!options.downloadFile)
this._eventObservable.add(
EventObservableTypes.ON_EXPORT,
resolve
);
await this.onModelReady();
let action = "",
value = null;
switch (options.type) {
case ExportFileType.GLB:
case ExportFileType.glTF: {
action = "broadcastGLTF";
value = {
binary: options.type === ExportFileType.GLB,
trs: false,
onlyVisible: true,
truncateDrawRange: false,
embedImages: true,
// animations: []
forceIndices: true,
forcePowerOfTwoTextures: true,
imagesFileType: null,
normalImagesFileType: null,
mroImagesFileType: null,
imagesCompressionFactor: null,
downloadFile: options.downloadFile,
maxTexturesSize: null,
maxDiffuseTexturesSize: null,
maxNormalTexturesSize: null,
maxMroTexturesSize: null,
optipng: options.compressPNG,
simplify: false,
deleteUV2: false,
};
break;
}
case ExportFileType.USDZ: {
action = "broadcastUSDZ";
break;
}
case ExportFileType.OBJ: {
action = "broadcastOBJ";
break;
}
default: {
action = "broadcastGLTF";
break;
}
}
this.sendToViewer({
action,
value,
to: IPostMessageOrigin.WORKER,
});
if (options.downloadFile) resolve(null);
});
}
getScreenshotsSequence(
options?: IGifGenOptions
): Promise<Array<string> | Blob> {
return new Promise(async (resolve: any, reject: any) => {
this._eventObservable.add(
EventObservableTypes.ON_SCREENSHOTS_SEQUENCE,
async (images: Array<string>) => {
if (options.format === MediaFormat.VIDEO) {
const i2v = new ImageToVideo(images);
if (options.codec)
i2v.codecs = options.codec;
resolve((await i2v.getVideo()) as Blob);
} else resolve(images);
}
);
await this.onModelReady();
await this.onAnimateEnterEnd();
const finalOptions = {} as any;
if (options) {
finalOptions.ggNumOfFrames = options.numOfFrames;
// finalOptions.ggInterval = options.interval;
// finalOptions.ggSampleInterval = options.sampleInterval;
// finalOptions.ggRotateSpeen = options.rotateSpeen;
}
finalOptions.msgObj = {
data: {},
action: "onScreenshotsSequence",
to: IPostMessageOrigin.TOP,
from: IPostMessageOrigin.WORKER,
};
this.sendToViewer({
action: "broadcastGifSequence",
to: IPostMessageOrigin.WORKER,
data: {
options: finalOptions,
},
});
});
}
applyTexture(options: IApplyTextureOptions) {
this.sendToViewer({
action: "mapTextureToMaterial",
to: IPostMessageOrigin.WORKER,
value: {
texture: options.src,
material: options.materialName,
options: {
map: options.mapType,
intensity: options.intensity,
textureSrc: options.src,
srcChange: !!options.src,
color: options.color,
videoSrc:
options.mimeType === TextureMimeType.VIDEO
? options.src
: null,
},
},
});
}
setMaterialProps(options: IMaterialPropsOptions) {
this.sendToViewer({
action: "setMaterialProps",
to: IPostMessageOrigin.WORKER,
value: {
name: options.materialName,
props: options.props
},
});
}
swapMaterialType(options: ISwapMaterialType) {
this.sendToViewer({
action: "swapMaterialType",
to: IPostMessageOrigin.WORKER,
value: {
name: options.materialName,
newType: options.type
},
});
}
displayFiles(files: Array<Blob>) {
this.sendToViewer({
action: "displayFiles",
to: IPostMessageOrigin.MAIN,
value: {
files: files,
},
});
}
getBoundingBox(): Promise<IBoundingBox> {
return new Promise(async (resolve: any, reject: any) => {
await this.onModelReady();
this._eventObservable.add(
EventObservableTypes.ON_BOUNDING_BOX,
resolve
);
this.sendToViewer({
action: "broadcastBoundingBox",
to: IPostMessageOrigin.WORKER,
});
});
}
async toggleNoDistanceLimit(state: boolean) {
await this.onModelReady();
this.sendToViewer({
action: "toggleNoDistanceLimit",
to: IPostMessageOrigin.WORKER,
value: state
});
}
async toggleCloseup(state: boolean) {
await this.onModelReady();
this.sendToViewer({
action: "toggleCloseup",
to: IPostMessageOrigin.WORKER,
value: state
});
}
async setCameraPosition(pos: ICameraControlsStateAnimation) {
await this.onModelReady();
this.sendToViewer({
action: "setCameraPosition",
to: IPostMessageOrigin.WORKER,
value: pos
});
}
async setDiamonds(state: boolean, meshesNames: Array<string>, options: IDiamondOptions) {
await this.onModelReady();
this.sendToViewer({
action: "toggleDiamond",
to: IPostMessageOrigin.WORKER,
value: {
state,
meshes: meshesNames,
options
}
});
}
getDiamondsOptions(): Promise<IDiamondOptions> {
return new Promise((resolve: any, reject: any) => {
this._eventObservable.add(
EventObservableTypes.ON_GET_DIAMONDS_OPTIONS,
resolve
);
const msg = {
action: "broadcastDiamondEffectOptions",
to: IPostMessageOrigin.WORKER,
};
this.sendToViewer(msg);
});
}
registerForLoadingProgress(callback: Function | any) {
this._onLoadingProgress.push(callback);
}
destroy() {
this._hasDestroyed = true;
this._eventObservable.destroy();
this._isViewerListening = false;
this._isViewerLoaded = false;
this._isModelLoaded = false;
this._isAnimateEnterEnd = false;
this._onLoadingProgress = [];
self.removeEventListener("message", this._onMessageBind, false);
}
}