import { AbstractMesh, Animation, AnimationGroup, ArcRotateCamera, Camera, EasingFunction, IParticleSystem, ISceneLoaderPlugin, ISceneLoaderPluginAsync, ISceneLoaderProgressEvent, Mesh, MeshBuilder, PBRMaterial, QuadraticEase, Quaternion, Scene, SceneLoader, Skeleton, Vector3, } from '@babylonjs/core'; import { AdvancedDynamicTexture, Button, Container, Control, InputText, Rectangle, ScrollViewer, StackPanel, TextBlock, } from '@babylonjs/gui'; import { ObjectsService } from 'src/app/service/objects.service'; import { ConfigManager } from '../controller/config-manager'; import { ModeManager } from '../controller/mode-manager'; import { UIManager } from '../controller/ui-manager'; import { UIBase } from '../view/window-base/ui-base'; import { BabylonUIStyleTool } from './babylon-ui-style-tool'; import { LoadTool } from './load-tool'; import { TsTool } from './ts-tool'; export class BabylonTool { static readonly c_radian1 = 180 / Math.PI; //1弧度的度数 static readonly c_cloneSuffix = '(Clone)'; //克隆物体的名称后缀 static readonly alpha_N = Math.PI * 0.5;//面朝北时的alpha值 右手坐标系是pi,左手是-pi //获取世界坐标 static getWorldPosition(mesh: AbstractMesh): Vector3 { let result = mesh.position; if (mesh.parent != null) { result = mesh.absolutePosition; } return result.clone(); } //载入模型 static importMesh( meshNames: any, rootUrl: string, sceneFilename?: string | File, scene?: Scene, onSuccess?: ( meshes: AbstractMesh[], particleSystems: IParticleSystem[], skeletons: Skeleton[], animationGroups: AnimationGroup[] ) => void, onProgress?: (event: ISceneLoaderProgressEvent) => void, onError?: (scene: Scene, message: string, exception?: any) => void, pluginExtension?: string ): ISceneLoaderPlugin | ISceneLoaderPluginAsync { let path = rootUrl; //"institutions/institution001/building001/outdoor/"; if (path == null) { return null; } if (path.indexOf(ConfigManager.c_resPath_assetsRoot) == -1) { path = ConfigManager.c_resPath_assetsRoot + rootUrl; } ModeManager.log("加载模型" + path + sceneFilename); return SceneLoader.ImportMesh( meshNames, path, sceneFilename, scene, onSuccess, onProgress, onError, pluginExtension ); } /** * 异步加载模型 * @param meshNames * @param rootUrl * @param sceneFilename * @param scene * @param tag 加载原因 * @param onSuccess * @param onProgress * @param onError * @param pluginExtension */ static importMeshSync(meshNames: any, rootUrl: string, sceneFilename?: string | File, scene?: Scene, tag?: string, onSuccess?: ( meshes: AbstractMesh[], particleSystems: IParticleSystem[], skeletons: Skeleton[], animationGroups: AnimationGroup[] ) => void, onProgress?: (event: ISceneLoaderProgressEvent) => void, onError?: (scene: Scene, message: string, exception?: any) => void, pluginExtension?: string) { let path = rootUrl; //"institutions/institution001/building001/outdoor/"; if (path == null) { return null; } if (path.indexOf(ConfigManager.c_resPath_assetsRoot) == -1) { //本地资源 path = ConfigManager.c_resPath_assetsRoot + rootUrl; } else { path = ObjectsService.baseUrl + path; //根据环境,动态改变桶名 } console.log("异步加载模型" + path + sceneFilename); let modelPath = path + sceneFilename; LoadTool.add(modelPath, tag); SceneLoader.ImportMeshAsync( meshNames, path, sceneFilename, scene, onProgress, ).then(function (result) { LoadTool.remove(modelPath); onSuccess(result.meshes, result.particleSystems, result.skeletons, result.animationGroups); }).catch(function (result) { onError(scene, "load error", result); }); } /** * 设置材质为无光材质 */ static setMatToUnlit(meshes: AbstractMesh[], isUnlit: boolean, mask: string = null) { if (meshes == null) { return; } for (let i = 0; i < meshes.length; i++) { if (meshes[i].material instanceof PBRMaterial) { let mat_pbr = (meshes[i].material as PBRMaterial); // mat_pbr.metallicF0Factor = 0;//关掉反光 if (mask == null || !TsTool.stringContain(mat_pbr.name, mask)) { mat_pbr.unlit = isUnlit; } } } } /** * 设置材质从菲涅尔反射(反光效果) * @param meshes * @param enable //是否开启 * @param linkToAlbedo //是否反射环境光颜色 */ static setMatSheen(meshes: AbstractMesh[], enable: boolean, linkToAlbedo: boolean) { for (let i = 0; i < meshes.length; i++) { if (meshes[i].material instanceof PBRMaterial) { let mat_pbr = (meshes[i].material as PBRMaterial); if (TsTool.stringContain(mat_pbr.name, "Color")) { mat_pbr.sheen.isEnabled = enable; mat_pbr.sheen.linkSheenWithAlbedo = linkToAlbedo; } } } } //改变摄像机观察目标 static changeCameraTarget(camera: ArcRotateCamera, mesh: AbstractMesh, animMove: boolean = true, size = null) { if (mesh == null) { return; } let maxScaleAxis = size; if (size == null) { maxScaleAxis = mesh.scaling.x; } camera._scene.stopAnimation(camera); maxScaleAxis = Math.max(maxScaleAxis, mesh.scaling.y); maxScaleAxis = Math.max(maxScaleAxis, mesh.scaling.z); maxScaleAxis *= 1.5; if (BabylonTool.cameraModeIs2D) { camera.target = BabylonTool.getWorldPosition(mesh); camera.radius = maxScaleAxis + 5; camera.alpha = BabylonTool.alpha_N; camera.beta = 0; } else { if (animMove) { BabylonTool.AnimMoveCameraTarget( camera, 60, BabylonTool.getWorldPosition(mesh), maxScaleAxis + 5 ); } else { let alpha = BabylonTool.alpha_N; camera.target = BabylonTool.getWorldPosition(mesh).clone(); camera.radius = maxScaleAxis + 50; camera.alpha = alpha; camera.beta = 1; BabylonTool.AnimMoveCameraTarget( camera, 60, BabylonTool.getWorldPosition(mesh).clone(), maxScaleAxis + 5 ); } } } /** * 停止动画 * @param camera */ static stopAnim(camera: ArcRotateCamera) { camera._scene.stopAnimation(camera); } /** * 摄像机状态- 是2d正交状态 */ static cameraModeIs2D = false; /** * 改变摄像机模式 * @param camera * @param is2D * @param mesh */ static changeCameraMode(camera: ArcRotateCamera, is2D: boolean) { camera.mode = is2D ? Camera.ORTHOGRAPHIC_CAMERA : Camera.PERSPECTIVE_CAMERA; BabylonTool.cameraModeIs2D = is2D; // BabylonTool.AnimMoveCameraTarget( // camera, // 60, // camera.target, // camera.radius // ); if (camera._scene.useRightHandedSystem) { } camera.alpha = BabylonTool.alpha_N; camera.beta = 0; } //动画移动摄像机target public static AnimMoveCameraTarget( camera: ArcRotateCamera, allFrame: number, target: Vector3, radius: number, ) { //缓动动画 let easingFunction = new QuadraticEase(); easingFunction.setEasingMode(EasingFunction.EASINGMODE_EASEOUT); //target let anim_target = new Animation( 'CameraAnim_target', 'target', 60, Animation.ANIMATIONTYPE_VECTOR3, Animation.ANIMATIONLOOPMODE_CYCLE ); let keys_target = [ { frame: 0, value: camera.target }, { frame: allFrame, value: target }, ]; anim_target.setKeys(keys_target); anim_target.setEasingFunction(easingFunction); //radius let anim_radius = new Animation( 'CameraAnim_radius', 'radius', 60, Animation.ANIMATIONTYPE_FLOAT, Animation.ANIMATIONLOOPMODE_CYCLE ); let keys_radius = [ { frame: 0, value: camera.radius }, { frame: allFrame, value: radius }, ]; anim_radius.setKeys(keys_radius); anim_radius.setEasingFunction(easingFunction); camera.animations = []; camera.animations.push(anim_target); camera.animations.push(anim_radius); camera._scene.beginAnimation(camera, 0, allFrame, false); // camera.target = target; // SceneManager.scene.beginAnimation(camera, 0, allFrame, false); } //克隆mesh static cloneMesh(prefab: Mesh, name?: string, onSuccess?: (childMesh: AbstractMesh[], root: AbstractMesh) => void): Mesh { if (name == null || name == undefined) { name = prefab.name + BabylonTool.c_cloneSuffix; } let prefabActive = prefab.isEnabled(false); if (prefabActive == false) { prefab.setEnabled(true); } let result = prefab.clone(name); let childMesh: AbstractMesh[] = []; //把名字中,因为复制而产生的无用前缀删掉 let allChildren = result.getChildren((node) => { let names = node.name.split('.'); let newName = names[names.length - 1]; node.name = newName; if (node instanceof AbstractMesh) { childMesh.push(node); } return true; }, false); if (prefabActive == false) { prefab.setEnabled(false); } if (onSuccess) { onSuccess(childMesh, result); } return result; } } //输入提示界面 export class InputHintWindow extends UIBase { btn_hide: Button; //隐藏按钮 isShow: boolean = true;//是否是显示状态 txt_first: TextBlock;//第一行文字 onInit() { super.onInit(); let window = this; let advancedTexture = UIManager.Instance.uiRoot; let inputInfoBg = new Rectangle('InputHintWindow'); inputInfoBg.horizontalAlignment = Control.HORIZONTAL_ALIGNMENT_RIGHT; inputInfoBg.verticalAlignment = Control.VERTICAL_ALIGNMENT_TOP; advancedTexture.addControl(inputInfoBg); this.root = inputInfoBg; BabylonUIStyleTool.setDefaultStyle_windowRoot(this.root, false); BabylonUIStyleTool.setStyle_size(this.root, "160px", "100px"); this.root.alpha = 0.8; let inputInfoRoot = new StackPanel('inputInfoRoot'); inputInfoRoot.horizontalAlignment = Control.HORIZONTAL_ALIGNMENT_RIGHT; inputInfoRoot.verticalAlignment = Control.VERTICAL_ALIGNMENT_TOP; inputInfoRoot.width = '150px'; inputInfoRoot.height = '100px'; inputInfoRoot.isVertical = true; inputInfoBg.addControl(inputInfoRoot); let infoStyle = advancedTexture.createStyle(); infoStyle.fontSize = 15; let mouseLeft = new TextBlock('mouseLeft', '左键拖动 => 旋转镜头'); mouseLeft.color = 'white'; mouseLeft.height = '30px'; mouseLeft.textHorizontalAlignment = Control.HORIZONTAL_ALIGNMENT_LEFT; mouseLeft.style = infoStyle; inputInfoRoot.addControl(mouseLeft); this.txt_first = mouseLeft; let mouseRight = new TextBlock('mouseRight', '右键拖动 => 平移镜头'); mouseRight.color = 'white'; mouseRight.height = '30px'; mouseRight.textHorizontalAlignment = Control.HORIZONTAL_ALIGNMENT_LEFT; mouseRight.style = infoStyle; inputInfoRoot.addControl(mouseRight); let mouseCenter = new TextBlock('mouseCenter', '前滑滚轮 => 拉近镜头'); mouseCenter.color = 'white'; mouseCenter.height = '30px'; mouseCenter.textHorizontalAlignment = Control.HORIZONTAL_ALIGNMENT_LEFT; mouseCenter.style = infoStyle; inputInfoRoot.addControl(mouseCenter); this.btn_hide = Button.CreateSimpleButton("btn_hide", "..."); this.root.addControl(this.btn_hide); this.btn_hide.textBlock.horizontalAlignment = Control.HORIZONTAL_ALIGNMENT_RIGHT; this.btn_hide.textBlock.verticalAlignment = Control.VERTICAL_ALIGNMENT_TOP; this.btn_hide.textBlock.color = "white"; this.btn_hide.thickness = 0; this.btn_hide.onPointerClickObservable.add(() => { console.log("隐藏"); window.setShowStatus(!window.isShow); }); this.setShowStatus(true); // setTimeout(() => { // if (window.isShow) { // window.setShowStatus(false); // } // }, 4000); } //设置显示状态 setShowStatus(newStatus: boolean) { this.isShow = newStatus; if (newStatus) { this.root.width = "160px"; this.root.height = "100px"; this.root.alpha = 0.8; this.txt_first.alpha = 1; this.btn_hide.textBlock.isVisible = false; } else { this.root.width = "20px"; this.root.height = "20px"; this.root.alpha = 0.5; this.txt_first.alpha = 0; this.btn_hide.textBlock.isVisible = true; } } } //封装的滚动条组件 export class MyScrollView { name: string; scrollView: ScrollViewer; container: StackPanel; constructor(name: string, parent: Rectangle, isVertical: boolean = true) { this.name = name; this.scrollView = new ScrollViewer(name); parent.addControl(this.scrollView); this.scrollView.paddingTop = "0px"; this.scrollView.paddingRight = "0px"; this.scrollView.paddingBottom = "0px"; this.scrollView.paddingLeft = "0px"; this.container = new StackPanel("Container"); this.scrollView.addControl(this.container); this.container.isVertical = isVertical; this.container.paddingTop = "0px"; this.container.paddingBottom = "0px"; this.container.paddingLeft = "0px"; this.container.paddingRight = "0px"; } } //带背景的输入框 export class MyInputText { name: string; bg: Rectangle; inputText: InputText; constructor(name: string, parent: Container, width: string, height: string) { this.name = name; this.bg = new Rectangle(name); parent.addControl(this.bg); this.bg.width = width; this.bg.height = height; this.inputText = new InputText(name + "_txt"); this.bg.addControl(this.inputText); this.inputText.width = width; this.inputText.height = height; } }