You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
538 lines
14 KiB
538 lines
14 KiB
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; |
|
} |
|
|
|
} |
|
|
|
|
|
|