import { NgZone, ChangeDetectorRef } from "@angular/core";
import {
AbstractAssetTask,
AbstractMesh,
ArcRotateCamera,
AssetContainer,
AssetsManager,
ImageAssetTask,
MeshAssetTask,
MultiMaterial,
PhotoDome,
PointLight,
Texture,
TextureAssetTask,
Tools,
UniversalCamera,
Vector3,
WebXRAnchorSystem,
WebXRBackgroundRemover,
WebXRDefaultExperience,
WebXRDomOverlay,
WebXRFeatureName,
WebXRHitTest,
WebXRPlaneDetector,
HemisphericLight,
WebXRSessionManager,
ArcRotateCameraPointersInput,
Mesh,
InstancedMesh,
GizmoManager,
VideoDome,
HighlightLayer,
Color3,
MeshBuilder,
ContainerAssetTask,
Node,
TransformNode,
AnimationGroup,
BoundingInfo,
GlowLayer,
Matrix,
Frustum,
Observer,
Scene,
Color4,
DefaultRenderingPipeline,
PointerEventTypes,
RayHelper,
PointerInfo,
PointerTouch,
ArcRotateCameraMouseWheelInput
} from "@babylonjs/core";
import { CabriDataService } from "../services/cabri-data.service";
import { GlobalService } from "../services/global.service";
import { BabylonIntegration } from "./babylon-integration";
//not remove load for inspector
import("@babylonjs/core/Debug/debugLayer");
import("@babylonjs/inspector");
import "@babylonjs/loaders/glTF";
import { AppUtils } from "../app-utils";
import { IonSegment } from "@ionic/angular";
import { PhotoDomePage } from "../page/photo-dome/photo-dome.page";
import { PhotoDomeItem, PhotoDomeModel, PmMeshType, TriggerAnimation, TypePhotoDome } from "./photodome";
import {
AdvancedDynamicTexture,
Button,
Control,
Grid,
Image,
Rectangle,
ScrollViewer,
StackPanel,
TextBlock,
TextWrapping
} from "@babylonjs/gui";
import { ContentTagNames, TTSContent } from "./fiche";
import { ScenarioOse } from "./scenario-ose";
import { ScrollViewerCustom } from "../babylon/components/ScrollViewerCustom";
import { environment } from "src/environments/environment";
export class BabylonIntegrationPhotoDome extends BabylonIntegration {
camera: UniversalCamera | ArcRotateCamera;
dome: PhotoDome | VideoDome;
assetsManager: AssetsManager;
assetsContainer: AssetContainer;
textures: Map<string, Texture>;
images: Map<string, HTMLImageElement>;
meshs: Map<string, AbstractMesh>;
xr: WebXRDefaultExperience;
compostList = ["banana", "egg_", "apple", "corn"];
recycleList = ["paper", "box", "can_", "plastic", "holder"];
glassList = ["bottle_of"];
scrapList = ["metal","Pneu","Europalette"];
banList = ["cracked", "plastic_coctail_cup", "paper_list"];
compostMeshs: AbstractMesh[];
recycleMeshs: AbstractMesh[];
glassMeshs: AbstractMesh[];
scrapMeshs: AbstractMesh[];
poiMeshs: (AbstractMesh | TransformNode)[];
xrAnchor: WebXRAnchorSystem;
xrPlaneDetection: WebXRPlaneDetector;
xrBackgroundRemover: WebXRBackgroundRemover;
hitTest: WebXRHitTest;
domOverlayFeature: WebXRDomOverlay;
placedMesh: AbstractMesh[];
camera2: ArcRotateCamera;
photoDome: PhotoDomeModel;
placedMeshs: InstancedMesh[];
gizmoManager: GizmoManager;
poiPanel: AdvancedDynamicTexture;
lastCalled: number;
hl: HighlightLayer;
gl: GlowLayer;
hoverAnimObserver: Observer<Scene>;
pipeline: DefaultRenderingPipeline;
poi: number;
poiFound: number;
scenarioOse: ScenarioOse;
constructor(
public globalService: GlobalService,
public dataService: CabriDataService,
public ngZone: NgZone,
public page: PhotoDomePage,
public cd?: ChangeDetectorRef
) {
super(globalService, dataService, ngZone, cd);
this.placedMeshs = [];
}
/**
* Populate scene a
*/
async initScene() {
this.createCamera();
this.createLight();
this.createGyzmoManager();
this.createHighLayer();
//this.createGlowLayer();
this.addRenderingPipeline();
await this.importAsset();
this.assetsContainer.addAllToScene();
await this.activateAr();
if (this.page.currentPhotodome) {
await this.loadPhotodome(this.page.currentPhotodome);
} else {
await this.createPhotoDome();
}
this.debug();
}
async restartScene() {
this.placedMeshs.forEach(mesh => {
mesh.isVisible = false;
});
}
debug() {
this.meshs.get("floor").setEnabled(false);
}
showDebug() {
if (this.scene.debugLayer.isVisible()) {
this.scene.debugLayer.hide();
} else {
this.scene.debugLayer.show();
}
}
createGyzmoManager() {
this.gizmoManager = new GizmoManager(this.scene);
}
createHighLayer() {
this.hl = new HighlightLayer("hl1", this.scene, { camera: this.camera });
}
createGlowLayer() {
this.gl = new GlowLayer("glow", this.scene);
this.gl.intensity = 0.5;
}
addRenderingPipeline() {
this.pipeline = new DefaultRenderingPipeline("defaultPipeline", true, this.scene, [this.scene.activeCamera]);
if (!this.globalService.lowPerformanceMode) {
this.pipeline.fxaaEnabled = true;
this.pipeline.samples = 4;
}
this.pipeline.imageProcessingEnabled = false;
this.pipeline.glowLayerEnabled = true;
this.pipeline.glowLayer.intensity = 0.5;
}
highlightMesh(mesh: AbstractMesh, light: boolean) {
if (light && !this.hl.hasMesh(mesh)) {
this.hl.addMesh(mesh as Mesh, Color3.Green());
} else if (!light && this.hl.hasMesh(mesh)) {
this.hl.removeMesh(mesh as Mesh);
}
}
detectMeshInsideCenterCamera() {
let widthPercentage = 0.1;
let heightPercentage = 0.1;
const m = new Matrix();
m.copyFrom(this.camera.getProjectionMatrix());
let vectorX = m.getRow(0);
vectorX.x /= widthPercentage;
m.setRow(0, vectorX);
let vectorY = m.getRow(1);
vectorY.y /= heightPercentage;
m.setRow(0, vectorY);
const frustumPlanes = Frustum.GetPlanes(this.camera.getViewMatrix(true).multiply(m));
this.page.currentPhotodome.photoDomeItems.forEach(photoDomeItem => {
photoDomeItem.animations?.forEach(animation => {
if (animation.trigger === TriggerAnimation.hover) {
const node = this.scene.getTransformNodeByUniqueId(photoDomeItem.meshId);
let condition = false;
if (node instanceof Mesh) {
condition = node.isInFrustum(frustumPlanes);
} else if (node instanceof TransformNode) {
condition = node.getChildMeshes().some(currentValue => {
return currentValue.isInFrustum(frustumPlanes);
});
}
if (condition) {
const animGroup = node.metadata.animations.find(item => item.name === animation.name) as AnimationGroup;
if (!animGroup.isPlaying) {
animGroup.play();
}
}
}
});
});
}
pickedMesh(mesh: AbstractMesh) {
this.page.pickedMesh = mesh;
this.gizmoManager.attachToMesh(mesh);
this.gizmoManager.positionGizmoEnabled;
}
addPickEvent() {
//
// Gestion des clicks
//
this.scene.pointerDownFastCheck = true;
this.scene.pointerDownPredicate = mesh => {
return mesh instanceof InstancedMesh;
};
this.lastCalled = new Date().getTime();
this.scene.onPointerDown = (evt, pickResult, type) => {
const now = new Date().getTime();
if (now - this.lastCalled > 500 /* half a second */) {
this.lastCalled = now;
if (this.page.builder) {
if (this.page.canAddMesh && evt.button === 2) {
let clone = this.placeClone(this.page.selectedMesh);
this.page.currentPhotodome.photoDomeItems.push(this.page.createPhotoDomeItem(clone));
this.page.canAddMesh = false;
this.pickedMesh(clone);
//tag photodome modified
this.page.modified(this.page.currentPhotodome);
} else if (pickResult.pickedMesh instanceof InstancedMesh) {
if (this.placedMeshs.includes(pickResult.pickedMesh)) {
this.pickedMesh(pickResult.pickedMesh);
} else {
this.pickedMesh(this.getTopMesh(pickResult.pickedMesh) as any);
}
}
} else {
//pointerX pointerY on Scene already handle offsetting
pickResult = this.scene.pickWithBoundingInfo(this.scene.pointerX,this.scene.pointerY, mesh => {
return mesh instanceof InstancedMesh;
});
if (pickResult.pickedMesh) {
this.page?.audioService?.playSelectSound();
//var rayHelper = new RayHelper(pickResult.ray);
//rayHelper.show(this.scene);
let photodomeItem = this.page.currentPhotodome.photoDomeItems.find(
item =>
item.meshId === pickResult.pickedMesh.uniqueId ||
this.getTopMesh(pickResult.pickedMesh).uniqueId === item.meshId
);
if (photodomeItem) {
if (photodomeItem.type === PmMeshType.portal) {
const nextPhotoDome = this.page.photodomes.find(item => item.id == photodomeItem.nextPhotoDome);
if (nextPhotoDome) {
this.page.currentPhotodome = nextPhotoDome;
if (this.camera instanceof ArcRotateCamera) {
this.camera.alpha = this.page.cameraAlphaDefault;
this.camera.beta = this.page.cameraBetaDefault;
}
if (nextPhotoDome.type === TypePhotoDome.visit) {
this.loadPhotodome(nextPhotoDome);
} else {
this.page.router.navigate(["/waste-sorting"], { queryParams: { wasteSortingId: nextPhotoDome.id } });
}
}
}
if (photodomeItem.type === PmMeshType.poi) {
photodomeItem.clicked = true;
this.countClickedItemPhotodome();
if (photodomeItem.ficheId) {
this.pickedMesh(this.getTopMesh(pickResult.pickedMesh) as Mesh);
} else {
this.createPoiGui(photodomeItem);
}
}
}
}
}
}
};
/*this.scene.onPointerMove = (evt, pickResult) => {
if (!this.page.builder) {
if (pickResult.pickedMesh && pickResult.pickedMesh instanceof InstancedMesh) {
this.highlightMesh(pickResult.pickedMesh, true);
} else {
this.hl.removeAllMeshes();
}
}
};*/
}
getTopMesh(mesh: Node) {
while (mesh.parent !== null) {
mesh = mesh.parent;
}
return mesh;
}
createPoiGui(photodomeItem: PhotoDomeItem) {
if (!this.poiPanel) {
this.camera.detachControl();
this.removeMouseWheelEvent();
this.poiPanel = AdvancedDynamicTexture.CreateFullscreenUI("Poi");
this.poiPanel.layer.layerMask = 2;
this.poiPanel.layer.applyPostProcess = false;
const modalPanel = new Rectangle("modalPanel");
modalPanel.width = "80%";
modalPanel.height = "80%";
modalPanel.verticalAlignment = Control.VERTICAL_ALIGNMENT_CENTER;
modalPanel.horizontalAlignment = Control.HORIZONTAL_ALIGNMENT_CENTER;
modalPanel.background = "#FFFFFFFF";
modalPanel.color = "#82AB00FF";
modalPanel.thickness = 10;
modalPanel.cornerRadius = 50;
this.poiPanel.addControl(modalPanel);
const scrollPanel = new ScrollViewerCustom("content");
scrollPanel.thickness = 0;
scrollPanel.width = "100%";
scrollPanel.height = "90%";
scrollPanel.verticalAlignment = Control.VERTICAL_ALIGNMENT_CENTER;
scrollPanel.horizontalAlignment = Control.HORIZONTAL_ALIGNMENT_CENTER;
scrollPanel.barColor = "#FCB731FF";
scrollPanel.barSize = 10;
scrollPanel.barBackground = "#F5F5F5FF";
/*scrollPanel.onPointerEnterObservable.add(() => {
this.camera.detachControl();
});
scrollPanel.onPointerOutObservable.add(() => {
this.camera.attachControl(this.canvas, true);
this.cameraControl();
});*/
modalPanel.addControl(scrollPanel);
//this.poiPanel.focusedControl(buttonExit.);
const buttonExit = Button.CreateImageOnlyButton("Exit", "assets/icon/close_ose.svg");
let size = 80
if(!this.globalService.isDesktop){
size = this.globalService.isTablet ? Math.round(window.innerHeight / 10) : Math.round(window.innerHeight / 8)
}
buttonExit.heightInPixels = size;
buttonExit.widthInPixels = size;
buttonExit.verticalAlignment = Button.VERTICAL_ALIGNMENT_TOP;
buttonExit.horizontalAlignment = Button.HORIZONTAL_ALIGNMENT_RIGHT;
buttonExit.top = "6.5%";
buttonExit.left = "-8%";
buttonExit.thickness = 0;
this.poiPanel.addControl(buttonExit);
//modalPanel.addControl(buttonExit);
const titleTextBlock = new TextBlock("title", photodomeItem.title);
titleTextBlock.height = "10%";
titleTextBlock.width = "100%";
titleTextBlock.verticalAlignment = Button.VERTICAL_ALIGNMENT_TOP;
titleTextBlock.horizontalAlignment = Button.HORIZONTAL_ALIGNMENT_LEFT;
titleTextBlock.fontFamily = "Quicksand-Bold";
titleTextBlock.fontSize = "30px";
titleTextBlock.fontWeight = "bold";
titleTextBlock.top = '0px'
titleTextBlock.color = "rgb(54, 73, 17)";
scrollPanel.addControl(titleTextBlock);
if(innerHeight <= 800){
titleTextBlock.fontSize = "20px";
}else if(innerHeight <= 600){
titleTextBlock.fontSize = "16px";
}
const imgPoi = new Image("imagePoi", photodomeItem.image);
imgPoi.top = "15%";
imgPoi.height = "60%";
imgPoi.stretch = Image.STRETCH_UNIFORM;
imgPoi.verticalAlignment = Image.VERTICAL_ALIGNMENT_TOP;
imgPoi.horizontalAlignment = Image.HORIZONTAL_ALIGNMENT_LEFT;
scrollPanel.addControl(imgPoi);
const contentTextBlock = new TextBlock("content", photodomeItem.text);
contentTextBlock.top = "75%";
contentTextBlock.width = "100%";
contentTextBlock.paddingTop = "5%";
contentTextBlock.paddingLeft = "5%";
contentTextBlock.paddingRight = "5%";
contentTextBlock.paddingBottom = "5%";
contentTextBlock.resizeToFit = true;
contentTextBlock.textWrapping = TextWrapping.WordWrap;
contentTextBlock.verticalAlignment = TextBlock.VERTICAL_ALIGNMENT_TOP;
contentTextBlock.horizontalAlignment = TextBlock.HORIZONTAL_ALIGNMENT_LEFT;
contentTextBlock.fontFamily = "Quicksand-Bold";
contentTextBlock.fontSize = "24px";
if(innerHeight <= 800){
contentTextBlock.fontSize = "18px";
}else if(innerHeight <= 600){
contentTextBlock.fontSize = "16px";
}
contentTextBlock.fontWeight = "bold";
scrollPanel.addControl(contentTextBlock);
if (this.page) {
const ttsContent = new Array<TTSContent>();
ttsContent.push({ tag: ContentTagNames.H1, text: photodomeItem.title });
ttsContent.push({ tag: ContentTagNames.P, text: photodomeItem.text });
this.scenarioOse = new ScenarioOse(
this.page.accountService,
this.globalService,
null,
this.page,
this.cd,
this.page.ttsService
);
this.scenarioOse.readSpeechSequenceFromFicheContent(ttsContent);
}
this.lastCalled = new Date().getTime();
buttonExit.onPointerClickObservable.add((x, state) => {
const now = new Date().getTime();
if (now - this.lastCalled < 500 /* half a second */) {
// skip next calls
state.skipNextObservers = true;
} else {
this.lastCalled = now;
this.camera.attachControl(this.canvas, true);
this.cameraControl();
this.addMouseWheelPinchEvent();
if (this.scenarioOse) {
this.page.ttsService.killSpeech();
this.scenarioOse.skipMathiaSpeechSequence(true);
}
this.poiPanel.dispose();
this.poiPanel = null;
}
});
}
}
createCamera() {
//outside angular zone camera declaration attach control by defaut an so declare event listener
this.ngZone.runOutsideAngular(() => {
this.camera = new ArcRotateCamera("Camera", -Math.PI / 2, Math.PI / 2, 5, Vector3.Zero(), this.scene);
this.camera.setTarget(Vector3.Zero());
this.camera.attachControl(this.canvas, true);
this.camera2 = new ArcRotateCamera("camera-screenshot", -Math.PI / 2, Math.PI / 2, 1.55, Vector3.Zero(), this.scene);
this.camera2.layerMask = this.camera.layerMask;
this.camera2.minZ = 0;
this.camera2.maxZ = 50;
});
this.cameraControl();
}
fovAdjust(delta: number) {
if (delta > 0) {
if (this.camera.fov - 0.05 > 0.14) {
this.camera.fov -= 0.05;
}
} else {
if (this.camera.fov + 0.05 < 1) {
this.camera.fov += 0.05;
}
}
}
addMouseWheelPinchEvent() {
this.scene.onPointerObservable.add(pointerInfo => {
// prevent all default (native chrome pinch zoom)
pointerInfo.event.preventDefault();
switch (pointerInfo.type) {
case PointerEventTypes.POINTERWHEEL:
this.fovAdjust((pointerInfo.event as any).wheelDelta);
break;
}
});
if (this.camera instanceof ArcRotateCamera) {
// hack reduce delay for pinch zoom or pan choice useful when only one activated to remove the delay
this.camera.pinchToPanMaxDistance = 0;
const pointers = this.camera.inputs.attached.pointers as ArcRotateCameraPointersInput;
pointers.onMultiTouch = (
pointA: PointerTouch,
pointB: PointerTouch,
previousPinchSquaredDistance: number,
pinchSquaredDistance: number,
previousMultiTouchPanPosition: PointerTouch,
multiTouchPanPosition: PointerTouch
) => {
this.fovAdjust(pinchSquaredDistance - previousPinchSquaredDistance);
};
}
}
removeMouseWheelEvent() {
this.scene.onPointerObservable.clear();
if (this.camera instanceof ArcRotateCamera) {
const pointers = this.camera.inputs.attached.pointers as ArcRotateCameraPointersInput;
pointers.onMultiTouch = null;
}
}
placeClone(selectedMesh: Mesh | TransformNode): InstancedMesh {
var pick = this.scene.pick(this.scene.pointerX, this.scene.pointerY, (mesh) => { return mesh.isVisible; }, false, null, (p0, p1, p2, ray) => {
var p0p1 = p0.subtract(p1);
var p2p1 = p2.subtract(p1);
var normal = Vector3.Cross(p0p1, p2p1);
return Vector3.Dot(ray.direction, normal) < 0;
});
let clone = null;
if (selectedMesh instanceof Mesh) {
clone as InstancedMesh;
clone = this.cloneMeshWAnim(selectedMesh);
const range = clone.getBoundingInfo().boundingSphere.radius * clone.scaling.x;
const directionOffset = pick.ray.direction.multiplyByFloats(range, range, range);
clone.position = pick.pickedPoint.subtract(directionOffset);
clone.isVisible = true;
} else {
clone = this.cloneNodeTransformWAnim(selectedMesh);
const range = this.totalBoundingInfo(clone.getChildMeshes()).boundingSphere.radius * clone.scaling.x;
const directionOffset = pick.ray.direction.multiplyByFloats(range, range, range);
clone.position = pick.pickedPoint.subtract(directionOffset);
clone.setEnabled(true);
}
this.placedMeshs.push(clone);
this.assetsContainer.meshes.push(clone);
return clone;
}
cloneMeshWAnim(mesh: Mesh) {
if (mesh instanceof Mesh && !mesh.instancedBuffers) {
mesh.registerInstancedBuffer("color", 3);
mesh.instancedBuffers.color = new Color3(1, 1, 1);
}
const clone = mesh.createInstance(mesh.name + 1);
const animations: AnimationGroup[] = [];
if (mesh.metadata?.animations?.length > 0) {
const sourceAnims: AnimationGroup[] = mesh.metadata.animations;
sourceAnims.forEach(animSource => {
// when an animation is clone a callback can be use to convert the old target "Mesh" whith a new
//AnimationGroup -> animationTarget[] -> target "node" animation "curve coordinate"
animations.push(
animSource.clone(animSource.name, oldTarget => {
return clone;
})
);
});
clone.metadata = { animations };
}
return clone;
}
cloneNodeTransformWAnim(node: TransformNode) {
node.getChildMeshes().forEach(mesh => {
if (mesh instanceof Mesh && !mesh.instancedBuffers) {
mesh.registerInstancedBuffer("color", 3);
mesh.instancedBuffers.color = new Color3(1, 1, 1);
}
});
const animations: AnimationGroup[] = [];
// associative array to keep relation between source and instantiate item inside transformNode children
const sourceInstanceItems: { source; instance }[] = [];
const clonedNode = node.instantiateHierarchy(null, null, (source, instance) => {
sourceInstanceItems.push({ source, instance });
});
/*if (node.name.includes('POI')){
clonedNode.getChildMeshes().forEach(mesh => {
var minimum = mesh.getBoundingInfo().boundingBox.minimum.clone();
var maximum = mesh.getBoundingInfo().boundingBox.maximum.clone();
var scaling = Matrix.Scaling(2, 2, 2);
minimum = Vector3.TransformCoordinates(minimum, scaling);
maximum = Vector3.TransformCoordinates(maximum, scaling);
mesh.setBoundingInfo(new BoundingInfo(minimum, maximum));
});
}*/
if (node.metadata?.animations?.length > 0) {
const sourceAnims: AnimationGroup[] = node.metadata.animations;
sourceAnims.forEach(animSource => {
// when an animation is clone a callback can be use to convert the old target "Mesh" whith a new
//AnimationGroup -> animationTarget[] -> target "node" animation "curve coordinate"
animations.push(
animSource.clone(animSource.name, oldTarget => {
return sourceInstanceItems.find(item => {
return item.source === oldTarget;
}).instance;
})
);
});
clonedNode.metadata = { animations };
}
return clonedNode;
}
totalBoundingInfo(meshes) {
var boundingInfo = meshes[0].getBoundingInfo();
var min = boundingInfo.minimum.add(meshes[0].position);
var max = boundingInfo.maximum.add(meshes[0].position);
for (var i = 1; i < meshes.length; i++) {
boundingInfo = meshes[i].getBoundingInfo();
min = Vector3.Minimize(min, boundingInfo.minimum.add(meshes[i].position));
max = Vector3.Maximize(max, boundingInfo.maximum.add(meshes[i].position));
}
return new BoundingInfo(min, max);
}
createLight() {
this.light = new PointLight("bulb", new Vector3(0, 40, 0), this.scene);
this.light.intensity = 9000;
this.centerlight = new HemisphericLight("centerbulb", new Vector3(0, 0, 0), this.scene);
this.centerlight.intensity = 0.3;
}
createPhotoDome(): Promise<void> {
return new Promise((resolve, reject) => {
this.dome = new PhotoDome(
"testdome",
/*"assets/babylon/wasteSorting/greenhouse.jpg",*/
"assets/babylon/wasteSorting/nature.jpg",
{
resolution: 32,
size: 40
},
this.scene
);
this.dome.imageMode = PhotoDome.MODE_MONOSCOPIC;
this.dome.onLoadObservable.addOnce(() => {
resolve();
});
});
}
loadPhotodome(photoDome: PhotoDomeModel): Promise<void> {
return new Promise((resolve, reject) => {
if (!this.globalService.globalLoading) {
this.engine.displayLoadingUI();
}
if (this.dome) {
this.dome.dispose();
}
this.removeInstancedItemsFromScene();
this.photoDome = photoDome;
const videos = ["mp4", "3gp", "ogg", "webm"];
const url = new URL(photoDome.srcImg + "?env=" + (environment.production?"prod":"dev"));
const extension = url.pathname.split(".")[1];
if (videos.includes(extension)) {
this.dome = new VideoDome(
photoDome.name,
photoDome.srcImg,
{
resolution: 32,
size: 40,
faceForward: false,
},
this.scene
);
this.dome.videoMode = PhotoDome.MODE_MONOSCOPIC;
} else {
this.dome = new PhotoDome(
photoDome.name,
photoDome.srcImg,
{
resolution: 32,
size: 40,
faceForward: false,
},
this.scene
);
this.dome.imageMode = PhotoDome.MODE_MONOSCOPIC;
}
this.dome.getChildMeshes()[0].isPickable = false;
this.loadItemsScene(photoDome.photoDomeItems);
this.dome.onLoadObservable.addOnce(() => {
this.setCameraOrientation(photoDome);
this.engine.hideLoadingUI();
resolve();
});
});
}
setCameraOrientation(photoDome: PhotoDomeModel) {
if (photoDome.cameraAlpha && photoDome.cameraBeta) {
if (this.camera instanceof ArcRotateCamera) {
this.camera.alpha = photoDome.cameraAlpha;
this.camera.beta = photoDome.cameraBeta;
}
}
}
countClickedItemPhotodome() {
this.poiFound = this.photoDome.photoDomeItems.reduce((accumulator, item) => {
if (item.type === PmMeshType.poi && item.clicked) {
accumulator++;
}
return accumulator;
}, 0);
}
loadItemsScene(photoDomeItem: PhotoDomeItem[]) {
let haveHoverAnim = false;
this.poi = photoDomeItem.filter(item => item.type === PmMeshType.poi).length;
this.poiFound = 0;
photoDomeItem.forEach(item => {
if (item.clicked) {
this.poiFound++;
}
const node = this.scene.getNodeByName(item.originalMeshName);
if (item.type === PmMeshType.poi && item.ficheId){
this.dataService.getFiche(item.ficheId);
}
if (node) {
let clone = null;
if (node instanceof Mesh) {
clone = this.cloneMeshWAnim(node);
if (item.color) {
clone.instancedBuffers.color = Color3.FromHexString(item.color);
}
clone.isVisible = true;
//Todo clone animation for mesh
} else if (node instanceof TransformNode) {
clone = this.cloneNodeTransformWAnim(node);
if (item.color) {
clone.getChildMeshes().forEach(mesh => {
mesh.instancedBuffers.color = Color3.FromHexString(item.color);
});
}
}
if (item.animations?.length > 0) {
item.animations.forEach(element => {
if (element.trigger === TriggerAnimation.loop) {
const animGroup = clone.metadata.animations.find(item => item.name === element.name) as AnimationGroup;
animGroup.play(true);
} else {
haveHoverAnim = true;
}
});
}
clone.position.x = item.position.x;
clone.position.y = item.position.y;
clone.position.z = item.position.z;
clone.rotation.x = item.rotation.x;
clone.rotation.y = item.rotation.y;
clone.rotation.z = item.rotation.z;
clone.scaling.x = item.scale.x;
clone.scaling.y = item.scale.y;
clone.scaling.z = item.scale.z;
clone.isVisible = true;
item.meshId = clone.uniqueId;
this.placedMeshs.push(clone);
this.assetsContainer.meshes.push(clone);
}
});
if (haveHoverAnim && !this.hoverAnimObserver) {
this.hoverAnimObserver = this.scene.onBeforeRenderObservable.add(() => {
this.detectMeshInsideCenterCamera();
});
} else if (!haveHoverAnim && this.hoverAnimObserver) {
this.scene.onBeforeRenderObservable.remove(this.hoverAnimObserver);
}
}
removeInstancedItemsFromScene() {
this.placedMeshs.forEach(mesh => {
mesh.dispose();
});
this.placedMeshs = [];
}
async activateAr() {
await this.ngZone.runOutsideAngular(async () => {
try {
const supportedAr = await WebXRSessionManager.IsSessionSupportedAsync("immersive-ar");
const supportedVr = await WebXRSessionManager.IsSessionSupportedAsync("immersive-vr");
if (supportedAr) {
this.xr = await this.scene.createDefaultXRExperienceAsync({
uiOptions: {
sessionMode: "immersive-ar"
},
optionalFeatures: true
});
} else if (supportedVr) {
this.xr = await this.scene.createDefaultXRExperienceAsync({
uiOptions: {
sessionMode: "immersive-vr"
},
optionalFeatures: true
});
}
//this.xr?.pointerSelection.attach();
} catch (error) {
console.error(error);
}
/*try {
//this.xrAnchor = this.xr?.baseExperience.featuresManager.enableFeature(WebXRAnchorSystem, 'latest') as WebXRAnchorSystem;
} catch (error) {
console.error(error);
}*/
/*try {
this.xrPlaneDetection = this.xr?.baseExperience.featuresManager.enableFeature(WebXRPlaneDetector, 1) as WebXRPlaneDetector;
} catch (error) {
console.error(error);
}
try {
this.xrBackgroundRemover = this.xr?.baseExperience.featuresManager.enableFeature(WebXRBackgroundRemover, "latest", {
environmentHelperRemovalFlags: {
skyBox: true,
ground: true
}
}) as WebXRBackgroundRemover;
} catch (error) {
// alert(error);
console.error(error);
}*/
try {
this.domOverlayFeature = this.xr?.baseExperience.featuresManager.enableFeature(WebXRFeatureName.DOM_OVERLAY, 1, {
element: ".overlayAr" // domOverlayRef.current
}) as WebXRDomOverlay;
} catch (error) {}
});
}
/********************************************* IMPORT ************************************************/
importAsset(): Promise<void> {
this.assetsManager = new AssetsManager(this.scene);
this.assetsContainer = new AssetContainer(this.scene);
this.importContainer();
this.importTexture();
this.importImage();
this.importMesh();
this.assetsManager.onFinish = this.importFinish.bind(this);
if (this.page.currentPhotodome) {
this.assetsManager.autoHideLoadingUI = false;
}
//this.assetsManager.useDefaultLoadingScreen = false;
return this.assetsManager.loadAsync();
}
importMesh() {
const mMeshs = [
{ name: "waste", url: "/assets/babylon/wasteSorting/scenes/", filename: "Demo0.gltf" },
{ name: "waste", url: "/assets/babylon/wasteSorting/scenes/", filename: "Pneu.gltf" },
{ name: "pallet", url: "/assets/babylon/wasteSorting/scenes/", filename: "Europalette.babylon" },
//{ name: "poi", url: "/assets/babylon/visit/scenes/", filename: "PoiPortal.babylon" }
];
mMeshs.forEach(mesh => {
this.assetsManager.addMeshTask(mesh.name, null, mesh.url, mesh.filename);
});
}
importContainer() {
const mMeshs = [
{ name: "poi", url: "/assets/babylon/poi/", filename: "poi.babylon" }
//{ name: "poi", url: "/assets/babylon/animals/", filename: "animals.babylon" }
];
mMeshs.forEach(mesh => {
this.assetsManager.addContainerTask(mesh.name, null, mesh.url, mesh.filename);
});
}
importImage() {
const images = [
/*{
name: "dome",
url: "assets/babylon/wasteSorting/PANO_20230111_095151_1.jpg"
}*/
];
images.forEach(image => {
this.assetsManager.addImageTask(image.name, image.url);
});
}
importTexture() {
const textureMap = [
/*
{ name: "parking", url: "assets/babylon/justePoint/textures/parking.png" },
*/
];
textureMap.forEach(texture => {
this.assetsManager.addTextureTask(texture.name, texture.url);
});
}
importFinish(tasks: AbstractAssetTask[]) {
this.textures = new Map<string, Texture>();
this.images = new Map<string, HTMLImageElement>();
this.meshs = new Map<string, AbstractMesh>();
this.recycleMeshs = [];
this.compostMeshs = [];
this.scrapMeshs = [];
this.glassMeshs = [];
this.poiMeshs = [];
tasks.forEach(task => {
if (task.isCompleted) {
//containerAssetTask import transformNode mesh animation and material/texture
if (task instanceof ContainerAssetTask) {
let nodeMeshs: AbstractMesh[] = [];
console.log(task.loadedMeshes);
this.assetsContainer.animationGroups = this.assetsContainer.animationGroups.concat(task.loadedAnimationGroups);
this.assetsContainer.transformNodes = this.assetsContainer.transformNodes.concat(task.loadedTransformNodes);
//search anim where target use a child mesh for each node
task.loadedTransformNodes.forEach(node => {
node.setEnabled(false);
node.rotation = new Vector3(0, 0, 0);
let animBind: AnimationGroup[] = [];
task.loadedAnimationGroups.forEach(animGroup => {
for (let index = 0; index < animGroup.targetedAnimations.length; index++) {
const target = animGroup.targetedAnimations[index].target;
if (
node.getChildren(nodeChild => {
return nodeChild.id === target.id;
}).length > 0
) {
animBind.push(animGroup);
}
}
});
if (animBind.length > 0) {
node.metadata = { animations: animBind };
}
//list meshs from transformNode to detect orphan mesh when we handle meshs
nodeMeshs = nodeMeshs.concat(node.getChildMeshes());
if (task.name === "poi") {
this.poiMeshs.push(node);
}
});
this.assetsContainer.meshes = this.assetsContainer.meshes.concat(task.loadedMeshes);
task.loadedMeshes.forEach(mesh => {
//orphan mesh
if (!nodeMeshs.includes(mesh)) {
mesh.rotation = new Vector3(0, 0, 0);
let animBind: AnimationGroup[] = [];
//search anim for this mesh
task.loadedAnimationGroups.forEach(animGroup => {
for (let index = 0; index < animGroup.targetedAnimations.length; index++) {
const target = animGroup.targetedAnimations[index].target;
if (target === mesh) {
animBind.push(animGroup);
}
}
if (animBind.length > 0) {
mesh.metadata = { animations: animBind };
}
});
mesh.isVisible = false;
if (task.name === "poi") {
this.poiMeshs.push(mesh);
}
}
if (mesh.material) {
if (mesh.material instanceof MultiMaterial) {
this.assetsContainer.multiMaterials.push(mesh.material as MultiMaterial);
} else {
this.assetsContainer.materials.push(mesh.material);
this.assetsContainer.textures = this.assetsContainer.textures.concat(mesh.material.getActiveTextures());
}
}
});
}
if (task instanceof MeshAssetTask) {
const lTask = task as MeshAssetTask;
this.assetsContainer.meshes = this.assetsContainer.meshes.concat(lTask.loadedMeshes);
lTask.loadedMeshes.forEach(mesh => {
const lTask = task as MeshAssetTask;
if (lTask.name === "poi") {
console.log(lTask.loadedMeshes);
this.poiMeshs.push(mesh);
}
else if (lTask.name === "pallet") {
mesh.scaling = new Vector3(0.1, 0.1, 0.1);
}
else {
mesh.scaling = new Vector3(10, 10, 10);
}
mesh.rotation = new Vector3(0, 0, 0);
this.meshs.set(mesh.name, mesh);
mesh.isVisible = false;
if (
!this.banList.find(elem => {
return mesh.name.includes(elem);
})
) {
if (
this.recycleList.find(elem => {
return mesh.name.includes(elem);
})
) {
this.addCustomBehavior(mesh, "recycle");
this.recycleMeshs.push(mesh);
}
if (
this.glassList.find(elem => {
return mesh.name.includes(elem);
})
) {
this.addCustomBehavior(mesh, "glass");
this.glassMeshs.push(mesh);
}
if (
this.compostList.find(elem => {
return mesh.name.includes(elem);
})
) {
this.addCustomBehavior(mesh, "compost");
this.compostMeshs.push(mesh);
}
if (
this.scrapList.find(elem => {
return mesh.name.includes(elem);
})
) {
this.addCustomBehavior(mesh, "scrap");
this.scrapMeshs.push(mesh);
}
}
if (mesh.material) {
if (mesh.material instanceof MultiMaterial) {
this.assetsContainer.multiMaterials.push(mesh.material as MultiMaterial);
} else {
this.assetsContainer.materials.push(mesh.material);
this.assetsContainer.textures = this.assetsContainer.textures.concat(mesh.material.getActiveTextures());
}
}
});
} else if (task instanceof ImageAssetTask) {
const lTask = task as ImageAssetTask;
this.images.set(task.name, lTask.image);
} else if (task instanceof TextureAssetTask) {
const lTask = task as TextureAssetTask;
this.assetsContainer.textures.push(lTask.texture);
this.textures.set(lTask.name, lTask.texture);
} else {
console.error("Task Not Handle", task);
}
} else {
console.error(task.errorObject);
}
});
}
addCustomBehavior(mesh: AbstractMesh, type: string) {
const customBehavior = { name: "custom", type, init: () => {}, attach: () => {}, detach: () => {} };
mesh.addBehavior(customBehavior);
// console.error(mesh.name+" : "+(mesh.getBehaviorByName("custom") as any).displayName);
}
/******************************************END IMPORT ************************************************/
}