File

src/app/models/cabri-integration.ts

Constructor

constructor(cabriService: CabriDataService, globalService: GlobalService, _ngZone: NgZone, page: any, renderer: Renderer2)

Methods

onBabylonReady
onBabylonReady()
Returns: any
unlockRotation
unlockRotation()
Returns: void
startRender
startRender(force: any)
Returns: void
waitNextRender
waitNextRender()
Returns: void
stopRender
stopRender(stop: any)

Stops cabri render
"stop" param is used only for "jeu de kim" overload method

Returns: void
rescale4kToHd
rescale4kToHd()

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

Returns: void
Public launchConfetti
launchConfetti(duration: number, position: Vector3)

Confetti animation

Parameters :
  • duration

    animation duration

Returns: void
restartRender
restartRender()
Returns: void
forceRender
forceRender()
Returns: void
clearCabriMeshes
clearCabriMeshes()
Returns: void
cleanCabri
cleanCabri()
Returns: void
cleanWebGl
cleanWebGl(gl: any)
Returns: void
saveDom
saveDom()
Returns: void
restoreDom
restoreDom()
Returns: void
cleanSolidesScene
cleanSolidesScene()
Returns: void
cleanKimScene
cleanKimScene()
Returns: void
setResponse
setResponse(setOperationMode: string, value: any)
Returns: void
clearResponse
clearResponse(setOperationMode: string, setOperationParam2: string)
Returns: any
toggleOperationWrapperPosition
toggleOperationWrapperPosition(arg0: string)
Returns: void
calculateOperationWrapperPosition
calculateOperationWrapperPosition(repeatQuestionWithKeyboard: boolean)
Returns: void
calculateHelpHeight
calculateHelpHeight()
Returns: void
hideOperationAndWrapper
hideOperationAndWrapper()
Returns: void
showOperationAndWrapper
showOperationAndWrapper()
Returns: void
getCurrentOperation
getCurrentOperation()
Returns: string
getOperationNumber
getOperationNumber()
Returns: void
setFingers
setFingers()
Returns: void
setOperation
setOperation(setOperationMode: string, setOperationParam2: string)
Returns: void
getUserOperation
getUserOperation(mode: string)
Returns: string
checkActivityChange
checkActivityChange()
Returns: boolean
hackHelpImageRendering
hackHelpImageRendering()
Returns: void
NumberToLetter
NumberToLetter(nombre: any, U: any, D: any)
Returns: void
checkHoloModeChange
checkHoloModeChange()
Returns: void
generateCss
generateCss(tabCSS: any)
Returns: void
toggleUserResponse
toggleUserResponse(value: boolean, operation: string)
Returns: void
updateSVG
updateSVG()
Returns: void
generateCustomCSS
generateCustomCSS(holoMode: boolean, styleElement: any)
Returns: void
getPasFromHTML
getPasFromHTML()
Returns: void
getFirstNumberFromHTML
getFirstNumberFromHTML()
Returns: void
timeOut
timeOut(ms: any, callback: any)

Custom async setTimeOut for waiting end of exe

Returns: any
getMesh
getMesh(id: any)
Returns: void
getLastMesh
getLastMesh(id: any)
Returns: void
getModels
getModels()
Returns: void
getCabriMesh
getCabriMesh(id: any, name: any, forceWait: boolean)
Returns: any
getCabriMeshAsync
getCabriMeshAsync(id: any, name: any, forceWait: boolean)
Returns: any
getCabriObject
getCabriObject(id: any)
Returns: void
loadMathia
loadMathia(animationCallback: any)
Returns: void
createAnimation
createAnimation(animationName: any, meshParameter: any, loop: any, frameDuration: number)
Returns: void
createVector3Animation
createVector3Animation(animationName: any, meshParameter: any, loop: any, frameDuration: number)
Returns: void
updateCabriPosition
updateCabriPosition(holoMode: string, activityName: string)

Update DOM Cabri elements

Parameters :
  • holoMode

    Holo mode

  • platform

    Platform

Returns: any
waitCabriReady
waitCabriReady(renderCanvas: any, pageDiv: any)
Returns: void
addToHoloCameras
addToHoloCameras(mesh: any)
Returns: void
returnGeneratedCode
returnGeneratedCode(gradientTab: any, textureImg: any)
Returns: void
waitCamera
waitCamera()
Returns: any
checkFlatHoloMode
checkFlatHoloMode(mesh: any)

Enable/Disable cabri background in mirrorMode
Called in checkFlatHoloBackground() & updateCabriPosition()

Returns: void
checkFlatHoloBackground
checkFlatHoloBackground()

Check if mirrorMode activated or not on restoreDom when no activityChange (and no updateCabriPosition)

Returns: void
updateCabriBackground
updateCabriBackground(renderCanvas: any, condition: any)
Returns: void
switchBackgroundLauncher
switchBackgroundLauncher(m: any, canvasHeight: any, canvasWidth: any, solides: boolean)
Returns: any
switchBackground
switchBackground(m: any, storyPlanet: string, assignation: boolean, canvasHeight: any, canvasWidth: any)
Returns: any
createBackgroundImageDynamic
createBackgroundImageDynamic(url: string, textureContext: any, diffuseTexture: any, canvasHeight: any, canvasWidth: any)
Returns: any
setBackgroundHolo
setBackgroundHolo()
Returns: { bkgz: Mesh; bkgZ: Mesh; bkgX: Mesh; bkgx: Mesh; }
restoreCamera
restoreCamera()
Returns: void
setOrthographicCamera
setOrthographicCamera()
Returns: void
addEventPointerOnCanvas
addEventPointerOnCanvas()
Returns: void
importMascotteMeshes
importMascotteMeshes(sizeHappy: number, sizePuzzled: number)
Returns: any
animLauncher
animLauncher(success: boolean, mascottePosY: number, mascotteRotation: Vector3)
Returns: any
animateSucces
animateSucces(newMesh: Mesh, speed: number, mascottePosY: number, mascotteRotation: Vector3)
Returns: any
animateFailure
animateFailure(newMesh: Mesh, speed: number, mascottePosY: number, mascotteRotation: any)
Returns: any
replaceCabriLights
replaceCabriLights()
Returns: void
disableCabriLights
disableCabriLights()
Returns: void
createNewLight
createNewLight()
Returns: void
getLastParentCabri
getLastParentCabri(target: Mesh)

return the top Parent of a Mesh or herself if no parent for cabri class

Parameters :
  • target
Returns: Mesh

Mesh

getLastParent
getLastParent(target: Mesh)

return the top Parent of a Mesh or herself if no parent

Parameters :
  • target
Returns: Mesh

Mesh

hideAllMeshes
hideAllMeshes()
Returns: void
showAllMeshes
showAllMeshes()
Returns: void
customLoadingScreen
customLoadingScreen()
Returns: void
removeCabriScroll
removeCabriScroll()

remove cabri canvas scroll events

Returns: void
setMSAAAndFXAA
setMSAAAndFXAA()

get the DefaultRenderingPipeline which comes from the editor's scene and set its MSAA / FXAA

Returns: void
Public faceCameraMesh
faceCameraMesh(mesh: Mesh, camera: ArcRotateCamera)
Returns: void
replaceMaterialOnMesh
replaceMaterialOnMesh(mesh: Mesh, original: Material, replacement: Material, assetsContainer: AssetContainer, materials: any)

Replace all original material into a mesh and it's children

Parameters :
  • mesh
  • original
  • replacement
  • assetsContainer
  • materials
Returns: void
debugMode
debugMode()

Activate debug mode

Returns: void
generateBoundingBoxFromAllChild
generateBoundingBoxFromAllChild(mesh: Mesh)

Generate a bounding box with the size off all children element combine

Parameters :
  • mesh
Returns: void
freezeMaterialOnMesh
freezeMaterialOnMesh(mesh: Mesh)

freezeMaterial material into a mesh and it's children

Parameters :
  • mesh
Returns: void
createColorMaterialFromClone
createColorMaterialFromClone(material: Material, color: Color3, name: string, assetsContainer: AssetContainer)

Clone a material with a simple Color3

Parameters :
  • material
  • color
  • name
Returns: Material
createColorMaterial
createColorMaterial(color: Color3, name: string, assetsContainer: AssetContainer)

create a simple material with a Color

Parameters :
  • color
  • name
  • assetsContainer
Returns: Material
destroy
destroy()
Returns: void

Properties

Private _loadingDiv
_loadingDiv: any
_ngZone
_ngZone: NgZone
accountService
accountService: AccountService
Public basketBallMesh
basketBallMesh: any
Public bigHelpHeight
bigHelpHeight: any
Public cabriObjects
cabriObjects: any
Public cabriRenderStarted
cabriRenderStarted: boolean
cabriService
cabriService: CabriDataService
Public camera
camera: ArcRotateCamera
Public cameraHud
cameraHud: ArcRotateCamera
Public canvasHolderHeight
canvasHolderHeight: any
clmcSubscription
clmcSubscription: any
Public collectionsImgs
collectionsImgs: { firstNumber: any[]; secondNumber: any[]; }
currentBackground
currentBackground: string
Default value: /assets/gabarits/fondsKidaia/cabri_default/stars_default.jpg
Public currentNumberOperations
currentNumberOperations: any[]
debug
debug: boolean
Default value: true
domReady
domReady: any
domReadySubcription
domReadySubcription: any
engine
engine: Engine
freezeSceneRender
freezeSceneRender: boolean
Default value: false
globalService
globalService: GlobalService
hemisphericLight
hemisphericLight: HemisphericLight
Public holoCameras
holoCameras: any
Public initialCameraPosition
initialCameraPosition: { radius: number; alpha: number; beta: number; }
light1
light1: any
light2
light2: any
loaderCamera
loaderCamera: any
marginTop
marginTop: number
Public mascotteHappyMesh
mascotteHappyMesh: Mesh
mascotteMesh
mascotteMesh: AbstractMesh
Public mascottePuzzledMesh
mascottePuzzledMesh: Mesh
Public mathiaMesh
mathiaMesh: any
Public operationCalled
operationCalled: string
Public operationWrapperID
operationWrapperID: string
Public operationWrapperMethods
operationWrapperMethods: { paramCollections: any; paramSimpleImg: any; }
page
page: any
Public pageDivHeight
pageDivHeight: any
Public pageDivWidth
pageDivWidth: any
Public paramDisplayMode
paramDisplayMode: any
perfModeSubscription
perfModeSubscription: any
renderer
renderer: Renderer2
Public rotationInterval
rotationInterval: any
Public saveDOMDisabled
saveDOMDisabled: boolean
Default value: false
Public scene
scene: Scene
Public smallHelpHeight
smallHelpHeight: any
Private waitCameraInterval
waitCameraInterval: Timeout
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);
		}
	}
}

results matching ""

    No results matching ""