File

src/app/models/cabri-integration-solides.ts

Constructor

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

Methods

Public pickWithBoundingInfo
pickWithBoundingInfo(x: number, y: number, predicate: (mesh: AbstractMesh) => boolean, camera: Camera)
Returns: PickingInfo
intersection
intersection(boudindBox: BoundingBox, ray: Ray)
Returns: void
createCustomTube
createCustomTube(a: Vector3, b: Vector3, name: string, v: any)
Returns: void
onBabylonReady
onBabylonReady()
Returns: void
getTexture
getTexture(type: string, number: any, gradient: any)
Returns: void
randomColor
randomColor()
Returns: void
meshPicked
meshPicked(mainMesh: any, subMeshId: any, randomColor: any, randomGradient: any)
Returns: void
getMeshTorotate
getMeshTorotate()
Returns: void
rotateMeshesLeft
rotateMeshesLeft(meshes: any, ratio: number)
Returns: void
rotateMeshesRight
rotateMeshesRight(meshes: any, ratio: number)
Returns: void
rotateMeshesUp
rotateMeshesUp(meshes: any, ratio: number)
Returns: void
rotateMeshesDown
rotateMeshesDown(meshes: any, ratio: number)
Returns: void
rotationFunction
rotationFunction()
Returns: void
updateCabriPosition
updateCabriPosition(holoMode: string)

Update DOM Cabri elements

Parameters :
  • holoMode

    Holo mode

  • platform

    Platform

Returns: any
updateCabriBackground
updateCabriBackground(renderCanvas: any, condition: any)
Returns: void
resetZoomToRadius
resetZoomToRadius()
Returns: void
checkActivityChange
checkActivityChange()
Returns: boolean
saveDom
saveDom()
Returns: void
restoreDom
restoreDom(activityChange: boolean)
Returns: void
getCurrentOperation
getCurrentOperation()
Returns: string
clearResponse
clearResponse(mode: string, value: any)
Returns: any
setResponse
setResponse(mode: string, value: any)
Returns: void
test3D
test3D()
Returns: void
distance
distance(pointA: any, pointB: any)
Returns: void
forceRenderOn
forceRenderOn()
Returns: void
forceRenderOff
forceRenderOff()
Returns: void
rotateLeft
rotateLeft($event: any)
Returns: void
rotateRight
rotateRight($event: any)
Returns: void
rotateUp
rotateUp($event: any)
Returns: void
rotateDown
rotateDown($event: any)
Returns: void
stop
stop($event: any)
Returns: void
resetposition
resetposition($event: any)
Returns: void
resetPatron
resetPatron()
Returns: void
updateBabylonJSObject
updateBabylonJSObject(id: any)
Returns: void
deleteIntersectingMeshes
deleteIntersectingMeshes(meshes: any)
Returns: void
mergeMeshes
mergeMeshes(meshes: any, id: any, info: any)
Returns: void
isEdge
isEdge(mesh: any)
Returns: void
isVertice
isVertice(mesh: any)
Returns: void
deleteObject
deleteObject(id: any)
Returns: void
lockRotation
lockRotation()
Returns: any
unlockRotation
unlockRotation()
Returns: void
showEdges
showEdges()
Returns: void
getPatronMeshesPositions
getPatronMeshesPositions()
Returns: void
setPatronMeshesPositions
setPatronMeshesPositions(positions: any)
Returns: void
checkPatronOK
checkPatronOK(next: boolean)
Returns: void
check
check(x: any, y: any)
Returns: void
loadMathia
loadMathia(next: boolean)
Returns: void
sortMeshesByName
sortMeshesByName(meshes: any)
Returns: void
removeCabriMouseEvents
removeCabriMouseEvents()
Returns: void
restoreCabriMouseEvents
restoreCabriMouseEvents()
Returns: void
getPickedElement
getPickedElement()
Returns: void
textureArray
textureArray(folder: string, size: number)
Returns: void
setTexture
setTexture(type: any, number: any, gradNumber: any, textureMaterial: any)
Returns: void
restoreCameraTarget
restoreCameraTarget()
Returns: void
setCameraDefaultPosition
setCameraDefaultPosition()
Returns: void

Properties

_ngZone
_ngZone: NgZone
Public _zoomToRadius
_zoomToRadius: number
afterRenderFunk
afterRenderFunk: () => void
Private cabriEventsOff
cabriEventsOff: boolean
Default value: false
cabriService
cabriService: CabriDataService
Public colorSolid
colorSolid: boolean
Default value: false
currentFPS
currentFPS: any
Public currentMeshId
currentMeshId: any
divider
divider: number
Default value: 0
edgeColoredCounter
edgeColoredCounter: number
Default value: 0
facetColoredCounter
facetColoredCounter: number
Default value: 0
globalService
globalService: GlobalService
grad1
grad1: any
grad2
grad2: any
grad3
grad3: any
isRenderForcedOn
isRenderForcedOn: boolean
Public markEdges
markEdges: boolean
Default value: false
markEdgesDone
markEdgesDone: boolean
Public markFacets
markFacets: boolean
Default value: false
markFacetsDone
markFacetsDone: boolean
Public markVertices
markVertices: boolean
Default value: false
markVerticesDone
markVerticesDone: boolean
Public mathiaMesh
mathiaMesh: any
nbGradients
nbGradients: number
numbersTextures
numbersTextures: {}
page
page: SolidesPage
Public patron
patron: boolean
Default value: false
patronDone
patronDone: boolean
Default value: false
Private previousMouseMoveEvt
previousMouseMoveEvt: any
Private rDown
rDown: boolean
Default value: false
renderer
renderer: Renderer2
Public representation
representation: boolean
Default value: false
Private rLeft
rLeft: boolean
Default value: false
Private rotationAngle
rotationAngle: number
Default value: 0.02
Private rRight
rRight: boolean
Default value: false
Private rUp
rUp: boolean
Default value: false
Private saveEvents
saveEvents: { onPointerDown: any; onPointerUp: any; onPointerMove: any; onTouchMove: any; }
verticeColoredCounter
verticeColoredCounter: number
Default value: 0
verticesDone
verticesDone: any[]
zoomToRadius
zoomToRadius: number
Public zoomToRadiusOptions
zoomToRadiusOptions: { step: string; min: string; max: string; }
import { Color3 } from "@babylonjs/core";
import { LayersModel } from "@tensorflow/tfjs-layers";
import { ProgressionExerciseStatement } from "./progression-exercise-statement";
import { CabriIntegration } from "./cabri-integration";
import { CabriDataService } from "../services/cabri-data.service";
import { GlobalService } from "../services/global.service";
import { NgZone, Renderer2 } from "@angular/core";
import { SolidesPage } from "../page/solides/solides.page";
import { RemoteCommandSolides } from "../services/remote.service";
import { AppUtils } from "../app-utils";
import { ExerciseType, SolideSubjects } from "./exercices-solides";
import { AwardsType } from "./enums/awards";
import { AbstractMesh, Camera, Mesh, Nullable, PickingInfo, Plane, Quaternion, Ray, Space, SubMesh, Vector3 } from "babylon4.1";
import * as BABYLON from "babylon4.1";
import { exit } from "process";

declare var window: {
	store: any;
	document: any;
	innerWidth: any;
	innerHeight: any;
	outerWidth: any;
	outerHeight: any;
	dispatchEvent: any;
	Globals: any;
	createCustomTube: any;
};
//declare var BABYLON ;

export class CabriIntegrationSolides extends CabriIntegration {
	markVerticesDone: boolean;
	markEdgesDone: boolean;
	markFacetsDone: boolean;
	patronDone = false;
	divider = 0;
	currentFPS: any;
	grad1: any;
	grad2: any;
	grad3: any;
	nbGradients: number;
	numbersTextures = {};
	isRenderForcedOn: boolean;
	verticesDone = [];

	constructor(
		public cabriService: CabriDataService,
		public globalService: GlobalService,
		public _ngZone: NgZone,
		public page: SolidesPage,
		public renderer: Renderer2
	) {
		super(cabriService, globalService, _ngZone, page, renderer);
		this.cabriRenderStarted = false;
		this.page = page;
		window.createCustomTube = this.createCustomTube.bind(this);
	}

	public pickWithBoundingInfo(
		x: number,
		y: number,
		predicate?: (mesh: AbstractMesh) => boolean,
		camera?: Nullable<Camera>
	): Nullable<PickingInfo> {
		const ray = Ray.Zero();
		const nullPickInfo = new PickingInfo();
		nullPickInfo.ray = ray;

		let lastPickInfo: PickingInfo = null;
		for (let meshIndex = 0; meshIndex < this.scene.meshes.length; meshIndex++) {
			const mesh = this.scene.meshes[meshIndex];

			//exclude all mesh who don't follow predicate rule
			if (predicate) {
				if (!predicate(mesh)) {
					continue;
				}
			}
			//exclude hidden not pickable or occlude mesh
			if (!mesh.isEnabled() || !mesh.isVisible || !mesh.isPickable || mesh.isOccluded) {
				continue;
			}
			//console.error(mesh);
			const world = mesh.computeWorldMatrix(true);
			//create a ray from camera
			this.scene.createPickingRayToRef(x, y, world, ray, camera);
			if ((mesh.metadata !== "facets" || this.markFacets) && ray.intersectsBox(mesh.getBoundingInfo().boundingBox, 0)) {
				//for each mesh candidate see if her boundingbox intersect and create pickinginfo with distance(ray/interceptionPoint) and submesh result
				//console.error("intersected", mesh);
				let currentPickInfo = new PickingInfo();
				currentPickInfo.hit = true;
				currentPickInfo.pickedMesh = mesh;
				let intersectionPoint = this.intersection(mesh.getBoundingInfo().boundingBox,ray);
				// intersection is by default on localspace up to worldspace
				intersectionPoint = BABYLON.Vector3.TransformCoordinates(intersectionPoint, world);
				/*let sss = BABYLON.MeshBuilder.CreateSphere("grut", { diameter: 0.3 }, this.scene);
				sss.position = intersectionPoint;
				sss.layerMask = 0x10000000;
				var mat1 = new BABYLON.StandardMaterial('mat1', this.scene);
				mat1.diffuseColor = new BABYLON.Color3(0, 1, 0);
				sss.material= mat1;
				sss.renderingGroupId=1;*/
				currentPickInfo.distance = Vector3.Distance(BABYLON.Vector3.TransformCoordinates(ray.origin, world), intersectionPoint);
				//console.error("intersectedDistance ", currentPickInfo.distance);
				if (mesh.subMeshes.length > 1) {
					//search submesh who intersect with the lower distance and return her face id
					let subMeshIndex = { index: 0, range: null };
					for (let subMeshesIndex = 0; subMeshesIndex < mesh.subMeshes.length; subMeshesIndex++) {
						const subMesh = mesh.subMeshes[subMeshesIndex];
						if (ray.intersectsBox(subMesh.getBoundingInfo().boundingBox, 0)) {
							let intersectionPointSm = this.intersection(subMesh.getBoundingInfo().boundingBox,ray);
							intersectionPointSm = BABYLON.Vector3.TransformCoordinates(intersectionPointSm, world);
							let currentSMRange = Vector3.Distance(BABYLON.Vector3.TransformCoordinates(ray.origin, world), intersectionPointSm);
							if (!subMeshIndex.range || currentSMRange < subMeshIndex.range) {
								subMeshIndex.index = subMeshesIndex;
								subMeshIndex.range = currentSMRange;
							}
						}
					}
					currentPickInfo.subMeshId = subMeshIndex.index;
				} else {
					currentPickInfo.subMeshId = 0;
				}
				currentPickInfo.ray = ray;
				if (!lastPickInfo || currentPickInfo.distance < lastPickInfo.distance) {
					// search the closest range replace when a new candidate with lower distance is detected
					lastPickInfo = currentPickInfo;
				}
			} else if (mesh.metadata === "facets" && !this.markFacets) {
				// for facets boundingbox are badly generated use default picker not based on boundingbox
				// the purpose it's to separate occlude part bt facet when we pick vertice or edges
				let currentPickInfo = ray.intersectsMesh(mesh, false);
				if (currentPickInfo.pickedMesh){
					// pinckInfo is by default on localspace up to worldspace
					currentPickInfo.pickedPoint= BABYLON.Vector3.TransformCoordinates(currentPickInfo.pickedPoint, world);
					currentPickInfo.distance = Vector3.Distance(BABYLON.Vector3.TransformCoordinates(ray.origin, world), currentPickInfo.pickedPoint);
				} 
				if (
					(!lastPickInfo && currentPickInfo.pickedMesh) ||
					(currentPickInfo.pickedMesh && currentPickInfo.distance < lastPickInfo.distance)
				) {
					/*
					console.error("intersected", mesh);
					console.error("intersectedDistance ", currentPickInfo.distance);
					
					let sss = BABYLON.MeshBuilder.CreateSphere("grut", { diameter: 0.3 }, this.scene);
					sss.position = currentPickInfo.pickedPoint;
					var mat1 = new BABYLON.StandardMaterial('mat1', this.scene);
					mat1.diffuseColor = new BABYLON.Color3(1, 0, 0);
					sss.material= mat1;
					sss.layerMask = 0x10000000;
					sss.renderingGroupId=1;*/
					lastPickInfo = currentPickInfo;
				}
			} else {
				continue;
			}
		}
		return lastPickInfo ? lastPickInfo : nullPickInfo;
	}

	intersection(boudindBox: BABYLON.BoundingBox, ray: BABYLON.Ray) {
		var d = distance(ray, boudindBox);
		let out : Vector3;
		if (d === Infinity) {
		  out = null
		} else {
			out = new BABYLON.Vector3();
			out.x = ray.origin.x +ray.direction.x * d;
			out.y = ray.origin.y +ray.direction.y * d;
			out.z = ray.origin.z +ray.direction.z * d;
		}
	  
		return out

		function distance (ray: BABYLON.Ray, boudindBox: BABYLON.BoundingBox) {
			var lo = -Infinity
			var hi = +Infinity
		  
			var dimLo = (boudindBox.minimum.x - ray.origin.x) / ray.direction.x;
			var dimHi = (boudindBox.maximum.x  - ray.origin.x) / ray.direction.x;
		
			if (dimLo > dimHi) {
			  var tmp = dimLo
			  dimLo = dimHi
			  dimHi = tmp
			}
		
			if (dimHi < lo || dimLo > hi) {
			  return Infinity
			}
		
			if (dimLo > lo) lo = dimLo
			if (dimHi < hi) hi = dimHi

			var dimLo = (boudindBox.minimum.y - ray.origin.y) / ray.direction.y;
			var dimHi = (boudindBox.maximum.y  - ray.origin.y) / ray.direction.y;
		
			if (dimLo > dimHi) {
			  var tmp = dimLo
			  dimLo = dimHi
			  dimHi = tmp
			}
		
			if (dimHi < lo || dimLo > hi) {
			  return Infinity
			}
		
			if (dimLo > lo) lo = dimLo
			if (dimHi < hi) hi = dimHi

			var dimLo = (boudindBox.minimum.z - ray.origin.z) / ray.direction.z;
			var dimHi = (boudindBox.maximum.z  - ray.origin.z) / ray.direction.z;
		
			if (dimLo > dimHi) {
			  var tmp = dimLo
			  dimLo = dimHi
			  dimHi = tmp
			}
		
			if (dimHi < lo || dimLo > hi) {
			  return Infinity
			}
		
			if (dimLo > lo) lo = dimLo
			if (dimHi < hi) hi = dimHi
		  
			return lo > hi ? Infinity : lo
		  }
	}

	createCustomTube(a: Vector3, b: Vector3, name: string, v: any) {
		//if patron default CreateCustom tube
		if (this.patron) {
			return v.CreateTube(name, {
				path: [a, b],
				radius: 0.05,
				tesselation: 8,
				updatable: !0
			});
		} else {
			//we use corrected boundingbox for edges and vertices
			if (!this.verticesDone[JSON.stringify([b, a])]) {
				const initialPath = new BABYLON.Vector3(a.x, a.y, a.z + BABYLON.Vector3.Distance(a, b));
				const tube = v.CreateTube(name, {
					path: [a, initialPath],
					radius: 0.05,
					updatable: !0
				}) as Mesh;

				var minimum = tube.getBoundingInfo().boundingBox.minimum.clone();
				var maximum = tube.getBoundingInfo().boundingBox.maximum.clone();

				const ratio = 0.2;
				const ratio2 = -0.3;
				var scaling = BABYLON.Matrix.Translation(-ratio, -ratio, -ratio2);
				var scaling2 = BABYLON.Matrix.Translation(ratio, ratio, ratio2);

				minimum = BABYLON.Vector3.TransformCoordinates(minimum, scaling);
				maximum = BABYLON.Vector3.TransformCoordinates(maximum, scaling2);

				tube.setBoundingInfo(new BABYLON.BoundingInfo(minimum, maximum));
				tube.computeWorldMatrix(true);
				//tube.showBoundingBox = true;

				tube.setPivotPoint(a, BABYLON.Space.WORLD);

				//calculate normal
				const normal = BABYLON.Vector3.Cross(initialPath.subtract(a), b.subtract(a)).normalize();
				const angle = BABYLON.Vector3.GetAngleBetweenVectors(initialPath.subtract(a), b.subtract(a), normal);
				tube.rotate(normal, angle, BABYLON.Space.WORLD);

				this.verticesDone[JSON.stringify([a, b])] = tube;
				// tube.lookAt(b);
				return tube;
			} else {
				return this.verticesDone[JSON.stringify([b, a])];
			}
		}
	}
	facetColoredCounter = 0;
	edgeColoredCounter = 0;
	verticeColoredCounter = 0;

	public mathiaMesh;

	public currentMeshId;
	private rotationAngle = 0.02;
	private rLeft = false;
	private rRight = false;
	private rUp = false;
	private rDown = false;

	private cabriEventsOff = false;
	private saveEvents = { onPointerDown: null, onPointerUp: null, onPointerMove: null, onTouchMove: null };
	private previousMouseMoveEvt;

	public markFacets = false;
	public colorSolid = false;
	public markVertices = false;
	public markEdges = false;
	public patron = false;
	public representation = false;
	public _zoomToRadius: number;
	public zoomToRadiusOptions = {
		step: "1",
		min: "125",
		max: "185"
	};

	async onBabylonReady() {
		await super.onBabylonReady();
		//
		// Gestion des pointers events pour remoteControl
		//
		if (this.page.remoteControl && !this.cabriService.mirrorMode && !window.store.getters.cps._pointerEventWithInfo) {
			window.store.getters.cps._pointerEventWithInfo = window.store.getters.cps.pointerEventWithInfo;
			window.store.getters.cps.mathia = this;
			window.store.getters.cps.pointerEventWithInfo = function (x, y, z, element, n) {
				element.pos = Object.keys(window.store.getters.cabri.SceneUpdate.CabriObjects).findIndex(
					(id: any) => Number(id) === element.pickedObjectId
				);
				this.mathia.page.sendEvent(RemoteCommandSolides.patronPositionsParam, { type: "pewi", x, y, z, element, n });
				this._pointerEventWithInfo(x, y, z, element, n);
			};
			window.store.getters.cps._pointerDown = window.store.getters.cps.pointerDown;
			window.store.getters.cps.pointerDown = function (x, y, element, a) {
				if (element) {
					element.pos = Object.keys(window.store.getters.cabri.SceneUpdate.CabriObjects).findIndex(
						(id: any) => Number(id) === element.id
					);
				}
				this.mathia.page.sendEvent(RemoteCommandSolides.patronPositionsParam, { type: "down", x, y, element, a });
				this._pointerDown(x, y, element, a);
			};
			window.store.getters.cps._pointerUp = window.store.getters.cps.pointerUp;
			window.store.getters.cps.pointerUp = function (x, y, element, a) {
				if (element) {
					element.pos = Object.keys(window.store.getters.cabri.SceneUpdate.CabriObjects).findIndex(
						(id: any) => Number(id) === element.id
					);
				}
				this.mathia.page.sendEvent(RemoteCommandSolides.patronPositionsParam, { type: "up", x, y, element, a });
				this._pointerUp(x, y, element, a);
			};
		}
		this.removeCabriMouseEvents();

		//
		// Check patron finished + update remote
		//
		this.scene.onPointerUp = (evt, pickResult) => {
			if (this.patron) {
				if (this.page.remoteControl) {
					setTimeout(() => {
						const patronPositions = this.getPatronMeshesPositions();
						this.page.sendEvent(RemoteCommandSolides.patronPositions, patronPositions);
					}, 1000);
				}
				this.checkPatronOK();
			}
			this.stop();
		};

		//
		// Gestion des clicks
		//
		this.scene.onPointerDown = (evt, pickResult) => {
			let pickResult2;
			// console.error("onPointerDown", this.scene.pointerX, this.scene._engine._gl.canvas.clientHeight - this.scene.pointerY);
			const noMarked = !this.markEdges && !this.markVertices && !this.markFacets;
			if (!noMarked) {
				let pointerY = this.scene.pointerY;
				if (this.cabriService.mirrorMode) {
					//mirror pointer y is reversed
					pointerY = (this.scene as any)._engine._gl.canvas.clientHeight - this.scene.pointerY;
				}
				if (this.markFacets) {
					//facet basic pick work well
					pickResult2 = this.scene.pick(
						this.scene.pointerX,
						pointerY,
						mesh => {
							return this.markFacets && mesh.metadata === "facets";
						},
						false,
						this.camera
					);
				} else {
					//vertices and edges use boundingbox with bigger size
					pickResult2 = this.pickWithBoundingInfo(
						this.scene.pointerX,
						pointerY,
						mesh => {
							//edges and facets to avoid picking invisible part same for vertices
							return (
								(this.markEdges && (mesh.metadata === "edges" || mesh.metadata === "facets")) ||
								(this.markVertices && (mesh.metadata === "vertices" || mesh.metadata === "facets")) ||
								(this.markFacets && mesh.metadata === "facets")
							);
						},
						this.camera
					);
				}
				console.error("pickResult2 = ", pickResult2);

				if (pickResult2.hit) {
					// submesh picked
					this.meshPicked(pickResult2.pickedMesh, pickResult2.subMeshId);
				}
			}
		};

		//
		// Gestion des rotations
		//
		this.scene.onPointerMove = evt => {
			// console.error(evt);
			if (evt.buttons > 0) {
				if (this.previousMouseMoveEvt) {
					const deltaX = evt.x - this.previousMouseMoveEvt.x;
					const deltaY = evt.y - this.previousMouseMoveEvt.y;
					// console.error("deltas", deltaX, deltaY);
					// console.error("event", evt);
					// if (deltaX === 0) {
					// 	this.rLeft = this.rRight = false;
					// }
					// if (deltaY === 0) {
					// 	this.rUp = this.rDown = false;
					// }
					if (deltaX < -5) {
						this.rotateMeshesLeft(this.getMeshTorotate(), 4);
						this.previousMouseMoveEvt = evt;
					}
					if (deltaX > 5) {
						this.rotateMeshesRight(this.getMeshTorotate(), 4);
						this.previousMouseMoveEvt = evt;
					}
					if (deltaY < -5) {
						this.rotateMeshesUp(this.getMeshTorotate(), 4);
						this.previousMouseMoveEvt = evt;
					}
					if (deltaY > 5) {
						this.rotateMeshesDown(this.getMeshTorotate(), 4);
						this.previousMouseMoveEvt = evt;
					}
				} else {
					this.previousMouseMoveEvt = evt;
				}

				if (this.patron) {
					this.page.sendEvent(RemoteCommandSolides.camera, { alpha: this.camera.alpha, beta: this.camera.beta });
				}
			}
		};
		this.scene.registerAfterRender(this.afterRenderFunk);

		// gradient for dynamic materials
		this.grad1 = [
			{ percent: 0.1007, r: 1, g: 0.48, b: 0 },
			{ percent: 0.4655, r: 1, g: 0.76, b: 0 },
			{ percent: 0.8161, r: 0.98, g: 1, b: 0.09 }
		];
		this.grad2 = [
			{ percent: 0, r: 1, g: 0.478, b: 0.729 },
			{ percent: 1, r: 0.933, g: 0, b: 0.447 }
		];
		this.grad3 = [
			{ percent: 0, r: 0.514, g: 0.875, b: 1 },
			{ percent: 0.46, r: 0, g: 0.686, b: 0.925 }
		];

		this.nbGradients = 3;

		if (!this.globalService.lowPerformanceMode) {
			// preload all number textures
			this.textureArray("square", 12);
			this.textureArray("triangle", 20);
			this.textureArray("pyramide", 5);
			this.textureArray("rectangleBig", 6);
			this.textureArray("rectangleSmall", 6);
		}

		// custom loader
		this.customLoadingScreen();
	}

	async getTexture(type: string, number, gradient) {
		if (this.numbersTextures[type] && this.numbersTextures[type][number] && this.numbersTextures[type][number][gradient]) {
			return this.numbersTextures[type][number][gradient];
		} else {
			let grad;
			switch (gradient) {
				case 0:
					grad = this.grad1;
					break;
				case 1:
					grad = this.grad2;
					break;
				case 2:
					grad = this.grad3;
					break;
			}
			const textureMaterial = await this.returnGeneratedCode(
				grad,
				"assets/babylon/textureNumbers/" + type + "/" + (number + 1) + ".png"
			);
			this.setTexture(type, number, gradient, textureMaterial);
			return textureMaterial;
		}
	}
	afterRenderFunk = () => {
		if ((this.divider < 1000 && this.divider % 30 === 0) || this.divider % 600 === 0) {
			this.currentFPS = (this.scene as any)._engine.getFps();
		}
		this.rotationFunction();
	};

	randomColor() {
		return new BABYLON.Color3(0.2 + Math.random() * 0.8, 0.2 + Math.random() * 0.8, 0.2 + Math.random() * 0.8);
	}
	async meshPicked(mainMesh, subMeshId = null, randomColor = null, randomGradient = null) {
		const submeshPicked = subMeshId !== null && mainMesh.subMeshes.length > 1 ? mainMesh.subMeshes[subMeshId] : mainMesh;

		if (!randomColor) {
			randomColor = this.randomColor();
		} else {
			randomColor = new BABYLON.Color3(randomColor.r, randomColor.g, randomColor.b);
		}
		if (!randomGradient) {
			randomGradient = Math.floor(Math.random() * this.nbGradients);
		}
		this.page.sendEvent(RemoteCommandSolides.subMeshPicked, {
			mainMeshId: mainMesh.id,
			subMeshId,
			color: randomColor,
			gradient: randomGradient
		});

		if (this.markEdges && mainMesh.metadata === "edges") {
			// edge clicked
			if (!submeshPicked.clicked) {
				submeshPicked.clicked = true;
				this.edgeColoredCounter++;
				if (this.page.remediationCountingMode) {
					if (
						this.edgeColoredCounter ===
						this.page.SOLIDES_DATA[this.page.currentSolid + "-" + this.page.currentRepresentation].nbEdges - 1
					) {
						this.page.scenario.readCustomText(this.edgeColoredCounter.toString() + "! " + $localize`Et enfin` + " ?");
					} else if (
						!(
							this.edgeColoredCounter ===
							this.page.SOLIDES_DATA[this.page.currentSolid + "-" + this.page.currentRepresentation].nbEdges - 1
						) ){
							this.page.scenario.readCustomText(this.edgeColoredCounter.toString() + "! " + $localize`Et enfin`+" ?");
					} else if (
							!(
								this.edgeColoredCounter ===
								this.page.SOLIDES_DATA[this.page.currentSolid + "-" + this.page.currentRepresentation].nbEdges
							)
						) {
							this.page.scenario.readCustomText(this.edgeColoredCounter.toString());
					}
				}
				this.page.checkEndMarkEdges();
			}
			mainMesh.material.diffuseColor = new BABYLON.Color3(0.08, 0.68, 0.11); // vert
		} else if (this.markVertices && mainMesh.metadata === "vertices") {
			// vertice clicked
			if (!submeshPicked.clicked) {
				submeshPicked.clicked = true;
				this.verticeColoredCounter++;
				if (this.page.remediationCountingMode) {
					if (
						this.verticeColoredCounter ===
						this.page.SOLIDES_DATA[this.page.currentSolid + "-" + this.page.currentRepresentation].nbVertices - 1
					) {
						this.page.scenario.readCustomText(this.verticeColoredCounter.toString() + "! " + $localize`Et enfin` + " ?");
					} else if (
						!(
							this.verticeColoredCounter ===
							this.page.SOLIDES_DATA[this.page.currentSolid + "-" + this.page.currentRepresentation].nbVertices - 1
						)) {
							this.page.scenario.readCustomText(this.verticeColoredCounter.toString() + "! " + $localize`Et enfin`+" ?");
						} else if (
							!(
								this.verticeColoredCounter ===
								this.page.SOLIDES_DATA[this.page.currentSolid + "-" + this.page.currentRepresentation].nbVertices
							)
						) {
							this.page.scenario.readCustomText(this.verticeColoredCounter.toString());
						}
					}
				this.page.checkEndMarkVertices();
			}
			mainMesh.material.diffuseColor = new BABYLON.Color3(1, 0, 0); // rouge
		} else if (mainMesh.metadata === "facets") {
			// facet clicked
			if (this.markFacets) {
				if (!submeshPicked.clicked) {
					submeshPicked.clicked = true;
					submeshPicked.faceNumber = this.facetColoredCounter;
					//submeshPicked.faceNumber=subMeshId;
					this.facetColoredCounter++;
					if (this.page.remediationCountingMode) {
						if (
							this.facetColoredCounter ===
							this.page.SOLIDES_DATA[this.page.currentSolid + "-" + this.page.currentRepresentation].nbFacets - 1
						) {
							this.page.scenario.readCustomText(this.facetColoredCounter.toString() + "! " + $localize`Et enfin`+" ?");
						} else if (
							!(
								this.facetColoredCounter ===
								this.page.SOLIDES_DATA[this.page.currentSolid + "-" + this.page.currentRepresentation].nbFacets
							)
						) {
							this.page.scenario.readCustomText(this.facetColoredCounter.toString());
						}
					}
					this.page.checkEndMarkFacets();
				}

				// draw number on each face
				if (
					(mainMesh.id === "5-1" || mainMesh.id === "7-1" || submeshPicked.id === "7-5-0" || submeshPicked.id === "7-5-1") &&
					submeshPicked.id !== "5-1-0"
				) {
					// pyramide base carrée (sauf base 5-1-0) et base triangle et faces supérieure et inférieure du prisme
					mainMesh.material.subMaterials[subMeshId] = await this.getTexture("pyramide", submeshPicked.faceNumber, randomGradient);
				} else if (mainMesh.id === "2-1" || mainMesh.id === "2-1-bis") {
					// PAVÉ (bis = triangulated polygons)
					if (submeshPicked.id === "2-1-1" || submeshPicked.id === "2-1-3") {
						// Petites faces
						mainMesh.material.subMaterials[subMeshId] = await this.getTexture(
							"rectangleSmall",
							submeshPicked.faceNumber,
							randomGradient
						);
					} else {
						// Grandes faces
						mainMesh.material.subMaterials[subMeshId] = await this.getTexture(
							"rectangleBig",
							submeshPicked.faceNumber,
							randomGradient
						);
					}
				} else if (mainMesh.id === "7-4") {
					// icosaèdre
					mainMesh.material.subMaterials[subMeshId] = await this.getTexture("triangle", submeshPicked.faceNumber, randomGradient);
				} else {
					// les autres
					mainMesh.material.subMaterials[subMeshId] = await this.getTexture("square", submeshPicked.faceNumber, randomGradient);
				}
				mainMesh.material.subMaterials[subMeshId].diffuseColor = randomColor;
			}
		}

		this.page.detectChanges();
	}
	getMeshTorotate() {
		let result = [];
		const mesh = this.getMesh(this.currentMeshId);
		const mesh2 = this.getMesh(this.currentMeshId + "-bis");
		const edges = this.getMesh("edges-parent");
		const vertices = this.getMesh(this.currentMeshId + "-vertices");
		const models = this.getModels();
		if (mesh) {
			result.push(mesh);
		}
		if (mesh2) {
			result.push(mesh2);
		}
		if (edges) {
			result.push(edges);
		}
		if (vertices) {
			result.push(vertices);
		}
		if (models) {
			result = result.concat(models);
		}
		return result;
	}
	rotateMeshesLeft(meshes, ratio = 1) {
		if (this.page.remoteControl) {
			if (this.currentFPS < 30) {
				ratio = ratio * 2;
			}
			if (this.currentFPS < 15) {
				ratio = ratio * 2;
			}
		}
		this.page.sendEvent(RemoteCommandSolides.rotate, { direction: "left", ratio });
		meshes.forEach((mesh: Mesh) => {
			mesh.rotate(BABYLON.Axis.Y, this.rotationAngle * ratio, BABYLON.Space.WORLD);
		});
	}
	rotateMeshesRight(meshes, ratio = 1) {
		if (this.page.remoteControl) {
			if (this.currentFPS < 30) {
				ratio = ratio * 2;
			}
			if (this.currentFPS < 15) {
				ratio = ratio * 2;
			}
		}
		this.page.sendEvent(RemoteCommandSolides.rotate, { direction: "right", ratio });
		meshes.forEach(mesh => {
			mesh.rotate(BABYLON.Axis.Y, -this.rotationAngle * ratio, BABYLON.Space.WORLD);
		});
	}
	rotateMeshesUp(meshes, ratio = 1) {
		if (this.page.remoteControl) {
			if (this.currentFPS < 30) {
				ratio = ratio * 2;
			}
			if (this.currentFPS < 15) {
				ratio = ratio * 2;
			}
		}
		this.page.sendEvent(RemoteCommandSolides.rotate, { direction: "up", ratio });
		meshes.forEach(mesh => {
			mesh.rotate(BABYLON.Axis.X, this.rotationAngle * ratio, BABYLON.Space.WORLD);
		});
	}
	rotateMeshesDown(meshes, ratio = 1) {
		if (this.page.remoteControl) {
			if (this.currentFPS < 30) {
				ratio = ratio * 2;
			}
			if (this.currentFPS < 15) {
				ratio = ratio * 2;
			}
		}
		this.page.sendEvent(RemoteCommandSolides.rotate, { direction: "down", ratio });
		meshes.forEach(mesh => {
			mesh.rotate(BABYLON.Axis.X, -this.rotationAngle * ratio, BABYLON.Space.WORLD);
		});
	}
	rotationFunction() {
		const meshes = this.getMeshTorotate();

		if (meshes.length > 0) {
			// SOLIDES
			if (this.rLeft) {
				this.rotateMeshesLeft(meshes);
			}
			if (this.rRight) {
				this.rotateMeshesRight(meshes);
			}
			if (this.rUp) {
				this.rotateMeshesUp(meshes);
			}
			if (this.rDown) {
				this.rotateMeshesDown(meshes);
			}
		} else {
			// PATRONS
			if (this.rLeft) {
				this.camera.alpha = this.camera.alpha + this.rotationAngle;
				this.page.sendEvent(RemoteCommandSolides.camera, { alpha: this.camera.alpha, beta: this.camera.beta });
			}
			if (this.rRight) {
				this.camera.alpha = this.camera.alpha - this.rotationAngle;
				this.page.sendEvent(RemoteCommandSolides.camera, { alpha: this.camera.alpha, beta: this.camera.beta });
			}
			if (this.rUp) {
				this.camera.beta = this.camera.beta + this.rotationAngle;
				this.page.sendEvent(RemoteCommandSolides.camera, { alpha: this.camera.alpha, beta: this.camera.beta });
			}
			if (this.rDown) {
				this.camera.beta = this.camera.beta - this.rotationAngle;
				this.page.sendEvent(RemoteCommandSolides.camera, { alpha: this.camera.alpha, beta: this.camera.beta });
			}
		}
	}

	/**
	 * Update DOM Cabri elements
	 * @param holoMode Holo mode
	 * @param platform Platform
	 */
	updateCabriPosition(holoMode: string = null): Promise<void> {
		return super.updateCabriPosition(holoMode, "solides");
	}

	async updateCabriBackground(renderCanvas, condition) {
		window.store.getters.cabri.Scene?.meshes.forEach(async m => {
			if (m.id === "background") {
				if (this.page.remoteHostActivate && (this.cabriService.holoMode === "1" || this.cabriService.holoMode === "-1")) {
					m.scaling.x = 1.6;
					m.scaling.z = 1.41;
				} else {
					// background width proportion = 4096/3072 = 1.333333:
					m.scaling.x = 1.5;
					m.scaling.z = 1.5;
					m.position.y = 1.1;
				}
				console.log("cabri background resized");
				this.checkFlatHoloMode(m);
				await this.switchBackgroundLauncher(m, renderCanvas.clientHeight, renderCanvas.clientWidth, false);
				console.log("cabri switchBackgroundLauncher end");
			}
		});
	}

	get zoomToRadius() {
		return this._zoomToRadius;
	}

	set zoomToRadius(value) {
		this.forceRenderOn();
		this.page.sendEvent(RemoteCommandSolides.zoom, value);
		if (this._zoomToRadius !== value) {
			this._zoomToRadius = value;
			this.camera.radius = 195 - value;
			// background scaling with zoom:
			if (!this.page.firstTime) {
				window.store.getters.cabri.Scene?.meshes.forEach(m => {
					if (m.id === "background") {
						if (this.page.remoteHostActivate && (this.cabriService.holoMode === "1" || this.cabriService.holoMode === "-1")) {
							m.scaling.x = 1.7 * (value * 0.01);
							m.scaling.z = 1.5 * (value * 0.01);
						} else {
							m.scaling.x = 1.5 * (value * 0.01);
							m.scaling.z = 1.5 * (value * 0.01);
							m.position.y = 1.1;
						}
					}
				});
			}
			this.page.detectChanges();
		}
		AppUtils.timeOut(100).then(() => {
			this.forceRenderOff();
		});
	}

	resetZoomToRadius() {
		// this.camera.radius = 50;
		this.zoomToRadius = 140;
		this.page.detectChanges();
	}

	checkActivityChange(): boolean {
		return true;
		const activityChange =
			this.cabriService.currentSavedDom !== "solides" ||
			this.cabriService.savedDomHoloMode !== this.cabriService.holoMode ||
			this.cabriService.savedDomMirrorMode !== this.cabriService.mirrorMode;
		// console.log("checkActivityChange = " + activityChange);
		return activityChange;
	}

	saveDom() {
		// temp fix for zoom on restoreDOM:
		this.resetposition();
		super.saveDom();
		this.cabriService.currentSavedDom = "solides";
	}

	restoreDom(activityChange: boolean = false) {
		window.Globals.HoloFaces = 4;
		super.restoreDom();

		if (window.document.querySelector("#page-div section")) {
			window.document.querySelector("#page-div section").style.opacity = "1";
		}
		if (window.document.querySelector("#page-div")) {
			window.document.querySelector("#page-div").style.height = window.innerHeight;
		}

		if (activityChange) {
			// clean all custom elements in #page-div
			if (
				window.document.querySelector("#page-div #operationWrapper") &&
				window.document.querySelector("#page-div #operationWrapper").length > 0
			) {
				window.document.querySelector("#page-div").removeChild(window.document.querySelector("#page-div #operationWrapper"));
			}
			if (
				window.document.querySelector("#page-div #operationWrapperCordova") &&
				window.document.querySelector("#page-div #operationWrapperCordova").length > 0
			) {
				window.document.querySelector("#page-div").removeChild(window.document.querySelector("#page-div #operationWrapperCordova"));
			}
		}

		this.startRender();
	}
	getCurrentOperation(): string {
		const str = "";
		let solidName: string;
		switch (this.page.solidsExercices.exoType) {
			case ExerciseType.name:
				solidName = this.page.solidsExercices.getCurrentSolidName();
				return $localize`Nommer un solide : ${solidName}:solidName:`;
			case ExerciseType.describe:
				solidName = this.page.solidsExercices.getCurrentSolidName();
				switch (this.page.solidsExercices.subject) {
					case SolideSubjects.arete:
						return $localize`Combien d’arêtes à ce solide : ${solidName}:solidName:`;
					case SolideSubjects.faceForme:
						return $localize`Quelle forme ont les faces de ce solide : ${solidName}:solidName:`;
					case SolideSubjects.faceNombre:
						return $localize`Combien de faces à ce solide : ${solidName}:solidName:`;
					case SolideSubjects.sommet:
						return $localize`Combien de sommets à ce solide : ${solidName}:solidName:`;
					default:
						throw new Error("subject not define");
				}
			case ExerciseType.pattern:
				solidName = this.page.solidsExercices.getCurrentSolidName();
				return $localize`Reconnaitre le patron d’un solide : ${solidName}:solidName:`;
			case ExerciseType.recognize:
				solidName = this.page.solidsExercices.getCurrentSolidName();
				return $localize`Reconnaitre un solide : ${solidName}:solidName:`;
			default:
				return "";
		}

		return str;
	}

	clearResponse(mode: string, value: any): Promise<any> {
		// selector
		// #cabriText-34531 > div > div.editor-container > div.editor-content > div > p
		// if(window.document.querySelector('.ZDR .svg-container')){
		//   window.document.querySelector('.ZDR .svg-container').innerHTML = '';
		// }
		// if(window.document.querySelector('.theZDR p')){
		//   window.document.querySelector('.theZDR p').innerHTML = '';
		//   window.document.querySelector('.theZDR').style.opacity = '0';
		// }
		return;
	}

	setResponse(mode: string, value: any) {
		// display response
		// window.document.querySelector('.theZDR p').innerHTML = value;
		// window.document.querySelector('.theZDR').style.opacity = '1';
	}

	/***
	 *
	 * TEST 3D
	 *
	 */
	test3D() {
		let distance = -1;
		let minPoint = null;
		for (const m in window.store.getters.cabri.Scene?.meshes) {
			if (window.store.getters.cabri.Scene.meshes.hasOwnProperty(m)) {
				const mesh = window.store.getters.cabri.Scene.meshes[m];
				if (mesh.object && mesh.object.type === "facet") {
					if (("" + mesh.name).indexOf("_") === -1) {
						// console.log(m, mesh.name, mesh.object, mesh.object.params.pts3D);
						if (distance < 0) {
							distance = this.distance(mesh.object.params.pts3D[0], mesh.object.params.pts3D[1]);
						}
						if (!minPoint) {
							minPoint = JSON.parse(JSON.stringify(mesh.position));
						}

						if (mesh.position.x < minPoint.x && mesh.position.y < minPoint.y && mesh.position.z < minPoint.z) {
							minPoint = mesh.position;
						}
					}
				} else {
					// console.error(mesh.name, m);
				}
			}
		}

		const center = {
			x: minPoint.x + distance / 2,
			y: minPoint.y + distance / 2,
			z: minPoint.z + distance / 2
		};
		// console.log(center);

		const centerRotation = new BABYLON.Vector3(0, center.y, 0);

		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].setPivotPoint(centerRotation);
			}
		}

		for (const m in window.store.getters.cabri.Scene?.meshes) {
			if (window.store.getters.cabri.Scene.meshes.hasOwnProperty(m)) {
				if (window.store.getters.cabri.Scene.meshes[m].id !== "sphere") {
					// console.log(window.store.getters.cabri.Scene.meshes[m].name);
				}
			}
		}
	}

	distance(pointA, pointB) {
		const dx = pointB.x - pointA.x;
		const dy = pointB.y - pointA.y;
		const dz = pointB.z - pointA.z;

		const dist = Math.sqrt(Math.pow(dx, 2) + Math.pow(dy, 2) + Math.pow(dz, 2));

		return dist;
	}
	forceRenderOn() {
		if (!this.page.cabri.cabriRenderStarted) {
			this.isRenderForcedOn = true;
			this.startRender();
		}
	}
	forceRenderOff() {
		if (this.page.cabri.cabriRenderStarted && this.isRenderForcedOn) {
			this.isRenderForcedOn = false;
			this.stopRender();
		}
	}
	async rotateLeft($event = null) {
		this.forceRenderOn();
		if ($event) {
			await this.globalService.waitButtonClick({ buttonClicked: $event.currentTarget }, true);
		}
		this.rLeft = true;
		this.rRight = false;
	}
	async rotateRight($event = null) {
		this.forceRenderOn();
		if ($event) {
			await this.globalService.waitButtonClick({ buttonClicked: $event.currentTarget }, true);
		}
		this.rRight = true;
		this.rLeft = false;
	}
	async rotateUp($event = null) {
		this.forceRenderOn();
		if ($event) {
			await this.globalService.waitButtonClick({ buttonClicked: $event.currentTarget }, true);
		}
		this.rUp = true;
		this.rDown = false;
	}
	async rotateDown($event = null) {
		this.forceRenderOn();
		if ($event) {
			await this.globalService.waitButtonClick({ buttonClicked: $event.currentTarget }, true);
		}
		this.rDown = true;
		this.rUp = false;
	}
	async stop($event = null) {
		this.forceRenderOff();
		if ($event) {
			await this.globalService.waitButtonClick({ buttonClicked: $event.currentTarget }, false);
		}
		this.rLeft = false;
		this.rRight = false;
		this.rUp = false;
		this.rDown = false;
		if (this.page.currentAction === "patron") {
			window.store.getters.cps.setPageOrientation(this.camera.alpha, this.camera.beta);
			await this.timeOut(100);
			this.unlockRotation();
		}
	}

	async resetposition($event = null) {
		this.forceRenderOn();

		let loaderOn = true;
		if (!this.page.customLoader && !this.globalService.globalLoading) {
			loaderOn = false;
			// await this.page.startLoader();
		}
		if ($event) {
			await this.globalService.waitButtonClick({ buttonClicked: $event.currentTarget });
		}
		this.page.sendEvent(RemoteCommandSolides.resetPosition);
		const meshes = this.getMeshTorotate();
		meshes.forEach(mesh => {
			mesh.rotationQuaternion = null;
			// mesh.rotationQuaternion = BABYLON.Quaternion.RotationAxis(new BABYLON.Vector3(0, 1, 0), 0);
		});
		this.resetZoomToRadius();
		if ($event) {
			await this.resetPatron();
		}
		if (!loaderOn) {
			await this.page.stopLoader();
		}
		this.forceRenderOff();
	}
	async resetPatron() {
		if (this.patron) {
			const currentPatron = this.page.currentPatron + 0;
			await this.page.patron();
			await AppUtils.timeOut(100);
			this.page.nextPatronButtonDisabled = false;
			await this.page.patron(currentPatron);
			await AppUtils.timeOut(100);
		}
	}
	updateBabylonJSObject(id) {
		this.facetColoredCounter = 0;
		this.edgeColoredCounter = 0;
		this.verticeColoredCounter = 0;
		this.currentMeshId = id;
		const mergedMeshes = [];
		let mergedEdges = [];
		let mergedVertices = [];

		let modelIndex = 0;
		(this.scene as any)?.meshes.forEach(m => {
			if (m && m.name) {
				// set default uvs for triangulated polygons
				if (m.name === "triangulatedPolygon") {
					m.setVerticesData(BABYLON.VertexBuffer.UVKind, [0, 0, 0, 1, 1, 1, 1, 0]);
				}

				//
				// Identify meshes to merge and set custom UVS
				//

				// facets
				if (m.object && m.object.type && m.object.type === "facet") {
					m.material = m.material.clone();
					const positions = m.getVerticesData(BABYLON.VertexBuffer.PositionKind);
					const nbVertices = positions.length / 3;
					if (nbVertices >= 5) {
						// uvs pentagone
						const uvs = [];
						for (let i = 0; i < 5; i++) {
							const x = 0.5 + Math.cos(-2 * Math.PI * 5 - (2 * Math.PI * i) / 5) / 2;
							const y = 0.5 + Math.sin(-2 * Math.PI * 5 - (2 * Math.PI * i) / 5) / 2;
							uvs.push(x);
							uvs.push(y);
						}
						m.setVerticesData(BABYLON.VertexBuffer.UVKind, uvs);
					} else {
						if (id === "7-4") {
							m.setVerticesData(BABYLON.VertexBuffer.UVKind, [0, 0, 0.5, 0.87, 1, 0]);
						}
						if (id === "7-5") {
							// inverse texture for prisme
							if (nbVertices === 4) {
								m.setVerticesData(BABYLON.VertexBuffer.UVKind, [1, 0, 1, 1, 0, 1, 0, 0]);
							}
						}
					}
					mergedMeshes.push(m);
				}

				// edges
				if (this.isEdge(m)) {
					m.isPickable = true;
					m.material = m.material.clone();
					// const info = m.getBoundingInfo().boundingBox.centerWorld;
					// m.setPivotMatrix(BABYLON.Matrix.Translation(-info.x, -info.y, -info.z));
					m.metadata = "edges";
					mergedEdges.push(m);
				}
				// vertices
				if (this.isVertice(m)) {
					m.isPickable = true;
					m.material = m.material.clone();
					mergedVertices.push(m);
				}

				// model (3ds), cône et cylindre
				if (
					m &&
					m.object &&
					m.object.type &&
					(m.object.type === "model" || m.object.type === "cone" || m.object.type === "cylinder") &&
					(!m.name || String(m.name).indexOf("dumCube") === -1)
				) {
					if (m.parent === null) {
						m.id = id + "-" + modelIndex;
						const info = m.getBoundingInfo().boundingBox.centerWorld;
						// const info = m.position;
						this.camera.setTarget(info);
						// this.camera.setTarget(new BABYLON.Vector3(0,20,0));
						modelIndex++;
					}
				}
				if (m.object && m.object.type && m.object.type === "model" && m.parent === null) {
					const info = m.getBoundingInfo().boundingBox.centerWorld;
					m.setPivotMatrix(BABYLON.Matrix.Translation(-info.x, -info.y, -info.z));
					m.gabarit = "solides";
				}
			}
		});

		//
		// Merge meshes and configure them
		//
		if (mergedMeshes.length > 0) {
			// facets

			// triangulated polygons can't be merged with facets
			const mergedMeshes1 = mergedMeshes.filter(f => f.name !== "triangulatedPolygon");
			const mergedMeshes2 = mergedMeshes.filter(f => f.name === "triangulatedPolygon");

			// inverse UVS for triangulated polygon
			if (mergedMeshes2.length > 0) {
				mergedMeshes2[1].setVerticesData(BABYLON.VertexBuffer.UVKind, [1, 0, 1, 1, 0, 1, 0, 0]);
			}

			const newMesh = this.mergeMeshes(mergedMeshes1, id);
			newMesh.refreshBoundingInfo();
			newMesh.metadata = "facets";
			(newMesh as any).gabarit = "solides";
			if (mergedMeshes2 && mergedMeshes2.length > 0) {
				const solidMesh = this.mergeMeshes(mergedMeshes2, id + "-bis");
				//solidMesh.refreshBoundingInfo();
				solidMesh.metadata = "facets";
				(solidMesh as any).gabarit = "solides";
			}

			// edges
			if (mergedEdges.length > 0) {
				mergedEdges = this.deleteIntersectingMeshes(mergedEdges);
				mergedEdges.forEach((m: Mesh) => {
					m.parent = newMesh;
					m.metadata = "edges";
					(m as any).gabarit = "solides";
					//add occlusion detection occluded are not rendered and status occluded is set
					m.occlusionQueryAlgorithmType = BABYLON.AbstractMesh.OCCLUSION_ALGORITHM_TYPE_ACCURATE;
					m.occlusionType = BABYLON.AbstractMesh.OCCLUSION_TYPE_STRICT;
				});
			}

			// vertices
			if (mergedVertices.length > 0) {
				mergedVertices = this.deleteIntersectingMeshes(mergedVertices);
				mergedVertices.forEach((m: Mesh) => {
					m.parent = newMesh;
					m.metadata = "vertices";
					(m as any).gabarit = "solides";
					var minimum = m.getBoundingInfo().boundingBox.minimum.clone();
					var maximum = m.getBoundingInfo().boundingBox.maximum.clone();
					const ratio = 0.8;
					var scaling = BABYLON.Matrix.Translation(-ratio, -ratio, -ratio);
					var scaling2 = BABYLON.Matrix.Translation(ratio, ratio, ratio);
					minimum = BABYLON.Vector3.TransformCoordinates(minimum, scaling);
					maximum = BABYLON.Vector3.TransformCoordinates(maximum, scaling2);
					m._boundingInfo = new BABYLON.BoundingInfo(minimum, maximum);
					m.computeWorldMatrix(true);
					//m.showBoundingBox = true;
					//add occlusion detection occluded are not rendered and status occluded is set
					m.occlusionQueryAlgorithmType = BABYLON.AbstractMesh.OCCLUSION_ALGORITHM_TYPE_ACCURATE;
					m.occlusionType = BABYLON.AbstractMesh.OCCLUSION_TYPE_STRICT;
				});
			}
		}
	}

	deleteIntersectingMeshes(meshes) {
		const sortedMeshes = this.sortMeshesByName(meshes);
		meshes = [];
		const disposedMeshes = [];
		sortedMeshes.forEach(m => {
			// on récupère le centre d'un object et on regarde s'il est "à l'intérieur" d'un autre objet
			const center = m.getBoundingInfo().boundingBox.centerWorld;
			if (disposedMeshes.indexOf(m.id) === -1) {
				// s'il n'a pas déjà été supprimé
				sortedMeshes.forEach((m2: Mesh) => {
					if (m != null && m2 !== null && m.id !== m2.id) {
						// ne pas comparé à lui même
						if (m2.intersectsPoint(center)) {
							// si intersection, on supprime le mesh et on stocke l'info pour filtrage ultérieur
							disposedMeshes.push(m2.id);
							m2.dispose();
						}
					}
				});
			}
		});
		return sortedMeshes.filter(m => disposedMeshes.indexOf(m.id) === -1);
	}

	mergeMeshes(meshes, id, info = null) {
		const newMesh = BABYLON.Mesh.MergeMeshes(meshes, true, false, null, true, true);
		newMesh.layerMask = 0x10000000;
		if (!info) {
			info = newMesh.getBoundingInfo().boundingBox.centerWorld;
		}
		newMesh.setPivotMatrix(BABYLON.Matrix.Translation(-info.x, -info.y, -info.z));
		this.camera.setTarget(info);
		newMesh.renderingGroupId = 1;
		newMesh.id = id;

		this.addToHoloCameras(newMesh);

		let i = 0;
		newMesh.subMeshes.forEach((submesh, index) => {
			(submesh as any).id = newMesh.id + "-" + i;
			(submesh as any).isPickable = true;
			i++;
		});

		return newMesh;
	}
	isEdge(mesh) {
		return (
			String(mesh.name).indexOf("e0") === 0 ||
			String(mesh.name).indexOf("e1") === 0 ||
			String(mesh.name).indexOf("e2") === 0 ||
			String(mesh.name).indexOf("e3") === 0 ||
			String(mesh.name).indexOf("e4") === 0 ||
			String(mesh.name).indexOf("e5") === 0
		);
	}
	isVertice(mesh) {
		return (
			String(mesh.name).indexOf("v0") === 0 ||
			String(mesh.name).indexOf("v1") === 0 ||
			String(mesh.name).indexOf("v2") === 0 ||
			String(mesh.name).indexOf("v3") === 0 ||
			String(mesh.name).indexOf("v4") === 0 ||
			String(mesh.name).indexOf("v5") === 0
		);
	}
	deleteObject(id) {
		const toDispose = (this.scene as any)?.meshes.filter(mesh => {
			return mesh.id === id || mesh.id === id + "-bis" || mesh.id === id + "-edges" || mesh.id === id + "-vertices";
		});

		if (toDispose) {
			toDispose.forEach(mesh => {
				mesh.dispose(true);
			});
		} else {
			console.error("no any mesh to remove");
		}
	}

	lockRotation(): Promise<void> {
		return new Promise(async (resolve, reject) => {
			// window.store.getters.cps.updateOnscreen(true);

			const cameraPositionUpdated =
				this.camera.radius !== this.initialCameraPosition.radius ||
				this.camera.alpha !== this.initialCameraPosition.alpha ||
				this.camera.beta !== this.initialCameraPosition.beta;

			this.camera.radius = this.initialCameraPosition.radius;
			this.camera.alpha = this.initialCameraPosition.alpha;
			this.camera.beta = this.initialCameraPosition.beta;

			if (this.holoCameras) {
				if (this.holoCameras.rtt_z) {
					this.holoCameras.rtt_z.activeCamera.alpha = -Math.PI / 2;
					this.holoCameras.rtt_z.activeCamera.beta = 1.396;
				}
				if (this.holoCameras.rtt_x && this.holoCameras.rtt_x.activeCamera) {
					this.holoCameras.rtt_x.activeCamera.alpha = Math.PI;
					this.holoCameras.rtt_x.activeCamera.beta = 1.396;
				}
				if (this.holoCameras.rttZ && this.holoCameras.rttZ.activeCamera) {
					this.holoCameras.rttZ.activeCamera.alpha = Math.PI / 2;
					this.holoCameras.rttZ.activeCamera.beta = 1.396;
				}
				if (this.holoCameras.rttX && this.holoCameras.rttX.activeCamera) {
					this.holoCameras.rttX.activeCamera.alpha = 0;
					this.holoCameras.rttX.activeCamera.beta = 1.396;
				}
			}

			(this.scene as any)?.CABRI.lockOrientation();
			if (cameraPositionUpdated) {
				window.store.getters.cps.setPageOrientation(this.camera.alpha, this.camera.beta);
				// keep for ios?
				// if (this.globalService.isIos && this.globalService.isTablet) {
				// this.updateCabriPosition();
				// }
			}

			if (this.holoCameras) {
				// console.error("HOLO CAMERAS");
				await this.timeOut(500);

				const bkgz = this.getLastMesh("bkgz");
				if (bkgz) {
					bkgz.rotation.y = 0;
				}
				const bkgZ = this.getLastMesh("bkgZ");
				if (bkgZ) {
					bkgZ.rotation.y = -Math.PI;
				}
				const bkgX = this.getLastMesh("bkgX");
				if (bkgX) {
					bkgX.rotation.y = -Math.PI / 2;
				}
				const bkgx = this.getLastMesh("bkgx");
				if (bkgx) {
					bkgx.rotation.y = Math.PI / 2;
				}
			} else {
				const background = this.getMesh("background");
				if (background) {
					background.rotation.x = -Math.PI / 2;
					background.rotation.y = 0;
				} else {
					console.error("no background");
				}
			}

			resolve();
		});
	}

	unlockRotation() {
		(this.scene as any).CABRI.unlockOrientation();
		this.camera.lowerAlphaLimit = null;
		this.camera.upperAlphaLimit = null;
		this.camera.lowerBetaLimit = null;
		this.camera.upperBetaLimit = null;
	}

	showEdges() {
		(this.scene as any).meshes.forEach(m => {
			m.enableEdgesRendering(0.95, true);
			m.edgesWidth = 4.0;
			m.edgesColor = new BABYLON.Color4(1, 1, 1, 1);
		});
	}

	getPatronMeshesPositions() {
		const result = [];

		// for (const k in this.cabriObjects) {
		// 	if (this.cabriObjects.hasOwnProperty(k)) {
		// 		const m = this.cabriObjects[k];
		// 		if (m.type === "facet") {
		// 			result.push({id: m.id, pts3D: m.params.pts3D});
		// 		}
		// 	}
		// }
		(this.scene as any).meshes.forEach(m => {
			if (m && m.id) {
				if (m.object && m.object.type && m.object.type === "facet") {
					result.push({ id: m.id, positions: m.getVerticesData(BABYLON.VertexBuffer.PositionKind) });
				}
				if (this.isEdge(m)) {
					// console.error(m);
				}
				if (this.isVertice(m)) {
					// console.error(m);
				}
			}
		});
		return result;
	}
	setPatronMeshesPositions(positions) {
		let counter = 0;
		// for (const k in this.cabriObjects) {
		// 	if (this.cabriObjects.hasOwnProperty(k)) {
		// 		const m = this.cabriObjects[k];
		// 		if (m.type === "facet") {
		// 			m.params.pts3D = positions[counter].pts3D;
		// 			counter++;
		// 		}
		// 	}
		// }

		(this.scene as any).meshes.forEach(m => {
			if (m && m.id) {
				if ((m.object && m.object.type && m.object.type === "facet") || this.isEdge(m) || this.isVertice(m)) {
					m.setVerticesData(BABYLON.VertexBuffer.PositionKind, positions[counter].positions);
					counter++;
				}
			}
		});
	}
	checkPatronOK(next = true) {
		if (!this.patronDone) {
			this.patronDone = true;
			const countCollisions = [];
			(this.scene as any).meshes.forEach(m => {
				if (m && m.name) {
					if (m.object && m.object.type && m.object.type === "facet") {
						(this.scene as any).meshes.forEach(m2 => {
							if (m2 && m2.name && m.id !== m2.id) {
								if (m2.object && m2.object.type && m2.object.type === "facet") {
									if (!countCollisions[m.id]) {
										countCollisions[m.id] = 0;
									}
									// let colide = false;
									for (let i = 0; i < 4; i++) {
										for (let j = 0; j < 4; j++) {
											if (
												this.check(m.object.params.pts3D[i].x, m2.object.params.pts3D[j].x) &&
												this.check(m.object.params.pts3D[i].y, m2.object.params.pts3D[j].y) &&
												this.check(m.object.params.pts3D[i].z, m2.object.params.pts3D[j].z)
											) {
												countCollisions[m.id]++;
											}
										}
									}
								}
							}
						});
					}
				}
			});
			let result = true;
			if (this.page.currentSolid === 1 || this.page.currentSolid === 2) {
				countCollisions.find(e => {
					if (e < 8 || e > 8) {
						result = false;
						this.patronDone = false;
						return false;
					}
				});
			}
			if (this.page.currentSolid === 5) {
				let count16 = 0;
				let count8 = 0;
				countCollisions.find(e => {
					if (e === 16) {
						count16++;
					}
					if (e === 8) {
						count8++;
					}
				});
				if (count16 !== 4 || count8 !== 1) {
					result = false;
					this.patronDone = false;
					return false;
				}
			}
			if (countCollisions.length > 0 && result) {
				this.patronDone = true;
				this.page.scenario.endPatron();
				this.page.patronsDone[this.page.currentSolid].push(this.page.currentPatron);
				this.loadMathia(next);
				// do not show starboard if in recognize pattern exercise remediation:
				if (this.cabriService.getCurrentExercice().variables.type !== ExerciseType.pattern) {
					setTimeout(async () => {
						this.page.updatePlayerAndTeamStarboards(AwardsType.shooting);
						await this.page.showHideAward();
					}, 2000);
				}
			} else {
				this.patronDone = false;
			}
		}
	}

	check(x, y) {
		const delta = 0.1;
		return x - delta < y && y < x + delta;
	}

	loadMathia(next = true) {
		super.loadMathia(() => {
			// end of animation => next patron
			if (next) {
				if (this.page.solidsExercices.remediationRunning) {
					this.page.remediationEndSubscription.next();
				} else {
					this.page.patron();
				}
			}
		});

		// this.lockRotation();
		// this.stopRender();
		// change facets texture
		const myMaterial = new BABYLON.StandardMaterial("grass", this.scene);
		myMaterial.diffuseTexture = new BABYLON.Texture(
			"assets/babylon/tex1" + (this.globalService.lowPerformanceMode ? "_ios" : "") + ".png",
			this.scene
		);
		(this.scene as any).meshes.forEach(m => {
			if (m && m.name) {
				if (m.object && m.object.type && m.object.type === "facet") {
					m.material = myMaterial;
					m.object.params.facetmaterial = myMaterial;
				}
			}
		});

		const hl1 = new BABYLON.HighlightLayer("hl1", this.scene);
		// change edges color
		(this.scene as any).meshes.forEach(m => {
			if (m && m.name && String(m.name).indexOf("e") === 0) {
				m.material.diffuseColor = new BABYLON.Color3(99 / 255, 80 / 255, 17 / 255);
				m.material.emissiveColor = new BABYLON.Color3(99 / 255, 80 / 255, 17 / 255);
				hl1.addMesh(m, BABYLON.Color3.Black(), true);
			}
		});
	}

	sortMeshesByName(meshes) {
		return meshes.sort((a, b) => {
			const nameA = a.name.toUpperCase(); // ignore upper and lowercase
			const nameB = b.name.toUpperCase(); // ignore upper and lowercase
			if (nameA < nameB) {
				return -1; // nameA comes first
			}
			if (nameA > nameB) {
				return 1; // nameB comes first
			}
			return 0; // names must be equal
		});
	}
	removeCabriMouseEvents() {
		if (!this.cabriEventsOff && window.store.getters.cabri.Scene) {
			this.saveEvents.onPointerDown = window.store.getters.cabri.Scene.CABRI.onPointerDown;
			this.saveEvents.onPointerUp = window.store.getters.cabri.Scene.CABRI.onPointerUp;
			this.saveEvents.onPointerMove = window.store.getters.cabri.Scene.CABRI.onPointerMove;

			window.store.getters.cabri.Scene.CABRI.onPointerDown = () => {};
			window.store.getters.cabri.Scene.CABRI.onPointerUp = () => {};
			window.store.getters.cabri.Scene.CABRI.onPointerMove = () => {};

			if (this.cabriService.mirrorMode && window.store.getters.cabri.Picker._pick3D) {
				window.store.getters.cabri.Picker.pick3D = window.store.getters.cabri.Picker._pick3D;
				window.store.getters.cabri.Picker._pick3D = null;
			}

			this.cabriEventsOff = true;
		}
	}
	restoreCabriMouseEvents() {
		if (this.cabriEventsOff && window.store.getters.cabri.Scene) {
			window.store.getters.cabri.Scene.CABRI.onPointerDown = this.saveEvents.onPointerDown;
			window.store.getters.cabri.Scene.CABRI.onPointerUp = this.saveEvents.onPointerUp;
			window.store.getters.cabri.Scene.CABRI.onPointerMove = this.saveEvents.onPointerMove;

			if (this.cabriService.mirrorMode) {
				window.store.getters.cabri.Picker._pick3D = window.store.getters.cabri.Picker.pick3D;
				window.store.getters.cabri.Picker.pick3D = (e, t, i) => {
					t.y = window.store.getters.cabri.Scene._engine._gl.canvas.clientHeight - t.y;
					return window.store.getters.cabri.Picker._pick3D(e, t, i);
				};
			}
			this.saveEvents.onPointerDown = null;
			this.saveEvents.onPointerUp = null;
			this.saveEvents.onPointerMove = null;
			this.cabriEventsOff = false;
		}
	}

	getPickedElement() {
		let element;
		const xPIxels = window.store.getters.cabri.Scene.pointerX;
		// (x + 10.5) / 21 * window.store.getters.cabri.Scene._engine._gl.canvas.clientWidth;
		const yPIxels = window.store.getters.cabri.Scene._engine._gl.canvas.clientHeight - window.store.getters.cabri.Scene.pointerY;
		// (y + (7.425-0.22)) / 14.85 * window.store.getters.cabri.Scene._engine._gl.canvas.clientHeight;

		console.error(xPIxels, yPIxels);
		const pickedResult = window.store.getters.cabri.Scene.pick(
			xPIxels,
			yPIxels,
			null,
			false,
			window.store.getters.cabri.Scene.CABRI.Camera2D3D
		);
		console.error("pickedElement", pickedResult);
		if (pickedResult.pickedMesh && pickedResult.pickedMesh.id !== "background") {
			element = { id: pickedResult.pickedMesh.id };
		}
		return element;
	}
	async textureArray(folder: string, size: number) {
		for (let i = 0; i < size; i++) {
			const textureMaterial = await this.returnGeneratedCode(
				this.grad1,
				"assets/babylon/textureNumbers/" + folder + "/" + (i + 1) + ".png"
			);
			this.setTexture(folder, i, 0, textureMaterial);
			const textureMaterial2 = await this.returnGeneratedCode(
				this.grad2,
				"assets/babylon/textureNumbers/" + folder + "/" + (i + 1) + ".png"
			);
			this.setTexture(folder, i, 1, textureMaterial2);

			const textureMaterial3 = await this.returnGeneratedCode(
				this.grad3,
				"assets/babylon/textureNumbers/" + folder + "/" + (i + 1) + ".png"
			);
			this.setTexture(folder, i, 2, textureMaterial3);
		}
	}
	setTexture(type, number, gradNumber, textureMaterial) {
		if (!this.numbersTextures[type]) {
			this.numbersTextures[type] = {};
		}
		if (!this.numbersTextures[type][number]) {
			this.numbersTextures[type][number] = {};
		}
		this.numbersTextures[type][number][gradNumber] = textureMaterial;
	}
	restoreCameraTarget() {
		const currentMesh = window.store.getters.cabri.Scene.meshes.find(mesh => {
			return mesh.gabarit && mesh.gabarit === "solides";
		});
		if (currentMesh) {
			const center = currentMesh.getBoundingInfo().boundingBox.centerWorld;
			this.camera.setTarget(center);
		}
	}
	setCameraDefaultPosition() {
		if (!this.camera) {
			this.camera = window.store.getters.cabri.Camera2D3D;
		}
		this.initialCameraPosition = { radius: 55, alpha: -Math.PI / 2, beta: (7 * Math.PI) / 18 };
		this.restoreCamera();
	}
}

results matching ""

    No results matching ""