import { ElementRef, NgZone, Renderer2, inject } from "@angular/core";
import { BehaviorSubject, Subject, Subscription } from "rxjs";
import { CabriDataService } from "../services/cabri-data.service";
import { GlobalService } from "../services/global.service";
import { environment } from "src/environments/environment";
import { AppUtils } from "../app-utils";
import {
ArcRotateCamera,
AbstractMesh,
DynamicTexture,
Engine,
Mesh,
Scene,
Texture,
Vector3,
HemisphericLight,
DefaultRenderingPipeline,
AssetContainer,
Material,
MultiMaterial,
StandardMaterial,
Color3,
PBRMaterial
} from "babylon4.1";
import postal from "postal";
import * as GUI from "gui4.1";
import { BabylonJsConfetti, ParticleAnimName } from "./babylonjs-confetti";
import { EngineIntegration } from "./engine-integration";
import { AccountService } from "../services/account.service";
declare var window: {
store: any;
document: any;
innerWidth: any;
innerHeight: any;
outerWidth;
outerHeight;
dispatchEvent: any;
CabriSceneBuilder: any;
SceneUpdater: any;
Hud: any;
Globals: any;
params: any;
webGLCleanup: any;
URL: any;
};
declare var BABYLON: any;
export class CabriIntegration extends EngineIntegration {
accountService:AccountService
engine: Engine;
clmcSubscription: any;
debug = true;
domReady;
domReadySubcription: any;
mascotteMesh: AbstractMesh;
hemisphericLight: HemisphericLight;
light1: any;
light2: any;
currentBackground = "/assets/gabarits/fondsKidaia/cabri_default/stars_default.jpg";
private _loadingDiv: any;
loaderCamera: any;
private waitCameraInterval: NodeJS.Timeout;
perfModeSubscription: Subscription;
// saveResize: any;
freezeSceneRender = false;
public saveDOMDisabled = false;
public basketBallMesh: any;
public mathiaMesh: any;
public cabriRenderStarted: boolean;
public canvasHolderHeight: any;
public pageDivHeight: any;
public pageDivWidth: any;
public smallHelpHeight: any;
public bigHelpHeight: any;
public operationWrapperID: string;
public rotationInterval;
public currentNumberOperations = new Array();
public paramDisplayMode: any;
public collectionsImgs = {
firstNumber: new Array(),
secondNumber: new Array()
};
// public page;
public operationCalled: string;
public operationWrapperMethods = {
paramCollections: new Subject(),
paramSimpleImg: new Subject()
};
public scene: Scene;
public camera: ArcRotateCamera;
public cameraHud: ArcRotateCamera;
public holoCameras: any;
public cabriObjects: any;
public initialCameraPosition = { radius: 55, alpha: -Math.PI / 2, beta: (7 * Math.PI) / 18 };
public mascotteHappyMesh: Mesh;
public mascottePuzzledMesh: Mesh;
marginTop: number;
constructor(
public cabriService: CabriDataService,
public globalService: GlobalService,
public _ngZone: NgZone,
public page,
public renderer: Renderer2,
) {
super(globalService);
if(!this.accountService){
this.accountService = this.cabriService.accountService;
}
this.cabriRenderStarted = true;
this.domReady = new BehaviorSubject(false);
// (window as any).test = this;
postal.subscribe({
channel: "mathia",
topic: "domready",
callback: (value: any) => {
this.domReady.next();
}
});
this.perfModeSubscription = this.globalService.perfModeEvent.subscribe(on => {
if (this.currentBackground === "/assets/gabarits/fondsKidaia/cabri_default/stars_default.jpg" && on) {
this.currentBackground = "/assets/gabarits/fondsKidaia/cabri_default/stars_default_ios.jpg";
} else if (this.currentBackground === "/assets/gabarits/fondsKidaia/cabri_default/stars_default_ios.jpg" && !on) {
this.currentBackground = "/assets/gabarits/fondsKidaia/cabri_default/stars_default.jpg";
}
});
}
onBabylonReady(): Promise<void> {
return new Promise(async (resolve, reject) => {
this.scene = window.store.getters.cabri.Scene;
this.engine = window.store.getters.cabri.Engine;
BABYLON.GUI = GUI;
//this.initialCameraPosition = { radius: this.camera.radius, alpha: this.camera.alpha, beta: this.camera.beta };
// if (!this.camera) {
// await this.waitCamera();
// }
this.scene.onAfterRenderObservable.addOnce(() => {
this.camera = window.store.getters.cabri.Scene.CABRI.Camera2D3D;
if (this.cabriService.currentActivity?.name !== "solides" && this.camera.inputs) {
// this.camera.inputs.attached.mousewheel.detachControl(window.store.getters.cabri.Canvas);
this.camera.inputs.remove(this.camera.inputs.attached.mousewheel);
}
this.cameraHud = window.store.getters.cabri.Scene.CABRI.CameraHUD;
this.holoCameras = window.store.getters.cabri.SceneUpdate.holoCameras;
this.cabriObjects = window.store.getters.cabri.SceneUpdate.CabriObjects;
this.page.detectChanges();
resolve();
});
});
}
unlockRotation() {
(this.scene as any).CABRI.unlockOrientation();
this.camera.lowerAlphaLimit = null;
this.camera.upperAlphaLimit = null;
this.camera.lowerBetaLimit = null;
this.camera.upperBetaLimit = null;
}
startRender(force = null) {
if (force) {
this.stopRender();
}
if (this.cabriRenderStarted === false) {
try {
window.store.getters.cabri.Cps.setActiveState(true);
console.log("%c RENDER STARTED in cabri integration", "background: green; color: red");
this._ngZone.runOutsideAngular(() => {
window.store.getters.cabri.Engine.runRenderLoop(() => {
if (!window.store.getters.cabri.firstFrameDone) {
if (this.cabriService.holoMode === "-1" || this.cabriService.holoMode === "1") {
try {
window.store.getters.cabri.SceneUpdate.updateHolo();
// tag doUpdateBg to update background when holo have been add
window.store.getters.cabri.doUpdateBg = 1;
} catch (error) {
console.error("Add holo Scene error :" + error);
}
}
window.store.getters.cabri.firstFrameDone = 1;
}
if (window.store.getters.cabri.doUpdateBg) {
this.setBackgroundHolo();
}
if (window.store.getters.cabri.Scene && !window.store.getters.cabri.Scene.isDisposed) {
try {
window.store.getters.cabri.SceneUpdate.update();
} catch (error) {
console.error("SceneUpdate error :" + error);
}
if (!this.freezeSceneRender) {
try {
window.store.getters.cabri.Scene.render();
} catch (error) {
console.error("SceneRender error :" + error);
}
}
}
});
});
this.cabriRenderStarted = true;
} catch (error) {
this.cabriRenderStarted = false;
}
}
}
waitNextRender() {
return new Promise<void>(resolve => {
window.store.getters.cabri.Scene.onAfterRenderObservable.addOnce(async () => {
await this.timeOut(50);
resolve();
});
});
}
/**
* Stops cabri render
* "stop" param is used only for "jeu de kim" overload method
*/
stopRender(stop = null) {
try {
// if (this.cabriRenderStarted === true) {
window.store.getters.cabri.Cps.setActiveState(false);
console.log("%c RENDER STOPPED", "background: red; color yellow");
window.store.getters.cabri.Engine.stopRenderLoop();
// window.store.getters.cabri.SceneUpdate.Disabled = true;
// enable camera -> useless?
// window.store.getters.cabri.Camera._skipRendering = true;
// window.store.getters.cabri.CameraHUD._skipRendering = true;
this.cabriRenderStarted = false;
// }
} catch (error) {
this.cabriRenderStarted = false;
}
}
/**
* If Babylon render is more than 1080 wide, divide its scale by 2 (for 4k)
* TODO: refine for in between resolutions (1440p etc)
* BEWARE : can break renderCanvas interaction like on solides activity where it is disabled
*/
rescale4kToHd() {
console.log(
"render before =",
window.store.getters.cabri.Scene._engine._gl.drawingBufferHeight,
window.store.getters.cabri.Scene._engine._gl.drawingBufferWidth
);
if (window.store.getters.cabri.Scene._engine._gl.drawingBufferWidth > 1920) {
window.store.getters.cabri.Scene._engine.setSize(
window.store.getters.cabri.Scene._engine._gl.drawingBufferWidth / 2,
window.store.getters.cabri.Scene._engine._gl.drawingBufferHeight / 2
);
}
console.log(
"render after = ",
window.store.getters.cabri.Scene._engine._gl.drawingBufferHeight,
window.store.getters.cabri.Scene._engine._gl.drawingBufferWidth
);
}
/**
* Confetti animation
* @param duration animation duration
*/
public async launchConfetti(duration = 1000, position?: Vector3) {
return new Promise<void>(resolve => {
if (!this.globalService.lowPerformanceMode && window.store?.getters?.cabri?.Scene?.onAfterRenderObservable) {
window.store.getters.cabri.Scene.onAfterRenderObservable.addOnce(async () => {
if (!this.scene) {
this.scene = window.store.getters.cabri.Scene;
}
if (!this.camera) {
this.camera = window.store.getters.cabri.Scene.CABRI.Camera2D3D;
}
if (!this.cameraHud) {
this.cameraHud = window.store.getters.cabri.Scene.CABRI.CameraHUD;
}
const particlesRightAnswer = new BabylonJsConfetti(this, 1000, duration, ParticleAnimName.explosion, position);
particlesRightAnswer.runParticles().then(() => {
resolve();
});
});
} else {
resolve();
}
});
}
restartRender() {
this.stopRender();
this.startRender();
}
forceRender() {
window.store.getters.cabri.Cps.setActiveState(true);
// window.store.getters.cabri.Engine.runRenderLoop(() => {
// window.store.getters.cabri.Scene.render();
// });
}
clearCabriMeshes() {
return new Promise<void>(async (resolve, reject) => {
if (window.store?.getters?.cabri) {
try {
await window.store.getters.cabri.SceneUpdate.clearCabriMeshes();
window.store.getters.cabri.SceneUpdate.update();
window.store.getters.cabri.Scene.onAfterRenderObservable.addOnce(() => {
resolve();
});
window.store.getters.cabri.Scene.render();
} catch (e) {
// do nothing...
console.error("@sam to know when this happen...");
}
}
});
}
cleanCabri() {
return new Promise<void>(async resolve => {
try {
for (const key in window.store.getters.cabri.SceneUpdate.CabriObjects) {
if (window.store.getters.cabri.SceneUpdate.CabriObjects.hasOwnProperty(key)) {
window.store.getters.cabri.SceneUpdate.CabriObjects[key].disposeMeshes();
}
}
this.scene = window.store.getters.cabri.Scene;
this.engine = window.store.getters.cabri.Engine;
await super.cleanEngine();
window.store.getters.cabri.Scene = null;
window.store.getters.cabri.SceneBuild.Scene = null;
window.store.getters.cabri.SceneUpdate.Scene = null;
window.store.getters.cabri.SceneUpdate.CabriObjects = {};
} catch (e) {
console.error("cleancabri error", e);
} finally {
resolve();
}
});
}
cleanWebGl(gl) {
if (window.webGLCleanup && gl) {
window.webGLCleanup.forEach((kind, index) => {
// console.log("call", kind, index);
// tslint:disable-next-line: prefer-for-of
for (let i = 0; i < kind.handles.length; i++) {
// console.log("call", kind.remove, kind.handles[i]);
if (kind.remove === "deleteBuffer" && gl.isBuffer(kind.handles[i])) {
try {
gl.bindBuffer(gl.ARRAY_BUFFER, kind.handles[i]);
gl.bufferData(gl.ARRAY_BUFFER, 1, gl.STATIC_DRAW);
} catch (error) {
console.warn("Error Clean gl Buffer", gl, error);
}
}
gl[kind.remove].call(gl, kind.handles[i]);
}
kind.handles = [];
});
}
}
saveDom() {
this.stopRender(true);
console.log("this.cabriRenderStarted @ save DOM = ", this.cabriRenderStarted);
const canvas = this.page.cd.rootNodes[0].querySelector("#renderCanvas1");
const pageDiv = window.document.querySelector("#page-div");
// save DOM
if (canvas && pageDiv) {
window.document.querySelector("#cabriHidden").appendChild(pageDiv);
window.document.querySelector("#cabriHidden").appendChild(canvas);
// reset CSS
canvas.style.width = (window.innerHeight * 21) / 14.85;
canvas.style.height = window.innerHeight;
canvas.style.marginLeft = "0";
canvas.style.marginTop = "0";
canvas.style.left = "0px";
canvas.style.top = "0px";
pageDiv.style.marginTop = "0";
// pageDiv.innerHTML = "";
}
const section = pageDiv.querySelector("section");
if (section) {
// section.innerHTML = "";
}
// save current state
this.cabriService.savedDomMirrorMode = this.cabriService.mirrorMode;
this.cabriService.savedDomHoloMode = this.cabriService.holoMode;
this.cabriService.currentSavedActivityId = this.cabriService.currentActivity.id;
}
restoreDom() {
const element = window.document.querySelector("#cabriHidden #renderCanvas1");
const hiddenPageDiv = window.document.querySelector("#cabriHidden #page-div");
const holder = this.page.cd.rootNodes[0].querySelector("#canvasholder1");
const currentPageCanvas = this.page.cd.rootNodes[0].querySelector("#renderCanvas1");
const currentPagePageDiv = this.page.cd.rootNodes[0].querySelector("#page-div");
if (currentPageCanvas) {
this.renderer.removeChild(holder, currentPageCanvas);
}
if (currentPagePageDiv) {
this.renderer.removeChild(holder, currentPagePageDiv);
}
// add
this.renderer.appendChild(holder, element);
this.renderer.appendChild(holder, hiddenPageDiv);
const cabri = window.store.getters.cabri;
if (this.cabriService.holoMode === "1" || this.cabriService.holoMode === "-1") {
window.Globals.Holo = Number(this.cabriService.holoMode);
window.Globals.hasHolo = true;
window.Globals.HoloFlat = false;
} else {
window.Globals.Holo = 0;
window.Globals.hasHolo = false;
window.Globals.HoloFlat = false;
}
(window as any).cabriImageManager.ImageCache = {};
cabri.MainDiv = this.page.cd.rootNodes[0].querySelector("#maindiv1");
cabri.CanvasHolder = this.page.cd.rootNodes[0].querySelector("#canvasholder1");
cabri.Engine = new BABYLON["Engine"](
element,
!0,
{
stencil: !0,
doNotHandleContextLost: true
},
!0
);
cabri.SceneBuild = new window.CabriSceneBuilder(cabri.Engine, element, cabri);
cabri.firstFrameDone = null;
cabri.Scene = cabri.SceneBuild.Scene;
cabri.Scene.cameraToUseForPointers = cabri.Scene.cameras[1];
cabri.SceneUpdate.Scene = cabri.SceneBuild.Scene;
cabri.SceneUpdate = new window.SceneUpdater(cabri);
cabri.Canvas = element;
cabri.HUD = new window.Hud(cabri);
const fov = cabri.SceneBuild.computeCameraFov(cabri.DocumentWidth, cabri.DocumentHeight);
cabri.Camera2D3D.fov = fov;
if (cabri.CameraHUD.mode !== 1) {
cabri.CameraHUD.fovMode = cabri.Camera2D3D.fovMode;
cabri.CameraHUD.fov = fov;
} else {
cabri.CameraHUD.orthoLeft = cabri.DocumentWidth / 2;
cabri.CameraHUD.orthoRight = -cabri.DocumentWidth / 2;
cabri.CameraHUD.orthoTop = -cabri.DocumentHeight / 2;
cabri.CameraHUD.orthoBottom = cabri.DocumentHeight / 2;
}
cabri.Cps.registerCallbacks(true);
}
cleanSolidesScene() {
const toDispose = window.store.getters.cabri.Scene.meshes.filter(mesh => {
return mesh.gabarit && mesh.gabarit === "solides";
});
toDispose.forEach(mesh => {
mesh.dispose(true);
});
}
cleanKimScene() {
const toDispose = window.store.getters.cabri.Scene.meshes.filter(mesh => {
return mesh.name === "circle" || mesh.name === "savedCircle";
});
toDispose.forEach(mesh => {
mesh.dispose(true);
});
}
setResponse(setOperationMode: string, value: any) {}
clearResponse(setOperationMode: string = null, setOperationParam2: string = null): Promise<any> {
return;
}
toggleOperationWrapperPosition(arg0: string) {}
calculateOperationWrapperPosition(repeatQuestionWithKeyboard = false) {}
calculateHelpHeight() {}
hideOperationAndWrapper() {}
showOperationAndWrapper() {}
getCurrentOperation(): string {
// console.error("no current opération");
return "";
}
getOperationNumber() {}
setFingers() {}
setOperation(setOperationMode: string = "result", setOperationParam2: string = null) {}
getUserOperation(mode?: string): string {
return "";
}
checkActivityChange(): boolean {
return false;
}
hackHelpImageRendering() {}
NumberToLetter(nombre, U = null, D = null) {}
checkHoloModeChange() {
const holoModeChange = this.cabriService.currentActivity.variables.holo !== this.cabriService.holoMode;
// console.log("checkHoloModeChange = " + holoModeChange);
return holoModeChange;
}
generateCss(tabCSS: any) {
let css = "<style>";
for (const id in tabCSS) {
if (tabCSS.hasOwnProperty(id)) {
const properties = tabCSS[id];
css += "#" + id + " { ";
for (const style in properties) {
if (properties.hasOwnProperty(style)) {
css += style + ": " + properties[style];
}
}
css += "}";
}
}
css += "</style>";
return css;
}
// only in jeu du furet
toggleUserResponse(value: boolean, operation: string) {}
updateSVG() {
let selector = "#page-div expr svg";
if (this.page.cd.rootNodes[0].querySelectorAll(selector).length < 1) {
selector = "#page-div .expression svg";
}
// console.log(window.document.querySelectorAll(selector));
const cabriSVG = this.page.cd.rootNodes[0].querySelectorAll(selector);
if (cabriSVG.length > 0) {
cabriSVG.forEach((element: SVGAElement) => {
const group = element.querySelector("g");
// console.log(element);
const groupBbox = group.getBBox();
// console.log(groupBbox);
const groupScale = group.attributes["transform"].nodeValue;
// console.log(groupScale);
const scale = parseFloat(groupScale.replace(/[^0-9\.]/g, ""));
element.style.width = groupBbox.width * scale + "px";
});
}
}
generateCustomCSS(holoMode: boolean, styleElement: ElementRef) {}
getPasFromHTML() {
return "";
}
getFirstNumberFromHTML() {
return "";
}
/**
* Custom async setTimeOut for waiting end of exe
*/
async timeOut(ms, callback: any = null): Promise<void> {
return new Promise(resolve =>
setTimeout(() => {
if (callback) {
callback();
}
resolve();
}, ms)
);
}
getMesh(id) {
for (const m in window.store.getters.cabri.Scene?.meshes) {
if (
window.store.getters.cabri.Scene.meshes.hasOwnProperty(m) &&
window.store.getters.cabri.Scene.meshes[m].id !== null &&
String(window.store.getters.cabri.Scene.meshes[m].id) === String(id)
) {
return window.store.getters.cabri.Scene.meshes[m];
}
}
}
getLastMesh(id) {
let mesh;
for (const m in window.store.getters.cabri.Scene?.meshes) {
if (
window.store.getters.cabri.Scene.meshes.hasOwnProperty(m) &&
window.store.getters.cabri.Scene.meshes[m].id !== null &&
String(window.store.getters.cabri.Scene.meshes[m].id) === String(id)
) {
mesh = window.store.getters.cabri.Scene.meshes[m];
}
}
return mesh;
}
getModels() {
const models = [];
for (const m in window.store.getters.cabri.Scene?.meshes) {
if (
window.store.getters.cabri.Scene.meshes.hasOwnProperty(m) &&
window.store.getters.cabri.Scene.meshes[m].object &&
(window.store.getters.cabri.Scene.meshes[m].object.type === "model" ||
window.store.getters.cabri.Scene.meshes[m].object.type === "cone" ||
window.store.getters.cabri.Scene.meshes[m].object.type === "cylinder" ||
window.store.getters.cabri.Scene.meshes[m].object.type === "sphere") &&
window.store.getters.cabri.Scene.meshes[m].parent === null
) {
models.push(window.store.getters.cabri.Scene.meshes[m]);
}
}
return models;
}
getCabriMesh(id, name = null, forceWait = false): Promise<any> {
return new Promise(async resolve => {
// tslint:disable-next-line: arrow-return-shorthand
let foundMesh;
await this.getCabriMeshAsync(id, name, forceWait).then(mesh => {
foundMesh = mesh;
});
resolve(foundMesh);
});
}
getCabriMeshAsync(id, name = null, forceWait = false): Promise<any> {
return new Promise(async resolve => {
// console.log("getCabriMeshAsync " + id + " " + name);
let foundMesh = null;
let counter = 0;
window.dispatchEvent(new Event("focus"));
while (
!foundMesh &&
((name !== "circle" && counter < 10) || (name === "circle" && (forceWait || (!forceWait && counter < 30))))
) {
for (const m in window.store.getters.cabri.Scene?.meshes) {
if (
window.store.getters.cabri.Scene.meshes.hasOwnProperty(m) &&
window.store.getters.cabri.Scene.meshes[m].object &&
String(window.store.getters.cabri.Scene.meshes[m].object.id) === String(id)
) {
if (!name || (name && window.store.getters.cabri.Scene.meshes[m].name === name)) {
foundMesh = window.store.getters.cabri.Scene.meshes[m];
}
}
}
if (!foundMesh) {
// console.log("get cabri mesh : " + id);
await this.timeOut(100);
}
counter++;
}
resolve(foundMesh);
});
}
getCabriObject(id) {
return window.store.getters.cabri.SceneUpdate.CabriObjects[id];
}
loadMathia(animationCallback = null) {
BABYLON.SceneLoader.ImportMesh(
null,
"/assets/mathia/mascotte3d/",
"mathia_happy_flame_levi.babylon",
this.scene,
(meshes, particleSystems, skeletons) => {
// do something with the meshes and skeletons
// particleSystems are always null for glTF assets
meshes.forEach(newMesh => {
newMesh.layerMask = this.camera.layerMask;
newMesh.renderingGroupId = 1;
newMesh.id = "MathiaEngaging";
newMesh.rotationQuaternion = null;
newMesh.rotation = new BABYLON.Vector3(0, 0, 0);
const info = newMesh.getBoundingInfo().boundingBox.centerWorld;
newMesh.setPivotMatrix(BABYLON.Matrix.Translation(-info.x, -info.y, -info.z));
newMesh.scaling.x = newMesh.scaling.y = newMesh.scaling.z = 10;
this.addToHoloCameras(newMesh);
const mathiaRotation = this.createAnimation("mathiaRotation", "rotation.y", true);
const keys = [];
keys.push({ frame: 0, value: 0 });
keys.push({ frame: 30, value: Math.PI * 2 });
mathiaRotation.setKeys(keys);
const mathiaDecollage = this.createAnimation("mathiaDecollage", "position.y", false);
const keys2 = [];
keys2.push({ frame: 0, value: 0 });
keys2.push({ frame: 100, value: 10 });
mathiaDecollage.setKeys(keys2);
const mathiaScaling = this.createVector3Animation("mathiaScaling", "scaling", false);
const keys3 = [];
keys3.push({ frame: 0, value: new BABYLON.Vector3(5, 5, 5) });
keys3.push({ frame: 50, value: new BABYLON.Vector3(40, 40, 40) });
mathiaScaling.setKeys(keys3);
mathiaDecollage.addEvent(
new BABYLON.AnimationEvent(
99,
(currentFrame: number) => {
this.mathiaMesh.dispose();
if (animationCallback) {
animationCallback();
}
},
true
)
);
newMesh.animations = [];
newMesh.animations.push(mathiaRotation);
newMesh.animations.push(mathiaDecollage);
newMesh.animations.push(mathiaScaling);
this.scene.beginAnimation(newMesh, 0, 100, true);
this.mathiaMesh = newMesh;
});
}
);
}
createAnimation(animationName, meshParameter, loop, frameDuration = 30) {
const animation = new BABYLON.Animation(
animationName,
meshParameter,
frameDuration,
BABYLON.Animation.ANIMATIONTYPE_FLOAT,
loop ? BABYLON.Animation.ANIMATIONLOOPMODE_CYCLE : BABYLON.Animation.ANIMATIONLOOPMODE_CONSTANT
);
return animation;
}
createVector3Animation(animationName, meshParameter, loop, frameDuration = 30) {
const animation = new BABYLON.Animation(
animationName,
meshParameter,
frameDuration,
BABYLON.Animation.ANIMATIONTYPE_VECTOR3,
loop ? BABYLON.Animation.ANIMATIONLOOPMODE_CYCLE : BABYLON.Animation.ANIMATIONLOOPMODE_CONSTANT
);
return animation;
}
/**
* Update DOM Cabri elements
* @param holoMode Holo mode
* @param platform Platform
*/
async updateCabriPosition(holoMode: string = null, activityName = ""): Promise<void> {
return new Promise(async (resolve, reject) => {
const debug = false;
if (!holoMode || holoMode === "0") {
console.log("RESIZE ENTERED");
window.dispatchEvent(new Event("focus"));
this.startRender();
// all dom elements
const ionContent: HTMLElement = this.page.cd.rootNodes[0];
const renderCanvas: HTMLElement = this.page.cd.rootNodes[0].querySelector("#renderCanvas1");
const cabriWrapper: HTMLElement = this.page.cd.rootNodes[0].querySelector("#cabriWrapper");
const pageDiv: HTMLElement = this.page.cd.rootNodes[0].querySelector("#page-div");
const canvasHolder: HTMLElement = this.page.cd.rootNodes[0].querySelector("#canvasholder1");
const mainDiv: HTMLElement = this.page.cd.rootNodes[0].querySelector("#maindiv1");
const appRoot: HTMLElement = window.document.querySelector("app-root");
// debug : page-div missing 2021-11-26
if (!pageDiv && !this.page.environment.production) {
console.error("no page-div @ updateCabriPosition ???");
// tslint:disable-next-line: no-debugger
debugger;
}
// TODO : replace interval by babylon/cabri event
await this.waitCabriReady(renderCanvas, pageDiv);
this.globalService.windowDocumentHeight = window.innerHeight;
this.globalService.windowDocumentWidth = window.innerWidth;
//
// adapt cabri DOM elements to ionic page
//
renderCanvas.style.position = "absolute";
renderCanvas.style.left = "0px";
renderCanvas.style.marginLeft = "0px";
if (this.cabriService.mirrorMode) {
renderCanvas.classList.add("renderCanvas1HoloMode");
}
cabriWrapper.style.position = "fixed";
cabriWrapper.style.top = this.globalService.toolbarHeight - 3 + "px";
cabriWrapper.style.height = this.globalService.windowDocumentHeight - this.globalService.toolbarHeight + 6 + "px";
mainDiv.style.position = "static";
canvasHolder.style.position = "fixed";
canvasHolder.style.width = "100vw";
canvasHolder.style.height = window.innerHeight - this.globalService.toolbarHeight + "px";
canvasHolder.style.top = this.globalService.toolbarHeight + "px";
canvasHolder.style.overflow = "hidden";
appRoot.style.overflow = "hidden";
mainDiv.style.overflow = "hidden";
ionContent.style.overflow = "hidden";
cabriWrapper.style.overflow = "hidden";
await AppUtils.timeOut(50);
renderCanvas.style.width = "100vw";
renderCanvas.style.height = "auto";
let condition: number;
await AppUtils.timeOut(100);
//
// set Canvas dimensions and positions based on device and activity
//
const heightDiff = window.innerHeight - this.globalService.toolbarHeight - renderCanvas.offsetHeight + 6;
console.log("heightDiff = " + heightDiff);
this.marginTop = null;
if (heightDiff > 0) {
// canvas height smaller than available height
if (this.debug) {
console.log("condition 1");
}
//
// TODO : piste simplification
// -- top 0 et style.top -> style.marginTop (comme pour solides, pour éviter problème de pointers ?)
//
renderCanvas.style.height = renderCanvas.offsetHeight + heightDiff + this.globalService.toolbarHeight / 2 + "px";
renderCanvas.style.width = "auto";
const marginLeftRatio = renderCanvas.offsetHeight / renderCanvas.offsetWidth;
renderCanvas.style.marginLeft = (heightDiff + this.globalService.toolbarHeight / 2) * -marginLeftRatio + "px";
if (activityName === "solides") {
renderCanvas.style.top = this.globalService.toolbarHeight * -0.5 + "px";
}
if (activityName === "calculmental" || activityName === "heure" || activityName === "galaxie") {
// si scène n'a pas objets 3D (autre que l'animation MathIA)
renderCanvas.style.top = this.globalService.toolbarHeight === 0 ? "0px" : -heightDiff / 2 + "px";
} else {
// sinon (kim, furet, solides), caler au mieux la hauteur.
if (!this.cabriService.mirrorMode) {
if (activityName === "solides") {
renderCanvas.style.marginTop = 0 + "px";
this.marginTop = 0;
} else {
renderCanvas.style.top = -heightDiff / 2 + "px";
}
} else if (this.cabriService.mirrorMode) {
const newTop = heightDiff / 2 + this.globalService.toolbarHeight + "px";
if (activityName === "solides") {
renderCanvas.style.marginTop = newTop;
this.marginTop = heightDiff / 2 + this.globalService.toolbarHeight;
} else {
renderCanvas.style.top = newTop;
}
} else {
console.error("@sam : is there a else ?");
}
}
condition = 1;
} else if (heightDiff < 0) {
// canvas height bigger than available height
if (this.debug) {
console.log("condition 2");
console.log("offsetHeight = ", window.innerHeight);
}
renderCanvas.style.width = "100vw";
renderCanvas.style.height = "auto";
renderCanvas.style.top = "0px";
// TODO : function in each class setMarginTop?
let ratio;
if (this.globalService.isMobile && !this.cabriService.mirrorMode && activityName !== "solides") {
ratio = 2;
if (activityName === "calculmental") ratio = 2.25;
if (activityName === "jeudekim") ratio = 1.75;
renderCanvas.style.marginTop = heightDiff / ratio + "px";
this.marginTop = heightDiff / ratio;
} else if (!this.cabriService.mirrorMode) {
ratio = 2.25;
if (activityName === "solides") ratio = 2;
renderCanvas.style.marginTop = heightDiff / ratio + "px";
this.marginTop = heightDiff / ratio;
} else if (this.cabriService.mirrorMode) {
ratio = 2.25;
if (activityName === "jeudekim" || activityName === "solides") ratio = 2;
renderCanvas.style.marginTop = heightDiff / ratio + this.globalService.toolbarHeight + "px";
this.marginTop = heightDiff / ratio + this.globalService.toolbarHeight;
} else {
console.error("@sam : is there a else ?");
}
condition = 2;
} else if (heightDiff === 0) {
// canvas height equal to available height
if (this.debug) {
console.log("condition 3");
}
if (activityName === "solides") {
if (!this.cabriService.mirrorMode) {
renderCanvas.style.top = "0";
renderCanvas.style.marginTop = 0 + "px";
this.marginTop = 0;
} else if (this.cabriService.mirrorMode) {
renderCanvas.style.marginTop = this.globalService.toolbarHeight + "px";
}
} else {
const widthOrig = renderCanvas.offsetWidth;
renderCanvas.style.height = renderCanvas.offsetHeight + this.cabriService.toolbarHeight * 2 + "px";
renderCanvas.style.width = "auto";
const widthUpdated = renderCanvas.offsetWidth;
renderCanvas.style.marginLeft = -(widthUpdated - widthOrig) / 2 + "px";
let ratio = 1.5;
if (activityName === "galaxie") {
ratio = 0;
}
renderCanvas.style.top = -this.cabriService.toolbarHeight * ratio + "px";
}
condition = 3;
}
if (activityName === "solides" && this.cabriService.mirrorMode) renderCanvas.style.top = "0px";
if (this.debug) {
console.log("condition resize = " + condition);
}
await AppUtils.timeOut(100);
//
// set page-div (cabri HTML elements container) size and position
//
pageDiv.style.width = "100vw";
pageDiv.style.height = renderCanvas.offsetHeight + "px";
pageDiv.style.marginTop = renderCanvas.style.marginTop;
pageDiv.style.top = renderCanvas.style.top;
pageDiv.style.left = "0";
//
// RESIZE BABYLON ENGINE:
//
window.store.getters?.cabri.Scene?._engine.resize();
await this.timeOut(100);
if (activityName !== "solides" && activityName !== "jeuJustePoint") {
// downscale if more than hd for performance (super slow in 4k):
// break down activity "solides" pointer events
this.rescale4kToHd();
}
// Switch background
await this.updateCabriBackground(renderCanvas, condition);
if (activityName === "solides") {
window.dispatchEvent(new Event("focus"));
}
if (activityName === "calculmental") {
await this.calculateHelpHeight();
}
this.globalService.isKidaia ? (window.document.title = "Kidaia") : (window.document.title = "Mathia");
if (this.page.activateSTTPaused || this.page.validationSTTPaused || this.cabriService.cabriInputDrawStatus) {
this.page.cabri.startRender();
await this.page.timeOutNormal(500);
this.page.detectChanges();
this.page.cabri.stopRender();
console.log("start/stop Render on updateCabriPosition end");
} else {
if (
this.page.sttActivated ||
this.page.validationActive ||
(this.cabriService.cabriInputDrawStatus && activityName !== "heure")
) {
await this.timeOut(100);
this.stopRender();
console.log("stopRender on updateCabriPosition end");
} else {
console.log("startRender on updateCabriPosition end");
this.startRender();
}
}
}
if (this.debug) {
console.log("resize end");
}
resolve();
});
}
async waitCabriReady(renderCanvas, pageDiv) {
return new Promise<void>(resolve => {
if (this.debug) {
console.log("waitcabri - start");
}
this.domReadySubcription = this.domReady.subscribe(() => {
const cabriLeftPadding = window.document.getElementById("renderCanvas1").style.left;
const cabriTop = window.document.getElementById("page-div").style.top;
if (parseFloat(cabriLeftPadding) > 0 || parseFloat(cabriTop) > 0) {
if (this.debug) {
console.log("waitcabri - end ok : ", parseFloat(cabriLeftPadding) > 0 || parseFloat(cabriTop) > 0);
}
} else {
console.error("waitcabri - end", parseFloat(cabriLeftPadding) > 0 || parseFloat(cabriTop) > 0);
}
if (this.domReadySubcription) {
this.domReadySubcription.unsubscribe();
}
resolve();
});
});
}
addToHoloCameras(mesh: any) {
// new meshes must be added to holo cameras if exist
if (!this.holoCameras) {
this.holoCameras = window.store.getters.cabri.SceneUpdate.holoCameras;
}
if (this.holoCameras) {
if (this.holoCameras.rtt_z && this.holoCameras.rtt_z.renderList) {
this.holoCameras.rtt_z.renderList.push(mesh);
}
if (this.holoCameras.rtt_x && this.holoCameras.rtt_x.renderList) {
this.holoCameras.rtt_x.renderList.push(mesh);
}
if (this.holoCameras.rttZ && this.holoCameras.rttZ.renderList) {
this.holoCameras.rttZ.renderList.push(mesh);
}
if (this.holoCameras.rttX && this.holoCameras.rttX.renderList) {
this.holoCameras.rttX.renderList.push(mesh);
}
}
}
returnGeneratedCode(gradientTab, textureImg) {
return new Promise(resolve => {
const nodeMaterial = new BABYLON.NodeMaterial("node");
nodeMaterial.backFaceCulling = false;
// InputBlock
const position = new BABYLON.InputBlock("position");
position.setAsAttribute("position");
// TransformBlock
const worldPos = new BABYLON.TransformBlock("WorldPos");
worldPos.complementZ = 0;
worldPos.complementW = 1;
// InputBlock
const world = new BABYLON.InputBlock("World");
world.setAsSystemValue(BABYLON.NodeMaterialSystemValues.World);
// TransformBlock
const worldPosViewProjectionTransform = new BABYLON.TransformBlock("WorldPos * ViewProjectionTransform");
worldPosViewProjectionTransform.complementZ = 0;
worldPosViewProjectionTransform.complementW = 1;
// InputBlock
const viewProjection = new BABYLON.InputBlock("ViewProjection");
viewProjection.setAsSystemValue(BABYLON.NodeMaterialSystemValues.ViewProjection);
// VertexOutputBlock
const vertexOutput = new BABYLON.VertexOutputBlock("VertexOutput");
vertexOutput.visibleInInspector = false;
vertexOutput.visibleOnFrame = false;
// InputBlock
const uv = new BABYLON.InputBlock("uv");
uv.setAsAttribute("uv");
// Rotate2dBlock
const rotated = new BABYLON.Rotate2dBlock("Rotate2d");
rotated.visibleInInspector = false;
rotated.visibleOnFrame = false;
// InputBlock
const angle = new BABYLON.InputBlock("angle");
angle.value = 5.6;
angle.min = 0;
angle.max = 10;
angle.isBoolean = false;
angle.matrixMode = 0;
angle.animationType = BABYLON.AnimatedInputBlockTypes.None;
angle.isConstant = false;
// InputBlock
const uv1 = new BABYLON.InputBlock("uv");
uv1.setAsAttribute("uv");
// MultiplyBlock
const multiply = new BABYLON.MultiplyBlock("Multiply");
multiply.visibleInInspector = false;
multiply.visibleOnFrame = false;
// GradientBlock
const gradient = new BABYLON.GradientBlock("Gradient");
// Need to add this line for generated code to make colors right
// Gradient.colorSteps = [];
gradientTab.forEach(e => {
gradient.colorSteps.push(new BABYLON.GradientBlockColorStep(e.percent, new BABYLON.Color3(e.r, e.g, e.b)));
});
// TextureBlock
const texture = new BABYLON.TextureBlock("Texture");
texture.texture = new BABYLON.Texture(textureImg, this.scene);
texture.texture.wrapU = 1;
texture.texture.wrapV = 1;
texture.texture.uAng = 0;
texture.texture.vAng = 0;
texture.texture.wAng = 0;
texture.texture.uOffset = 0;
texture.texture.vOffset = 0;
texture.texture.uScale = 1;
texture.texture.vScale = 1;
texture.convertToGammaSpace = false;
texture.convertToLinearSpace = false;
// FragmentOutputBlock
const fragmentOutput = new BABYLON.FragmentOutputBlock("FragmentOutput");
fragmentOutput.visibleInInspector = false;
fragmentOutput.visibleOnFrame = false;
// Connections
position.output.connectTo(worldPos.vector);
world.output.connectTo(worldPos.transform);
worldPos.output.connectTo(worldPosViewProjectionTransform.vector);
viewProjection.output.connectTo(worldPosViewProjectionTransform.transform);
worldPosViewProjectionTransform.output.connectTo(vertexOutput.vector);
uv.output.connectTo(rotated.input);
angle.output.connectTo(rotated.angle);
rotated.output.connectTo(gradient.gradient);
gradient.output.connectTo(multiply.left);
uv1.output.connectTo(texture.uv);
texture.rgb.connectTo(multiply.right);
multiply.output.connectTo(fragmentOutput.rgb);
// Output nodes
nodeMaterial.addOutputNode(vertexOutput);
nodeMaterial.addOutputNode(fragmentOutput);
nodeMaterial.build();
nodeMaterial.backFaceCulling = false;
texture.texture.onLoadObservable.addOnce(() => {
resolve(nodeMaterial);
});
});
}
waitCamera(): Promise<void> {
return new Promise((resolve, reject) => {
this.waitCameraInterval = setInterval(async () => {
if ((this.scene as any).CABRI.Camera2D3D) {
clearInterval(this.waitCameraInterval);
this.camera = (this.scene as any).CABRI.Camera2D3D;
this.page.detectChanges();
resolve();
}
}, 1000);
});
}
/**
* Enable/Disable cabri background in mirrorMode
* Called in checkFlatHoloBackground() & updateCabriPosition()
*/
checkFlatHoloMode(mesh) {
// console.log("checkFlatHoloMode this.cabriService.holoMode = " + this.cabriService.holoMode);
const canvas: HTMLElement = this.page.cd.rootNodes[0].querySelector("#renderCanvas1");
const canvasHolder: HTMLElement = this.page.cd.rootNodes[0].querySelector("#canvasholder1");
if (canvas && canvasHolder) {
if (this.cabriService.holoMode === "0" && this.cabriService.mirrorMode) {
if (this.cabriService.currentActivity.id !== "10") {
// background:
canvas.style.backgroundColor = "rgba(0,0,0,1)";
canvasHolder.style.backgroundColor = "rgba(0,0,0,1)";
window.store.getters.cabri.Scene.clearColor = new BABYLON.Color4(0, 0, 0, 0);
console.log("mesh.layerMask = ", mesh.layerMask);
// 268435456
mesh.layerMask = null;
} else {
mesh.rotation.x = (3 * Math.PI) / 2;
mesh.material.backFaceCulling = false;
}
// reverse canvasHolder:
if (!canvasHolder.classList.contains("mirrorMode")) {
canvasHolder.classList.add("mirrorMode");
}
} else if (this.cabriService.holoMode === "0" && !this.cabriService.mirrorMode) {
// background:
canvas.style.backgroundColor = "white";
window.store.getters.cabri.Scene.clearColor = new BABYLON.Color4(0, 0, 0, 0);
mesh.layerMask = 268435456;
// recover canvasHolder:
if (canvasHolder.classList.contains("mirrorMode")) {
canvasHolder.classList.remove("mirrorMode");
}
}
}
}
/**
* Check if mirrorMode activated or not on restoreDom when no activityChange (and no updateCabriPosition)
*/
checkFlatHoloBackground() {
// console.log("checkFlatHoloBackground()");
window.store.getters.cabri.Scene.meshes.forEach(m => {
if (m.id === "background") {
this.checkFlatHoloMode(m);
}
});
}
async updateCabriBackground(renderCanvas, condition) {
window.store.getters.cabri.Scene.meshes.forEach(async m => {
// m.doNotSyncBoundingInfo = true;
// m.freezeWorldMatrix();
// m.convertToUnIndexedMesh();
if (m.id === "background") {
this.checkFlatHoloMode(m);
await this.switchBackgroundLauncher(
m,
window.document.getElementById("renderCanvas1").clientHeight,
window.document.getElementById("renderCanvas1").clientWidth
);
console.log("cabri background resized");
}
});
}
// Test how background needs to be replaced and launch switchBackground accordingly:
async switchBackgroundLauncher(m, canvasHeight?: any, canvasWidth?: any, solides?: boolean): Promise<void> {
return new Promise(async resolve => {
const assignation = this.page.lmsService?.currentUserJourney;
const storyPlanet = this.cabriService.currentActivity?.story?.chapter?.planet;
if (
storyPlanet &&
!(this.cabriService.holoMode === "0" && this.cabriService.mirrorMode && this.cabriService.currentActivity.id !== "10")
) {
await this.switchBackground(m, storyPlanet, null, canvasHeight, canvasWidth);
} else if (assignation) {
await this.switchBackground(m, null, true, canvasHeight, canvasWidth);
} else {
await this.switchBackground(m, null, null, canvasHeight, canvasWidth);
}
resolve();
});
}
// Switch cabri background depending on context
async switchBackground(m, storyPlanet?: string, assignation?: boolean, canvasHeight?: any, canvasWidth?: any): Promise<void> {
return new Promise(async resolve => {
let textUrl: string;
// for testing:
// storyPlanet = "olympe2Map";
if (storyPlanet === "fireMap") {
const ratio = canvasWidth / canvasHeight;
m.scaling.x = ratio;
m.scaling.z = 1;
}
if (this.cabriService.forceBackgroundTexture) {
this.currentBackground = this.cabriService.forceBackgroundTexture;
const texture: Texture = new BABYLON.DynamicTexture(
"background",
{ width: canvasWidth, height: canvasHeight },
this.scene
) as DynamicTexture;
const textureContext = (texture as any).getContext();
await AppUtils.setCabriBackground(
this.cabriService.http,
texture,
textureContext,
this.cabriService.forceBackgroundTexture,
canvasWidth,
canvasHeight
);
m.material.diffuseTexture = texture;
resolve();
} else {
const ios = this.globalService.lowPerformanceMode ? "_ios" : "";
if (storyPlanet) {
switch (storyPlanet) {
case "fireMap":
textUrl = "assets/babylon/Fire/scenes/scene/files/fond intro" + ios + ".jpg";
break;
case "tropicalMap":
textUrl = "assets/babylon/Tropical/scenes/scene/files/tropicana" + ios + ".jpg";
break;
case "iceMap":
textUrl = "assets/babylon/Ice/scenes/scene/files/fond glacier" + ios + ".jpg";
break;
case "swampMap":
textUrl = "assets/babylon/Swamp/scenes/scene/files/mare" + ios + ".jpg";
break;
case "canyonMap":
textUrl = "assets/babylon/Canyon/scenes/scene/files/canyon" + ios + ".jpg";
break;
case "ruinsMap":
textUrl = "assets/babylon/Ruins/scenes/scene/files/ruine" + ios + ".jpg";
break;
case "halloweenMap":
textUrl = "assets/babylon/Halloween/scenes/scene/files/halloween" + ios + ".jpg";
break;
case "candyMap":
textUrl = "assets/babylon/Candy/scenes/scene/files/fond candy" + ios + ".jpg";
break;
case "kindergartenMap":
textUrl = "assets/babylon/Kindergarten/scenes/scene/files/fond kindergarten" + ios + ".jpg";
break;
case "schoolMap":
textUrl = "assets/babylon/School/scenes/scene/files/fond school" + ios + ".jpg";
break;
case "olympe1Map":
textUrl = "assets/babylon/Olympe1/scenes/scene/files/fond olympe1" + ios + ".jpg";
break;
case "olympe2Map":
textUrl = "assets/babylon/Olympe2/scenes/scene/files/fond olympe2" + ios + ".jpg";
break;
case "spaceshipMap":
textUrl = "assets/babylon/Spaceship/scenes/scene/files/fond spaceship" + ios + ".jpg";
break;
case "fountainMap":
textUrl = "assets/babylon/Fountain/scenes/scene/files/fond fountain" + ios + ".jpg";
break;
case "downtownMap":
textUrl = "assets/babylon/Downtown/scenes/scene/files/fond downtown" + ios + ".jpg";
break;
case "factoryMap":
textUrl = "assets/babylon/Factory/scenes/scene/files/fond factory" + ios + ".jpg";
break;
case "mapWiseMen":
textUrl = "assets/babylon/narration17/scenes/scene/files/background" + ios + ".jpg";
break;
case "map18":
textUrl = "assets/babylon/narration18/scenes/scene/files/background" + ios + ".jpg";
break;
case "map19":
textUrl = "assets/babylon/narration19/scenes/scene/files/background" + ios + ".jpg";
break;
case "map20":
textUrl = "assets/babylon/narration20/scenes/scene/files/background" + ios + ".jpg";
break;
case "map21":
textUrl = "assets/babylon/narration21/scenes/scene/files/background" + ios + ".jpg";
break;
case "map22":
textUrl = "assets/babylon/narration22/scenes/scene/files/background" + ios + ".jpg";
break;
case "map23":
textUrl = "assets/babylon/narration23/scenes/scene/files/background" + ios + ".jpg";
break;
case "map24":
textUrl = "assets/babylon/narration24/scenes/scene/files/background" + ios + ".jpg";
break;
case "map25":
textUrl = "assets/babylon/narration25/scenes/scene/files/background" + ios + ".jpg";
break;
case "map26":
textUrl = "assets/babylon/narration26/scenes/scene/files/background" + ios + ".jpg";
break;
case "map27":
textUrl = "assets/babylon/narration27/scenes/scene/files/background" + ios + ".jpg";
break;
case "map28":
textUrl = "assets/babylon/narration28/scenes/scene/files/background" + ios + ".jpg";
break;
case "map29":
textUrl = "assets/babylon/narration29/scenes/scene/files/background" + ios + ".jpg";
break;
case "map30":
textUrl = "assets/babylon/narration30/scenes/scene/files/background" + ios + ".jpg";
break;
default:
textUrl = environment.kidaia ? "assets/gabarits/fondsKidaia/assignations/stars.jpg" : null;
break;
}
} else if (assignation) {
if(this.accountService.user?.isC3){
textUrl = Math.random()>0.5 ? "assets/img/c3/backgrounds/Fond1.jpg" : "assets/img/c3/backgrounds/Fond2.jpg"
} else {
textUrl = "assets/gabarits/fondsKidaia/assignations/stars" + ios + ".jpg";
}
} else {
textUrl = "assets/gabarits/fondsKidaia/cabri_default/stars_default" + ios + ".jpg";
// textUrl = "assets/gabarits/fondsKidaia/cabri_default/sd1-2.jpg"
// textUrl = "assets/babylon/narration17/scenes/scene/files/background.jpg"
}
if (textUrl) {
this.currentBackground = textUrl;
// const webGLTexture = m.material.diffuseTexture._texture._webGLTexture;
// m.material.diffuseTexture.dispose();
// window.store.getters.cabri.Engine._gl.deleteTexture(webGLTexture);
const texture: Texture = new BABYLON.DynamicTexture(
"background",
{ width: canvasWidth, height: canvasHeight },
this.scene
) as DynamicTexture;
m.material.diffuseTexture = texture;
const textureContext = m.material.diffuseTexture.getContext();
await this.createBackgroundImageDynamic(textUrl, textureContext, m.material.diffuseTexture, canvasHeight, canvasWidth);
}
resolve();
}
});
}
// draw background image in the new resized dynamicTexture:
createBackgroundImageDynamic(
url: string,
textureContext: any,
diffuseTexture: any,
canvasHeight?: any,
canvasWidth?: any
): Promise<any> {
return new Promise(resolve => {
this.cabriService.http
.get(url, {
responseType: "blob"
})
.subscribe(blob => {
const o = new Image();
o.src = window.URL.createObjectURL(blob);
o.onload = () => {
let targetWidth, targetHeight;
if (o.width / o.height > canvasWidth / canvasHeight) {
targetHeight = canvasHeight;
targetWidth = (canvasHeight * o.width) / o.height;
textureContext.drawImage(o, -(targetWidth - canvasWidth) / 2, 0, targetWidth, targetHeight);
} else {
targetHeight = (canvasWidth * o.height) / o.width;
targetWidth = canvasWidth;
textureContext.drawImage(o, 0, -(targetHeight - canvasHeight) / 2, targetWidth, targetHeight);
}
if(this.accountService.user.isC3){
textureContext.fillStyle = "#0D0D0D";
textureContext.globalAlpha = 0.4;
textureContext.rect(0, 0, canvasWidth, canvasHeight);
textureContext.fill();
textureContext.globalAlpha = 1;
}
// textureContext.drawImage(o, image start x, image start y, image width, image height,
// canvas to x, canvas to y, destination width, destination height)
// textureContext.drawImage(o, 0, 0, canvasWidth, canvasHeight);
diffuseTexture.update();
console.log("background texture is now a DynamicTexture resized to = ", diffuseTexture.getSize());
resolve(o);
};
});
});
}
setBackgroundHolo(): { bkgz: Mesh; bkgZ: Mesh; bkgX: Mesh; bkgx: Mesh } {
const bkgz = this.getLastMesh("bkgz");
const bkgZ = this.getLastMesh("bkgZ");
const bkgX = this.getLastMesh("bkgX");
const bkgx = this.getLastMesh("bkgx");
if (bkgz && bkgZ && bkgX && bkgx) {
bkgz.rotation.y = 0;
bkgz.rotation.x = -1.3963;
bkgZ.rotation.y = -Math.PI;
bkgZ.rotation.x = -1.3963;
bkgX.rotation.y = -Math.PI / 2;
bkgX.rotation.x = -1.3963;
bkgx.rotation.y = Math.PI / 2;
bkgx.rotation.x = -1.3963;
//for render loop untag doUpdateBg to stop background update when already done
window.store.getters.cabri.doUpdateBg = 0;
}
return { bkgz, bkgZ, bkgX, bkgx };
}
restoreCamera() {
this.camera.setTarget(new BABYLON.Vector3.Zero());
this.camera.radius = this.initialCameraPosition.radius;
this.camera.alpha = this.initialCameraPosition.alpha;
this.camera.beta = this.initialCameraPosition.beta;
}
async setOrthographicCamera() {
this.cameraHud.orthoTop = 7.425;
this.cameraHud.orthoBottom = -7.425;
this.cameraHud.orthoLeft = -10.5;
this.cameraHud.orthoRight = 10.5;
this.cameraHud.position = new BABYLON.Vector3(0, 0, -50);
this.cameraHud.rotation = new BABYLON.Vector3(0, 0, 0);
}
addEventPointerOnCanvas() {
window.store.getters.cabri.MainDiv.addEventListener(
"pointerdown",
e => {
window.store.getters.cabri.onPointerDown.call(window.store.getters.cabri, e);
},
!1
);
window.store.getters.cabri.MainDiv.addEventListener(
"pointerup",
e => {
window.store.getters.cabri.onPointerUp.call(window.store.getters.cabri, e);
},
!1
);
window.store.getters.cabri.MainDiv.addEventListener(
"pointermove",
e => {
window.store.getters.cabri.onPointerMove.call(window.store.getters.cabri, e);
},
!1
);
}
// preload mascotte meshes for anims and stores them
importMascotteMeshes(sizeHappy = 1.05, sizePuzzled = 1.09): Promise<void> {
return new Promise(async (resolve, reject) => {
const mesh1 = BABYLON.SceneLoader.ImportMeshAsync(
null,
"/assets/mathia/mascotte3d/",
"happy-flame-multires.babylon",
this.scene
).then(result => {
result.meshes.forEach(mesh => {
this.mascotteHappyMesh = mesh;
this.mascotteHappyMesh.scaling = new Vector3(sizeHappy, sizeHappy, sizeHappy);
});
});
// neutral-flame-multires.babylon
const mesh2 = BABYLON.SceneLoader.ImportMeshAsync(
null,
"/assets/mathia/mascotte3d/",
"puzzled-flame-multires.babylon",
this.scene
).then(result => {
result.meshes.forEach(newMesh => {
this.mascottePuzzledMesh = newMesh;
this.mascottePuzzledMesh.scaling = new Vector3(sizePuzzled, sizePuzzled, sizePuzzled);
});
});
await Promise.all([mesh1, mesh2]);
resolve();
});
}
// Launch success/failure animations
async animLauncher(success: boolean = false, mascottePosY = 0.8, mascotteRotation: Vector3 = new Vector3(0, 0, 0)): Promise<void> {
console.log("this.AccountService.user.isC3",this.accountService.user.isC3);
if (!this.accountService.user.isC3) {
try {
if (success) {
await this.animateSucces(this.mascotteHappyMesh, 1.5, mascottePosY, mascotteRotation);
} else {
await this.animateFailure(this.mascottePuzzledMesh, 1.5, mascottePosY, mascotteRotation);
}
} catch (error) {
console.error("Animation error:", error);
}
}
}
// success anim
animateSucces(newMesh: Mesh, speed = 1.5, mascottePosY = 0.8, mascotteRotation: Vector3): Promise<void> {
return new Promise(async (resolve, reject) => {
try {
speed = 2;
newMesh.isVisible = true;
newMesh.layerMask = this.camera.layerMask;
newMesh.renderingGroupId = 1;
newMesh.rotationQuaternion = null;
newMesh.rotation = mascotteRotation;
newMesh.setPivotMatrix(BABYLON.Matrix.Translation(0, 0, 0));
this.addToHoloCameras(newMesh);
const mathiaRotation = this.createAnimation("mathiaRotation", "rotation.y", true);
const keys = [];
const rotateAdjust = 0.2;
keys.push({ frame: 0, value: 0 - rotateAdjust });
// 2tours:
// keys.push({ frame: 45, value: Math.PI * 1 + 0.05 });
// keys.push({ frame: 55, value: Math.PI * 1 + 0.05 });
// 1 tour:
keys.push({ frame: 50, value: Math.PI * 1 + 0.05 });
keys.push({ frame: 100, value: Math.PI * 2 + rotateAdjust });
mathiaRotation.setKeys(keys);
const mathiaDecollage = this.createAnimation("mathiaDecollage", "position.y", false);
const keys2 = [];
keys2.push({ frame: 0, value: mascottePosY });
keys2.push({
frame: 50,
// 2tours:
// value: mascottePosY + (this.cabriService.holoMode === "1" || this.cabriService.holoMode === "-1" ? 0.5 : 1.2)
value: mascottePosY + (this.cabriService.holoMode === "1" || this.cabriService.holoMode === "-1" ? 0.5 : 0.8)
});
keys2.push({ frame: 100, value: mascottePosY });
mathiaDecollage.setKeys(keys2);
// const mathiaScaling = this.createVector3Animation("mathiaScaling", "scaling", false);
// const keys3 = [];
// keys3.push({ frame: 0, value: new BABYLON.Vector3(5, 5, 5) });
// keys3.push({ frame: 50, value: new BABYLON.Vector3(40, 40, 40) });
// mathiaScaling.setKeys(keys3);
/*mathiaDecollage.addEvent(
new BABYLON.AnimationEvent(
99,
(currentFrame: number) => {
newMesh.isVisible = false;
// console.error("endOFANIM");
resolve();
},
true
)
);*/
newMesh.animations = [];
newMesh.animations.push(mathiaRotation);
newMesh.animations.push(mathiaDecollage);
// newMesh.animations.push(mathiaScaling);
this.scene.beginAnimation(newMesh, 0, 100, false, speed).onAnimationEndObservable.addOnce(() => {
newMesh.isVisible = false;
// console.error("endOFANIM");
resolve();
});
} catch (error) {
reject(error);
}
});
}
// failure anim
animateFailure(newMesh: Mesh, speed = 1.5, mascottePosY = 0.8, mascotteRotation): Promise<void> {
return new Promise(async (resolve, reject) => {
try {
newMesh.isVisible = true;
newMesh.layerMask = this.camera.layerMask;
newMesh.renderingGroupId = 1;
newMesh.rotationQuaternion = null;
newMesh.rotation = mascotteRotation;
newMesh.setPivotMatrix(BABYLON.Matrix.Translation(0, 0, 0));
this.addToHoloCameras(newMesh);
const mathiaRotation = this.createAnimation("mathiaRotation", "rotation.y", true);
const keys = [];
const rotateAdjust = 0.2;
keys.push({ frame: 0, value: 0 });
keys.push({ frame: 15, value: 0 });
// keys.push({ frame: 10, value: Math.PI / 4 + rotateAdjust});
// keys.push({ frame: 20, value: -Math.PI / 4 + rotateAdjust});
keys.push({ frame: 30, value: Math.PI / 4 + rotateAdjust });
keys.push({ frame: 40, value: -Math.PI / 4 + rotateAdjust });
keys.push({ frame: 50, value: Math.PI / 4 + rotateAdjust });
keys.push({ frame: 60, value: -Math.PI / 4 + rotateAdjust });
keys.push({ frame: 70, value: Math.PI / 4 + rotateAdjust });
keys.push({ frame: 80, value: -Math.PI / 4 + rotateAdjust });
keys.push({ frame: 85, value: 0 });
keys.push({ frame: 100, value: 0 });
mathiaRotation.setKeys(keys);
// const mathiaDecollage = this.createAnimation("mathiaDecollage", "position.y", false);
// const keys2 = [];
// keys2.push({ frame: 0, value: mascottePosY });
// // keys2.push({ frame: 50, value: mascottePosY });
// keys2.push({ frame: 100, value: mascottePosY });
// mathiaDecollage.setKeys(keys2);
// const mathiaScaling = this.createVector3Animation("mathiaScaling", "scaling", false);
// const keys3 = [];
// keys3.push({ frame: 0, value: new BABYLON.Vector3(5, 5, 5) });
// keys3.push({ frame: 50, value: new BABYLON.Vector3(40, 40, 40) });
// mathiaScaling.setKeys(keys3);
/*mathiaRotation.addEvent(
new BABYLON.AnimationEvent(
99,
(currentFrame: number) => {
newMesh.isVisible = false;
console.error("endOFANIM");
resolve();
},
true
)
);*/
newMesh.animations = [];
newMesh.animations.push(mathiaRotation);
// newMesh.animations.push(mathiaDecollage);
// newMesh.animations.push(mathiaScaling);
this.scene.beginAnimation(newMesh, 0, 100, false, speed).onAnimationEndObservable.addOnce(() => {
newMesh.isVisible = false;
// console.error("endOFANIM");
resolve();
});
} catch (error) {
reject(error);
}
});
}
// replace cabri's light by a new one to make animations mascotte brighter
replaceCabriLights() {
this.disableCabriLights();
this.createNewLight();
}
disableCabriLights() {
this.scene.getNodes().forEach(node => {
if (node.name === "light1") {
this.light1 = node as any;
this.light1.setEnabled(false);
}
if (node.name === "light2") {
this.light2 = node;
this.light2.setEnabled(false);
}
});
}
createNewLight() {
this.hemisphericLight = new HemisphericLight("Hemispheric Light", new Vector3(0, 1, -3), this.scene);
this.hemisphericLight.intensity = 1.1;
this.hemisphericLight.diffuse = new BABYLON.Color3(1, 1, 1);
this.hemisphericLight.specular = new BABYLON.Color3(0, 0, 0);
this.hemisphericLight.groundColor = new BABYLON.Color3(0, 0, 0);
}
/**
* return the top Parent of a Mesh or herself if no parent for cabri class
* @param target
* @returns Mesh
*/
getLastParentCabri(target: Mesh): Mesh {
if (target.parent && target.parent instanceof BABYLON.Mesh) {
const tParent = target.parent as Mesh;
if (tParent.parent && tParent.parent instanceof BABYLON.Mesh) {
return this.getLastParentCabri(target.parent as Mesh);
} else {
return tParent;
}
} else {
return target;
}
}
/**
* return the top Parent of a Mesh or herself if no parent
* @param target
* @returns Mesh
*/
getLastParent(target: Mesh): Mesh {
if (target.parent && target.parent instanceof Mesh) {
if (target.parent.parent && target.parent.parent instanceof Mesh) {
return this.getLastParent(target.parent);
} else {
return target.parent;
}
} else {
return target;
}
}
hideAllMeshes() {
return new Promise<void>(resolve => {
if (this.camera) {
// create new camera to show loader without cabri_camera movements
this.loaderCamera = this.camera.clone("loaderCamera");
this.loaderCamera.layerMask = 0x40000000;
// in mirror mode no background
if (!this.cabriService.mirrorMode) {
// create new background to show behind loader without cabri movements
const background = this.getMesh("background");
if (background) {
const background2 = background.clone();
background2.layerMask = 0x40000000;
background2.id = "background2";
// wait next frame for background2 render
this.scene.onAfterRenderObservable.addOnce(() => {
// change active camera
this.scene.activeCameras = [this.loaderCamera];
resolve();
});
} else {
resolve();
console.error("background noot exist");
}
} else {
resolve();
}
} else {
resolve();
}
});
}
showAllMeshes() {
return new Promise<void>(resolve => {
if (this.camera) {
// reactivate default camera
this.scene.activeCameras = [this.camera];
if (this.scene) {
// wait next frame for active camera change
this.scene.onAfterRenderObservable.addOnce(async () => {
// destroy loader's camera and background
this.loaderCamera.dispose();
if (!this.cabriService.mirrorMode) {
const background = this.getMesh("background2");
if (background) {
background.dispose();
}
}
await AppUtils.timeOut(20);
resolve();
});
} else {
resolve();
}
} else {
resolve();
}
});
}
customLoadingScreen() {
BABYLON.DefaultLoadingScreen.prototype.displayLoadingUI = () => {
this.page.customLoader = true;
this.hideAllMeshes();
};
BABYLON.DefaultLoadingScreen.prototype.hideLoadingUI = () => {
this.page.customLoader = false;
this.showAllMeshes();
};
}
/**
* remove cabri canvas scroll events
*/
removeCabriScroll() {
(window.store.getters.cabri.Canvas as HTMLCanvasElement).eventListeners("wheel").forEach(eventListener => {
(window.store.getters.cabri.Canvas as HTMLCanvasElement).removeEventListener("wheel", eventListener);
});
}
/**
* get the DefaultRenderingPipeline which comes from the editor's scene and set its MSAA / FXAA
*/
setMSAAAndFXAA() {
this.scene.postProcessRenderPipelineManager.supportedPipelines.forEach(pp => {
if (pp.name === "defaultPipeline") {
// console.error("pp.fxaaEnabled = ", (pp as any).fxaaEnabled);
// disable FXAA
(pp as any).fxaaEnabled = false;
// change MSAA samples
(pp as DefaultRenderingPipeline).samples = this.globalService.lowPerformanceMode ? 2 : 4;
}
});
}
public faceCameraMesh(mesh: Mesh, camera: ArcRotateCamera) {
mesh.setPreTransformMatrix(camera.getViewMatrix().getRotationMatrix().invert());
}
/**
* Replace all original material into a mesh and it's children
* @param mesh
* @param original
* @param replacement
* @param assetsContainer
* @param materials
*/
replaceMaterialOnMesh(
mesh: Mesh,
original: Material,
replacement: Material,
assetsContainer?: AssetContainer,
materials?: Map<string, StandardMaterial>
) {
if (!materials) {
materials = new Map<string, StandardMaterial>();
}
if (mesh.material instanceof BABYLON.MultiMaterial) {
const multimat = mesh.material as MultiMaterial;
const indexSubmat = multimat.subMaterials.findIndex((material, index) => {
return material.name === original.name || material.id === original.id;
});
if (indexSubmat > -1) {
let multimatTextureReplacement: MultiMaterial = materials.get(multimat.name + replacement.name) as unknown as MultiMaterial;
if (!multimatTextureReplacement) {
multimatTextureReplacement = multimat.clone(multimat.name + replacement.name);
if (assetsContainer) {
assetsContainer.multiMaterials.push(multimatTextureReplacement);
}
multimatTextureReplacement.subMaterials[indexSubmat] = replacement;
materials.set(multimatTextureReplacement.name, multimatTextureReplacement as unknown as StandardMaterial);
}
mesh.material = multimatTextureReplacement;
}
} else {
if (mesh.material?.name === original?.name || mesh.material?.id === original?.id) {
mesh.material = replacement;
}
}
if (mesh.getChildMeshes && mesh.getChildMeshes.length > 0) {
mesh.getChildMeshes().forEach(mesh => {
this.replaceMaterialOnMesh(mesh as Mesh, original, replacement);
});
}
}
/**
* Activate debug mode
*/
debugMode() {
if (!this.page.environment.production) {
this.scene.debugLayer.show({
embedMode: true
});
}
}
/**
* Generate a bounding box with the size off all children element combine
* @param mesh
*/
generateBoundingBoxFromAllChild(mesh: Mesh) {
const childMeshes = mesh.getChildMeshes();
let min = childMeshes[0].getBoundingInfo().boundingBox.minimum;
let max = childMeshes[0].getBoundingInfo().boundingBox.maximum;
for (let i = 0; i < childMeshes.length; i++) {
let meshMin = childMeshes[i].getBoundingInfo().boundingBox.minimum;
let meshMax = childMeshes[i].getBoundingInfo().boundingBox.maximum;
min = BABYLON.Vector3.Minimize(min, meshMin);
max = BABYLON.Vector3.Maximize(max, meshMax);
}
mesh.setBoundingInfo(new BABYLON.BoundingInfo(min, max));
}
/**
* freezeMaterial material into a mesh and it's children
* @param mesh
*/
freezeMaterialOnMesh(mesh: Mesh) {
mesh.material.freeze();
if (mesh.getChildMeshes && mesh.getChildMeshes.length > 0) {
mesh.getChildMeshes().forEach(mesh => {
this.freezeMaterialOnMesh(mesh as Mesh);
});
}
}
/**
* Clone a material with a simple Color3
* @param material
* @param color
* @param name
* @returns
*/
createColorMaterialFromClone(material: Material, color: Color3, name: string, assetsContainer?: AssetContainer): Material {
const cloneMaterial = material.clone("Material_" + name);
if (cloneMaterial instanceof BABYLON.PBRMaterial) {
const cloneMaterialPBR = cloneMaterial as PBRMaterial;
cloneMaterialPBR.albedoColor = color;
} else if (cloneMaterial instanceof BABYLON.StandardMaterial) {
const cloneMaterialStandard = cloneMaterial as StandardMaterial;
cloneMaterialStandard.diffuseColor = color;
} else {
console.log("wtf");
}
if (assetsContainer) {
assetsContainer.materials.push(cloneMaterial);
}
return cloneMaterial;
}
/**
* create a simple material with a Color
* @param color
* @param name
* @param assetsContainer
* @returns
*/
createColorMaterial(color: Color3, name: string, assetsContainer?: AssetContainer): Material {
let material = new BABYLON.StandardMaterial("Material_" + name, this.scene);
material.diffuseColor = color;
material.specularColor = new BABYLON.Color3(0, 0, 0) as Color3;
material.emissiveColor = new BABYLON.Color3(0, 0, 0) as Color3;
material.ambientColor = new BABYLON.Color3(0, 0, 0) as Color3;
if (assetsContainer) {
assetsContainer.materials.push(material);
}
return material;
}
destroy() {
this.perfModeSubscription?.unsubscribe();
if (this.waitCameraInterval) {
clearInterval(this.waitCameraInterval);
}
}
}