babylon.js比 three.js要简单很多
加载 electron 离屏窗口渲染到场景中。加载mmd模型文件pmx ,加载动作文件vmd
- 模型地址: https://www.aplaybox.com/
- [角色-三月七] https://www.aplaybox.com/details/model/3Z9TvNRzUCT2
- mmd - MikuMikuDance
/**
* @file babylon.js
* @warning @babylonjs/havok 物理引擎有wasm文件,添加到vite配置文件 optimizeDeps 中排除
* @file 模型地址: https://www.aplaybox.com/
* @file [角色-三月七] https://www.aplaybox.com/details/model/3Z9TvNRzUCT2
* @file [摇摆动作] https://www.aplaybox.com/details/motion/MROM14tKgFlB
*
* @author 一切时空过去未来
*
* @description 加载mmd模型, .pmx .vmd 文件, 某些需要使用blender调整模型
* @file blender地址: https://www.blender.org/download/
* @file blender插件blender_mmd_tools: https://github.com/UuuNyaa/blender_mmd_tools/releases
* @file 纯Blender流程制作MMD 3D动画】 https://www.bilibili.com/video/BV1Pz4y1h7Aq/?share_source=copy_web&vd_source=25362a4390fdd7435668ff21e468a64a
*/
import HavokPhysics from'@babylonjs/havok' //物理引擎有wasm文件,添加到vite配置文件 optimizeDeps 中排除
import _ from 'lodash-es'
export{ 启动babylon }
/**
* @function 启动babylon
* @param {Object} 传入canvas节点
* @returns
* @description 由于 babylonjs 包太大, 在 vite 里用 manualChunks 单独打包
*/
async function 启动babylon(canvas节点){
if(!canvas节点 || canvas节点.存在动画) return
canvas节点.存在动画 = true
const __静态资源 = window.__静态资源 //vite中配置资源 publicDir: '🏜️静态资源'
const 模型路径 = `${__静态资源}3D模型/`
const mmd模型路径 = `${__静态资源}mmd模型/`
const 全景天空={
天空1:`${__静态资源}图片/全景/1.jpg`,
天空2:`${__静态资源}图片/全景/2.jpg`,
天空3:`${__静态资源}图片/全景/3.jpg`,
}
/** @type {import('@babylonjs/core/Legacy/legacy')} */
const { useIntersectionObserver , useResizeObserver } = await import('@vueuse/core')
const BABYLON = await import('@babylonjs/core/Legacy/legacy')
const GUI = await import("@babylonjs/gui")
const Loaders = await import('@babylonjs/loaders')
const MMD = await import('babylon-mmd')
// const Inspector = await import( '@babylonjs/inspector')
// import * as cannon from "cannon";
// import { GrassProceduralTexture } from "@babylonjs/procedural-textures"
// import { WebXRLayers } from "@babylonjs/core/XR/features/WebXRLayers";
// import { WebXRDomOverlay } from "@babylonjs/core/XR/features/WebXRDOMOverlay";
const { port1:本地渲染port, port2:远程渲染port } = new MessageChannel()
const { port1:本地操作port, port2:远程操作port } = new MessageChannel()
window.postMessage("创建离屏窗口","*",[ 远程渲染port,远程操作port ] )
let scene,engine, camera, webXR, inputMap = {} ;
let vmdLoader, mmdRuntime
//----------- 启动 -------------------
async function init() {
// 建立基础场景
engine = await BABYLON.EngineFactory.CreateAsync(canvas节点);
scene = new BABYLON.Scene(engine);
canvas节点.存在动画 = scene
// scene.autoClear = false; // Color buffer
// scene.autoClearDepthAndStencil = false; // Depth and stencil, obviously
const havokInstance = await HavokPhysics();
const havok插件 = new BABYLON.HavokPlugin(true, havokInstance);
scene.enablePhysics(new BABYLON.Vector3(0, -9.81 * 10 , 0), havok插件);
vmdLoader = new MMD.VmdLoader(scene);
mmdRuntime = new MMD.MmdRuntime(scene , new MMD.MmdPhysics(scene));
mmdRuntime.register(scene);
// mmdRuntime.playAnimation();
// mmdRuntime.pauseAnimation();
// mmdRuntime.seekAnimation(0)
// camera = new BABYLON.ArcRotateCamera('自由摄像机', new BABYLON.Vector3(0, 6, 20) , scene);
camera = new BABYLON.ArcRotateCamera("camera1", Math.PI / 2, Math.PI / 2.5, 10, new BABYLON.Vector3(0, 5, 0), scene);
scene.activeCamera = camera;
scene.activeCamera.attachControl(canvas节点, true);
camera.upperBetaLimit = Math.PI / 1.5;
camera.lowerBetaLimit = Math.PI / 5;
camera.lowerRadiusLimit = 2;
camera.upperRadiusLimit = 30;
camera.wheelPrecision = 0.001;
camera.wheelDeltaPercentage = 0.01;
camera.targetScreenOffset = new BABYLON.Vector2(-2, -7)
camera.position = new BABYLON.Vector3(0, 0, 20)
// camera.lowerRadiusLimit = 2;
// camera.upperRadiusLimit = 10;
// camera.wheelDeltaPercentage = 0.01;
// camera.attachControl(true);
// camera.setTarget(new BABYLON.Vector3(0, 7, 0));
// camera.keysUp.push(87); //WASD移动
// camera.keysDown.push(83);
// camera.keysLeft.push(65);
// camera.keysRight.push(68);
// camera.speed= 2;
// camera.inertia=0.3; //惯性
// camera.minZ=0.5; //屏蔽物体的距离 防止物体遮挡
// camera.checkCollisions = true;
// camera.applyGravity = true;
// camera.ellipsoid = new BABYLON.Vector3(1, 4, 1);
// scene.onBeforeCameraRenderObservable.add(()=> { if(camera.position.y < -20) camera.position = new BABYLON.Vector3(0, 4, 20) } )
/
// 天空 环境光 地面
/
let 天空 = new BABYLON.PhotoDome("全景天空", 全景天空.天空1, {size: 1000}, scene );
天空.material.backFaceCulling = true //背面不显示
天空.freezeWorldMatrix();
天空.material.freeze();
scene.ambientColor = new BABYLON.Color3(1, 1, 1);
let 环境光 = new BABYLON.HemisphericLight("环境光", new BABYLON.Vector3(0, 1, 0), scene);
环境光.intensity = 0.6;
环境光.specular = BABYLON.Color3.Black();
let 直线光 = new BABYLON.DirectionalLight("直线光", new BABYLON.Vector3(0, -0.5, -1.0), scene);
直线光.position = new BABYLON.Vector3(0, 5, 5);
let 世界地面 = BABYLON.MeshBuilder.CreateGround('世界地面', {width: 200, height:200 }, scene );
世界地面.material = new BABYLON.StandardMaterial("woodMaterial", scene)
世界地面.material.diffuseColor = BABYLON.Color3.Gray(); //反光颜色, 无光黑色
世界地面.material.specularColor = new BABYLON.Color3(0, 0, 0);
世界地面.material.emissiveColor = new BABYLON.Color3(0, 0, 0); //自发光
世界地面.material.backFaceCulling = true
世界地面.material.alpha = 0.5;
世界地面.checkCollisions = true; // 检测碰撞 mass = 0 表示静止
// 木头材质.diffuseTexture = new GrassProceduralTexture("text", 512, scene);
///
// 监听事件
///
canvas节点.setAttribute("tabindex","-1") //可以监听键盘事件
canvas节点.focus()
useResizeObserver(canvas节点, (entries)=>{ engine.resize() })
useIntersectionObserver(canvas节点, ([{ isIntersecting }], observerElement)=>{
if(isIntersecting) engine.runRenderLoop( () => scene.render() )
else engine.stopRenderLoop() //页面不可见时,暂停渲染
本地渲染port.postMessage({类型:"babylon动画可见",可见:isIntersecting})
})
// Keyboard events
scene.actionManager = new BABYLON.ActionManager(scene);
scene.actionManager.registerAction(new BABYLON.ExecuteCodeAction(BABYLON.ActionManager.OnKeyDownTrigger, function (evt) {
inputMap[evt.sourceEvent.key] = evt.sourceEvent.type == "keydown";
}));
scene.actionManager.registerAction(new BABYLON.ExecuteCodeAction(BABYLON.ActionManager.OnKeyUpTrigger, function (evt) {
inputMap[evt.sourceEvent.key] = evt.sourceEvent.type == "keydown";
}));
/
// 加载资源
/
sub打开网页浏览器()
sub菜单UI界面()
sub添加3D模型()
// sub开启VR模式()
}
/**
* @name 网页浏览器
* @description 坐标系在(1 1 1) 看(0 0 0),正好是 X(左边) Y(上) Z(右边)
* @description 实际显示大小,由 Mesh Plane的大小决定,且宽高比 必须是 1:1 才不失真
* @description 实际网页比例,宽度是由主进程 BrowserWindow决定,且ImageData宽度必须相同, 高度自动计算
* @description GUI.Rectangle ,相机位置必须在正面, 否则点击无效
* @description
* @code```js
* 转换html窗口画面数据, 两种方法 ImageBitmap 和 VideoFrame
* let imgdata = new ImageData(new Uint8ClampedArray(uint8array),1000) //Bitmap 数据为 Uint8Array 转为 Uint8ClampedArray
* let frame = await createImageBitmap(imgdata)
* ```
* @code```js canvas offscreen
* const offscreen = new OffscreenCanvas(1000, 750);
* const offscreen画笔 = offscreen.getContext("2d");
* offscreen画笔.putImageData(new ImageData(new Uint8ClampedArray(uint8array),1000) ,0,0)
* offscreen画笔.drawImage(frame,0, 0, 1000, 750)
* offscreen画笔.reset()
* // let frame = new VideoFrame(uint8array, {
// codedWidth: 1000,
// codedHeight: 750,
// timestamp:0,
// format:"BGRA", // "BGRA" | "BGRX"
// })
* ```
*/
async function sub打开网页浏览器(){
const axes = new BABYLON.Debug.AxesViewer(scene, 1) // 红色X 绿色Y 蓝色Z
/
//网页屏幕
const 网页屏幕mesh = BABYLON.MeshBuilder.CreatePlane("网页屏幕", { size:15 }); //必须1:1 否则失真
网页屏幕mesh.position = new BABYLON.Vector3(0, 5, 0) // 屏幕中心在 0 0 0
网页屏幕mesh.rotation.y = Math.PI //旋转180
const 网页屏幕 = GUI.AdvancedDynamicTexture.CreateForMesh(网页屏幕mesh);
网页屏幕.hasAlpha = true //允许透明
const 网页屏幕画笔 = 网页屏幕.getContext()
//
//渲染 html 图片
//
//主进程 app.commandLine.appendSwitch('enable-features','SharedArrayBuffer') 才能用 SharedArrayBuffer
// const offscreen = new OffscreenCanvas(1000, 750);
// const offscreen画笔 = offscreen.getContext("2d");
// let 共享 = new SharedArrayBuffer(10*1024*1024)
// let newView = new Uint8Array(共享)
本地渲染port.onmessage = event =>{
let 数据 = event.data ,dirty = 数据.obj ,buffer = 数据.buffer
let frame = new VideoFrame(buffer , {
codedWidth: dirty.width,
codedHeight: dirty.height,
timestamp:0,
format:"BGRX", // "BGRA" | "BGRX"
})
网页屏幕画笔.clearRect(dirty.x, dirty.y,dirty.width,dirty.height)
网页屏幕画笔.drawImage(frame,dirty.x, dirty.y,dirty.width,dirty.height)
网页屏幕.update()
frame.close()
}
本地渲染port.start()
//鼠标事件矩形
const 事件矩形 = new GUI.Rectangle("鼠标事件矩形"); //默认尺寸 100%
事件矩形.isPointerBlocker = true; //make sure gui events are triggered before the scene events
事件矩形.cornerRadius = 0
事件矩形.thickness = 0
事件矩形.color = "White";
事件矩形.zIndex = 999;
事件矩形.width=1000;
事件矩形.height=750;
事件矩形.hoverCursor = "pointer"
事件矩形.isPickable = true
网页屏幕.addControl(事件矩形);
网页屏幕.moveToNonOverlappedPosition()
let 记录鼠标位置 = {x:0,y:0}
const 获取mouse坐标 = (eventData)=>{ return {clientX:eventData.x , clientY:eventData.y, button:eventData.buttonIndex } }
const 获取wheel坐标 = (eventData)=>{ return {clientX:记录鼠标位置.x , clientY:记录鼠标位置.y, wheel:{x:eventData.x, y:eventData.y} } }
事件矩形.onPointerClickObservable.add((eventData, eventState)=>{ 本地操作port.postMessage({类型:"click" , 属性: 获取mouse坐标(eventData) }) }) //坐标
事件矩形.onPointerDownObservable.add((eventData, eventState)=>{ 本地操作port.postMessage({类型:"pointerdown" , 属性: 获取mouse坐标(eventData) }) })
事件矩形.onPointerUpObservable.add((eventData, eventState)=>{ 本地操作port.postMessage({类型:"pointerup" , 属性: 获取mouse坐标(eventData) }) })
事件矩形.onPointerMoveObservable.add((eventData, eventState)=>{ 本地操作port.postMessage({类型:"pointermove" , 属性: {clientX:eventData.x , clientY:eventData.y} }) ;记录鼠标位置.x = eventData.x; 记录鼠标位置.y = eventData.y; })
事件矩形.onPointerOutObservable.add((eventData, eventState)=>{ 本地操作port.postMessage({类型:"pointerout" }) })
事件矩形.onWheelObservable.add((eventData, eventState)=>{ 本地操作port.postMessage({类型:"wheel" , 属性: 获取wheel坐标(eventData)}) })
}
function sub菜单UI界面(){
//
// UI控制面板
//
let 全屏菜单 = GUI.AdvancedDynamicTexture.CreateFullscreenUI("全屏菜单");
let 进入全屏 = GUI.Button.CreateSimpleButton("进入全屏", "进入全屏");
进入全屏.horizontalAlignment = GUI.Control.HORIZONTAL_ALIGNMENT_LEFT;
进入全屏.verticalAlignment = GUI.Control.VERTICAL_ALIGNMENT_BOTTOM;
进入全屏.textHorizontalAlignment = GUI.TextBlock.HORIZONTAL_ALIGNMENT_LEFT;
进入全屏.width = "90px";
进入全屏.height = "40px";
进入全屏.color = "white";
进入全屏.background = "transparent"
进入全屏.fontSize = 14;
进入全屏.zIndex = 999
全屏菜单.addControl(进入全屏);
进入全屏.onPointerClickObservable.add(() => { engine.enterFullscreen() });
进入全屏.onWheelObservable.add(()=>{ })
engine.onResizeObservable.add( 状态 => 进入全屏.isVisible = 状态.isFullscreen ? false :true )
let 显示fps = new GUI.TextBlock();
显示fps.horizontalAlignment = GUI.Control.HORIZONTAL_ALIGNMENT_RIGHT;
显示fps.verticalAlignment = GUI.Control.VERTICAL_ALIGNMENT_TOP;
显示fps.textHorizontalAlignment = GUI.TextBlock.HORIZONTAL_ALIGNMENT_LEFT;
显示fps.text = "fps";
显示fps.height = "50px";
显示fps.color = "white";
显示fps.textHorizontalAlignment = GUI.Control.HORIZONTAL_ALIGNMENT_CENTER;
显示fps.fontSize = "30px"
显示fps.resizeToFit = true
全屏菜单.addControl(显示fps);
const 显示帧率 = _.throttle(()=>{ 显示fps.text = parseInt(engine.getFps()) }, 500)
scene.onBeforeRenderObservable.add( 状态 => 显示帧率() )
// 2D GUI
// let 界面定位物体 = BABYLON.MeshBuilder.CreatePlane("plane", {size:25,});
// 界面定位物体.position = new BABYLON.Vector3(0, 3, 2)
// let advancedTexture = GUI.AdvancedDynamicTexture.CreateForMesh(界面定位物体);
// let 平面按钮容器 = new GUI.StackPanel();
// advancedTexture.addControl(平面按钮容器);
// let 显示文本 = new GUI.TextBlock();
// 显示文本.text = "欢迎使用2D界面";
// 显示文本.height = "50px";
// 显示文本.color = "white";
// 显示文本.textHorizontalAlignment = GUI.Control.HORIZONTAL_ALIGNMENT_CENTER;
// 显示文本.fontSize = "30px"
// 显示文本.resizeToFit = true
// 平面按钮容器.addControl(显示文本);
// let num = 1
// const button = GUI.Button.CreateSimpleButton("but", "点击");
// button.textBlock.text = '点击';
// button.width = "200px";
// button.height = "50px";
// button.color = "white";
// button.background = "#eeeeee20";
// button.fontSize = 30;
// button.onPointerClickObservable.add((index,eventState) => {
// eventState.currentTarget.textBlock.text = '你好 ' + num++
// })
// 平面按钮容器.addControl(button);
// const 图片展示 = new GUI.Image("图片展示", 天空图片);
// 图片展示.height = "300px"
// 平面按钮容器.addControl(图片展示);
// 3D GUI
// var 显示面板 = new GUI.HolographicBackplate("holoSlate");
// 显示面板.content = 布局网格;
// 显示面板.dimensions = new BABYLON.Vector2(100, 100); //大小
// 显示面板.position = new BABYLON.Vector3(2, 0, 3);
// 显示面板.title = "HolographicBackplate";
// 显示面板.scaling = new BABYLON.Vector3(3, 3, 3);
// UI管理器.addControl(显示面板);
// var 布局网格 = new GUI.Grid("bioGrid");
// 布局网格.background = "transparent";
// 布局网格.adaptHeightToChildren = true
// 布局网格.adaptWidthToChildren = true
// var UI管理器 = new GUI.GUI3DManager(scene);
// // UI管理器.useRealisticScaling = true //真实大小
// var anchor = new BABYLON.AbstractMesh("anchor", scene)
// const Button3D = new GUI.Button3D("reset");
// Button3D.linkToTransformNode(anchor);
// Button3D.position = new BABYLON.Vector3(-5, 2, -5);
// var text1 = new GUI.TextBlock();
// text1.text = "欢迎使用";
// text1.color = "white";
// text1.fontSize = 30;
// Button3D.content = text1;
// UI管理器.addControl(Button3D);
// Button3D.onPointerUpObservable.add(()=> {text1.text = "点击"+ num++ });
// 显示信息.textWrapping = GUI.TextWrapping.WordWrap;
// 显示信息.textHorizontalAlignment = GUI.Control.HORIZONTAL_ALIGNMENT_LEFT;
// 显示信息.textVerticalAlignment = GUI.Control.VERTICAL_ALIGNMENT_TOP;
// 显示信息.setPadding("0%", "5%", "0%", "5%"); //边距
// 显示信息.horizontalAlignment = GUI.Control.HORIZONTAL_ALIGNMENT_RIGHT;
// 显示信息.verticalAlignment = GUI.Control.VERTICAL_ALIGNMENT_TOP;
// var UI管理器2 = new GUI.GUI3DManager(scene);
// const panel = new GUI.PlanePanel();
// UI管理器2.addControl(panel)
// panel.position = new BABYLON.Vector3(2, 2, 6);
// for (let index = 0; index < 5; index++) {
// let button = new GUI.Button3D("click me"+index)
// let text1 = new GUI.TextBlock();
// text1.text = "欢迎使用";
// text1.color = "white";
// text1.fontSize = 30;
// button.content = text1
// button.onPointerUpObservable.add((index,eventState)=>eventState.currentTarget.content.text = "欢迎使用" +num++ );
// panel.addControl(button);
// }
}
async function sub开启VR模式(){
// VR 模式
if(navigator.xr) { // 会话 session -> 参考空间 referenceSpace
webXR = await scene.createDefaultXRExperienceAsync({
uiOptions: {
sessionMode:'immersive-vr' , // 'immersive-vr' | 'immersive-ar' | 'inline',
referenceSpaceType: "local-floor", // 如果是站姿原地使用,适合 local-floor 类型,会增加相对于地板的高度 // 如果使用者是坐姿原地使用,不会离开起始位置太多,适合用 local 类型,让原点更稳定
},
disableDefaultUI: false, //关闭默认进入 按钮 会有按钮,点击进入全景vr模式
floorMeshes: [世界地面],
inputOptions: true, //开启 XR 控制器
disableTeleportation: false, //启用传送功能
optionalFeatures: true, //打开所有
});
let fm = webXR.baseExperience.featuresManager
let 传送环Material = new BABYLON.StandardMaterial("传送环", scene);
传送环Material.backFaceCulling = false;
传送环Material.diffuseColor = BABYLON.Color3.White();
const 传送能力 = fm.enableFeature(BABYLON.WebXRFeatureName.TELEPORTATION, "stable", {
xrInput: webXR.input,
floorMeshes: [世界地面],
// renderingGroupId: 1,
defaultTargetMeshOptions: {
teleportationFillColor: "#00000000",
teleportationBorderColor: "white",
torusArrowMaterial: 传送环Material,
},
});
// const domOverlayFeature = fm.enableFeature(BABYLON.WebXRFeatureName.DOM_OVERLAY, "latest", { element: el }, undefined, false);
webXR.baseExperience.onInitialXRPoseSetObservable.add((xrCamera) => { xrCamera.y = 2; }); // floor is at y === 2
webXR.baseExperience.onStateChangedObservable.add((state) => { });
// const domOverlayFeature = fm.enableFeature(BABYLON.WebXRDomOverlay.Name, "latest", { element: el }, undefined, false);
// BABYLON.WebXRLayers
// const xrCamera = this.WebXR.baseExperience.camera
// const sm = this.WebXR.baseExperience.sessionManager
// const fm = this.WebXR.baseExperience.featuresManager
// fm.enableFeature(BABYLON.WebXRFeatureName.LAYERS,"latest",{},true,false) ;
// const referenceSpace = sessionManager.setReferenceSpaceTypeAsync( /*referenceSpaceType = 'local-floor'*/ );
// const renderTarget = sessionManager.getWebXRRenderTarget( /*outputCanvasOptions: WebXRManagedOutputCanvasOptions*/ );
// const xrWebGLLayer = renderTarget.initializeXRLayerAsync(this.sessionManager.session);
// sessionManager.runXRRenderLoop();
// VR 手柄
var Box_Left_Trigger = BABYLON.MeshBuilder.CreateBox("Box_Left_Trigger",{},this.scene);
Box_Left_Trigger.position = new BABYLON.Vector3(-2.5,1,3);
var Box_Left_Squeeze = BABYLON.MeshBuilder.CreateBox("Box_Left_Squeeze",{},this.scene);
Box_Left_Squeeze.position = new BABYLON.Vector3(-2.5,-1,3);
var Sphere_Left_YButton = BABYLON.MeshBuilder.CreateSphere("Sphere_Left_YButton", {diameter:1}, this.scene);
Sphere_Left_YButton.position = new BABYLON.Vector3(-2,0,3);
var Sphere_Left_XButton = BABYLON.MeshBuilder.CreateSphere("Sphere_Left_XButton", {diameter:1}, this.scene);
Sphere_Left_XButton.position = new BABYLON.Vector3(-2,0,2);
var Box_Left_ThumbStick = BABYLON.MeshBuilder.CreateBox("Box_Left_ThumbStick",{size:0.5},this.scene);
Box_Left_ThumbStick.position = new BABYLON.Vector3(-1,0,1);
var Box_Right_Trigger = BABYLON.MeshBuilder.CreateBox("右_射击按钮",{},this.scene);
Box_Right_Trigger.position = new BABYLON.Vector3(2.5,1,3);
var Box_Right_Squeeze = BABYLON.MeshBuilder.CreateBox("右_握持按钮",{},this.scene);
Box_Right_Squeeze.position = new BABYLON.Vector3(2.5,-1,3);
var Sphere_Right_BButton = BABYLON.MeshBuilder.CreateSphere("右_B按钮", {diameter:1}, this.scene);
Sphere_Right_BButton.position = new BABYLON.Vector3(2,0,3);
var Sphere_Right_AButton = BABYLON.MeshBuilder.CreateSphere("右_A按钮", {diameter:1}, this.scene);
Sphere_Right_AButton.position = new BABYLON.Vector3(2,0,2);
var Box_Right_ThumbStick = BABYLON.MeshBuilder.CreateBox("右_摇杆",{size:0.5},this.scene);
Box_Right_ThumbStick.position = new BABYLON.Vector3(1,0,1);
this.WebXR.input.onControllerAddedObservable.add((controller) => {
controller.onMotionControllerInitObservable.add((motionController) => {
if (motionController.handness === 'right') {
const xr_ids = motionController.getComponentIds();
let 射击按钮 = motionController.getComponent(xr_ids[0]);//xr-standard-trigger
射击按钮.onButtonStateChangedObservable.add(() => {
if (射击按钮.pressed) {
Box_Right_Trigger.scaling= new BABYLON.Vector3(1.2,1.2,1.2);
文本.text =Box_Right_Trigger.name ;
}else{
Box_Right_Trigger.scaling= new BABYLON.Vector3(1,1,1);
文本.text = ""
}
});
let 握持按钮 = motionController.getComponent(xr_ids[1]);//xr-standard-squeeze
握持按钮.onButtonStateChangedObservable.add(() => {
if (握持按钮.pressed) {
Box_Right_Squeeze.scaling= new BABYLON.Vector3(1.2,1.2,1.2);
文本.text =Box_Right_Squeeze.name ;
}else{
Box_Right_Squeeze.scaling=new BABYLON.Vector3(1,1,1);
文本.text = ""
}
});
let 摇杆按钮 = motionController.getComponent(xr_ids[2]);//xr-standard-thumbstick
摇杆按钮.onButtonStateChangedObservable.add(() => {
if (摇杆按钮.pressed) {
Box_Right_ThumbStick.scaling= new BABYLON.Vector3(1.2,1.2,1.2);
文本.text =Box_Right_ThumbStick.name ;
}else{
Box_Right_ThumbStick.scaling=new BABYLON.Vector3(1,1,1);
文本.text = ""
}
});
摇杆按钮.onAxisValueChangedObservable.add((axes) => {
//Box_Right_ThumbStick is moving according to stick axes but camera rotation is also changing..
// Box_Right_ThumbStick.position.x += (axes.x)/100;
// Box_Right_ThumbStick.position.y += (axes.y)/100;
// console.log(values.x, values.y);
});
let A按钮 = motionController.getComponent(xr_ids[3]);//a-button
A按钮.onButtonStateChangedObservable.add(() => {
if (A按钮.pressed) {
Sphere_Right_AButton.scaling= new BABYLON.Vector3(1.2,1.2,1.2);
文本.text =Sphere_Right_AButton.name ;
}else{
Sphere_Right_AButton.scaling=new BABYLON.Vector3(1,1,1);
文本.text = ""
}
});
let B按钮 = motionController.getComponent(xr_ids[4]);//b-button
B按钮.onButtonStateChangedObservable.add(() => {
if (B按钮.pressed) {
Sphere_Right_BButton.scaling= new BABYLON.Vector3(1.2,1.2,1.2);
文本.text =Sphere_Right_BButton.name ;
}else{
Sphere_Right_BButton.scaling=new BABYLON.Vector3(1,1,1);
文本.text = ""
}
});
/* not worked.
let thumbrestComponent = motionController.getComponent(xr_ids[5]);//thumrest
thumbrestComponent.onButtonStateChangedObservable.add(() => {
//not worked
if ((thumbrestComponent.value>0.1&&thumbrestComponent.value<0.6) {
sphere1.position.y=10;
}
if(thumbrestComponent.touched){
sphere1.position.y=10;
}
});
*/
/*
const xr_ids = motionController.getComponentIds();
for (let i=0;i<xr_ids.length;i++){
console.log("right:"+xr_ids[i]);
}
*/
}
})
});
}
}
async function sub添加3D模型(){
let 主角, animating = true, 记录meshArray = [], 记录动作Array = [] //{文件名:文件名, 动作:vmd动作}
const 打开窗口选取文件 = ()=>{
return new Promise((成功后回调,失败后回调)=>{
const input文件按钮 = document.createElement("input") //这个按钮不好看,隐藏 ,触发 button事件
input文件按钮.type = "file"
input文件按钮.accept = ".gltf,.glb,.fbx,.obj,.pmx,.vmd"
input文件按钮.onchange= async (事件)=>{
const File对象数组 = 事件.target.files;
成功后回调(File对象数组[0])
}
input文件按钮.onended= 事件 =>{ 成功后回调("") }
input文件按钮.click()
})
}
const mmd删除模型 = (mesh)=>{
if(mesh?.扩展名=="pmx"){
if(mesh?.动画播放器mmd && mesh?.mmdRuntime){
mesh.mmdRuntime.destroyMmdModel(mesh.动画播放器mmd)
mesh.mmdRuntime.unregister(scene)
}
mesh.dispose()
}
}
const mmd删除全部展示模型 = ()=>{
记录meshArray.forEach( mesh=>{
(mesh?.扩展名=="pmx") ? mmd删除模型(mesh) : mesh.dispose() //glb
})
记录meshArray = []
}
const mmd启停动画_防抖 = _.debounce((mesh)=>{
if(mesh?.mmdRuntime){
if(mesh.mmdRuntime.isAnimationPlaying){
mesh.mmdRuntime.pauseAnimation();
}
else{
mesh.mmdRuntime.seekAnimation(0,true)
mesh.mmdRuntime.playAnimation();
}
}
},100)
const mmd模型加载动作 = (mesh, 传入动作名)=>{
//加载动作后,不能缩放mesh,否则错误
if(mesh?.扩展名=="pmx"){
let vmd = (typeof 传入动作名=="object") ? 传入动作名
:(!传入动作名 && mesh?.动作vmd) ? mesh.动作vmd //播放自带的动作
:(typeof 传入动作名 =="string" && 记录动作Array.length) ?
_.find(记录动作Array, {文件名: 传入动作名}) || 记录动作Array[0]
:(记录动作Array.length) ? _.find(记录动作Array, {文件名: "空闲.vmd"}) || 记录动作Array[0]
: null
if(!vmd) return
mesh.动作vmd = vmd
if(! mesh?.mmdRuntime){
mesh.mmdRuntime = new MMD.MmdRuntime(scene , new MMD.MmdPhysics(scene));
mesh.mmdRuntime.register(scene);
mesh.mmdRuntime.playAnimation()
}
if(! mesh?.动画播放器mmd) mesh.动画播放器mmd = mesh.mmdRuntime.createMmdModel(mesh); //自带一个
let 动画数组 = mesh.动画播放器mmd.runtimeAnimations
if(!动画数组.some(项=>项.animation == vmd.动作)) { mesh.动画播放器mmd.addAnimation(vmd.动作) } // mesh.动画播放器mmd.resetState()
mesh.动画播放器mmd.setAnimation(vmd.文件名);
mesh.mmdRuntime.seekAnimation(0,true) //从头开始播放
mesh.mmdRuntime.playAnimation()
mesh.mmdRuntime.afterPhysics()
}
}
const mmd导入本地模型 = async (传入路径)=>{
let 文件名 = /(?<=\\|\/?)[^\\\/]+$/g.exec(传入路径)[0]
//一种是本地文件, 一种是vite调试文件
let 路径 = /^https?:\/\//g.test(传入路径) ? 传入路径 : /^file:/g.test(传入路径) ? 传入路径 : "file:" + 传入路径
let 目录 = 路径.replace(new RegExp(`${文件名}$`),"")
let 扩展名 = (/[^\.]+$/g.exec(文件名)[0]).toLowerCase()
console.warn("导入模型", 路径)
if(扩展名=="vmd"){ //动作文件
let vmd动作 = await vmdLoader.loadAsync(文件名, 路径);
let vmd对象 = { 文件名, 扩展名, 动作:vmd动作}
return vmd对象
}else{
let mesh = await BABYLON.SceneLoader.ImportMeshAsync("",目录,文件名, scene ).then(加载结果=> 加载结果.meshes[0]);
mesh.文件名 = 文件名, mesh.扩展名 = 扩展名, mesh.路径 = 路径, mesh.目录 = 目录
return mesh
}
}
let 全屏菜单 = GUI.AdvancedDynamicTexture.CreateFullscreenUI("添加模型");
let 添加模型 = GUI.Button.CreateSimpleButton("添加模型", "添加模型");
添加模型.horizontalAlignment = GUI.Control.HORIZONTAL_ALIGNMENT_RIGHT;
添加模型.verticalAlignment = GUI.Control.VERTICAL_ALIGNMENT_BOTTOM;
添加模型.textHorizontalAlignment = GUI.TextBlock.HORIZONTAL_ALIGNMENT_LEFT;
添加模型.width = "90px";
添加模型.height = "40px";
添加模型.color = "white";
添加模型.background = "transparent"
添加模型.fontSize = 14;
添加模型.zIndex = 999
全屏菜单.addControl(添加模型);
添加模型.onPointerClickObservable.add( async() => {
let 文件路径 = (await 打开窗口选取文件() ).path
let mesh = await mmd导入本地模型(文件路径)
if(mesh?.扩展名=="vmd"){ //mmd动作文件
记录动作Array.unshift(mesh)
mmd模型加载动作(记录meshArray[0], mesh.文件名)
}
else{
mesh.position = new BABYLON.Vector3(6, 0, 5)
mesh.scaling.scaleInPlace(0.5);
mesh.lookAt( new BABYLON.Vector3(0, 0, -100) )
if(mesh?.扩展名=="pmx"){ //mmd文件
mmd删除全部展示模型()
mmd模型加载动作(mesh,"舞蹈_快乐摇摆.vmd")
}
记录meshArray.push(mesh)
mesh.actionManager = new BABYLON.ActionManager(scene);
mesh.actionManager.registerAction(
new BABYLON.ExecuteCodeAction(
BABYLON.ActionManager.OnLeftPickTrigger,
(actionEvent)=>{
缩放比例 +=0.5 ;
mesh.scaling.scaleInPlace(缩放比例);
}
)
);
mesh.actionManager.registerAction(
new BABYLON.ExecuteCodeAction(
BABYLON.ActionManager.OnRightPickTrigger,
(actionEvent)=>{
if(缩放比例>1) 缩放比例 -=0.5 ;
mesh.scaling.scaleInPlace(缩放比例);
}
)
);
}
});
//全局快捷键
scene.actionManager.registerAction(
new BABYLON.ExecuteCodeAction(
BABYLON.ActionManager.OnKeyUpTrigger,
(actionEvent)=> { if(actionEvent.sourceEvent.key=="Delete"){ mmd删除全部展示模型() }; }
)
);
//导入模型
let [ 导入主角 ,走路vmd, 空闲vmd, 跳跃vmd, 快乐摇摆vmd, 大摆锤vmd ] = await Promise.all([
mmd导入本地模型(`${mmd模型路径}女主角_三月七/三月七.pmx`),
// mmd导入本地模型(`${mmd模型路径}女主角_荧/荧.pmx`),
mmd导入本地模型(`${mmd模型路径}动作/走路.vmd`),
mmd导入本地模型(`${mmd模型路径}动作/空闲.vmd`),
mmd导入本地模型(`${mmd模型路径}动作/跳跃.vmd`),
mmd导入本地模型(`${mmd模型路径}舞蹈/舞蹈_快乐摇摆.vmd`),
mmd导入本地模型(`${mmd模型路径}舞蹈/舞蹈_大摆锤.vmd`)
])
记录动作Array = [走路vmd, 空闲vmd, 跳跃vmd, 快乐摇摆vmd, 大摆锤vmd ]
主角 = 导入主角
主角.position = new BABYLON.Vector3(0, 0, 5)
主角.scaling.scaleInPlace(0.5);
camera.target = 主角; //跟随
mmd模型加载动作(主角 ,空闲vmd)
主角.mmdRuntime.onAnimationTickObservable.add((a,b,c,d)=>{
if(主角.mmdRuntime.currentFrameTime>=主角.mmdRuntime.animationFrameTimeDuration){
mmd模型加载动作(主角) //播放完成,循环当前动画
}
})
//按键控制主角动画
scene.onBeforeRenderObservable.add(() => {
let keydown = false;
const 计算向量=()=>{
//Manage the movements of the character (e.g. position, direction)
// let 射线 = camera.getForwardRay()
let 终点= 主角.position
let 起点 = camera.position
// let plane = new BABYLON.Plane(0,1,0,0) // Ax + By + Cz + D =0 设任意平面上一点(x,y,z),(A,B,C)是法向量
// let 射线与平面相交点 = 终点.projectOnPlane(plane, 起点)
// 点在平面上投影 new BABYLON.Vector3(终点.x,0,终点.z)
let 纵向向量 = new BABYLON.Vector3(终点.x - 起点.x, 0, 终点.z - 起点.z) .normalize()
let 横向向量 = 纵向向量.cross(BABYLON.Vector3.Up())
return{
前方视线向量:纵向向量.scale(100).negate(),
纵向向量,
横向向量
}
}
if (inputMap["w"]) {
let 向量 = 计算向量()
主角.lookAt(向量.前方视线向量) ;
主角.position = 主角.position.add(向量.纵向向量.scale(0.2)) // scaleInPlace 会修改自身
keydown = true;
}
if (inputMap["s"]) {
let 向量 = 计算向量()
主角.lookAt(向量.前方视线向量)
主角.position = 主角.position.add(向量.纵向向量.scaleInPlace(0.2).negate())
keydown = true;
}
if (inputMap["a"]) {
let 向量 = 计算向量()
主角.lookAt(向量.横向向量.scale(100).negate())
主角.position = 主角.position.add(向量.横向向量.scaleInPlace(0.2))
keydown = true;
}
if (inputMap["d"]) {
let 向量 = 计算向量()
主角.lookAt(向量.横向向量.scale(100))
主角.position = 主角.position.add(向量.横向向量.scaleInPlace(0.2).negate())
keydown = true;
}
if (inputMap["1"]) { keydown = true; }
if (inputMap["2"]) { keydown = true; }
if (inputMap[" "]) { keydown = true; mmd启停动画_防抖(记录meshArray[0]) }
//管理播放动画
if (keydown) {
if (!animating) {
animating = true;
if (inputMap["s"]) {}
else if(inputMap["1"]) mmd模型加载动作(主角, 快乐摇摆vmd)
else if(inputMap["2"]) mmd模型加载动作(主角, 大摆锤vmd)
else if(inputMap[" "]) mmd模型加载动作(主角, 跳跃vmd)
else mmd模型加载动作(主角, 走路vmd)
}
}
else {
if(animating) {
animating = false;
let 等待跳跃 = false
if(主角.动作vmd == 跳跃vmd && !等待跳跃){
等待跳跃 = true
setTimeout(()=>{ mmd模型加载动作(主角, 空闲vmd) ; 等待跳跃=false }, 500)
}
else if(!等待跳跃) mmd模型加载动作(主角, 空闲vmd)
}
}
});
console.log("主角模型",主角)
}
function sub视频播放模块(){
//视频播放
// let 全景播放器 = new BABYLON.VideoDome( "videoDome",
// ["./静态资源/test.mp4"], {
// resolution: 32,
// clickToPlay: true,
// halfDomeMode: true
// }, scene)
// 全景播放器.videoMode = BABYLON.VideoDome.MODE_MONOSCOPIC;
var 配置 = {
height: 5.4762,
width: 7.3967,
sideOrientation: BABYLON.Mesh.DOUBLESIDE
};
var 播放器网格 = BABYLON.MeshBuilder.CreatePlane("plane", 配置, scene);
播放器网格.position = (new BABYLON.Vector3(0,0,0.1));
var 视频纹理 = new BABYLON.VideoTexture("vidtex","", scene);
// 视频纹理.coordinatesMode = BABYLON.VideoTexture.PLANAR_MODE
var 视频材质 = new BABYLON.StandardMaterial("m", scene);
视频材质.diffuseTexture = 视频纹理;
视频材质.roughness = 1;
视频材质.emissiveColor = BABYLON.Color3.White();
播放器网格.material = 视频材质;
scene.onPointerObservable.add((evt)=>{
if(evt.pickInfo.pickedMesh === 播放器网格){
视频纹理.video.src = "./静态资源/test.mp4"
视频纹理.video.play();
// 视频纹理.video.pause();
}
}, BABYLON.PointerEventTypes.POINTERPICK);
const 六自由度控制行为 = new BABYLON.SixDofDragBehavior();
六自由度控制行为.dragDeltaRatio = 0.2;
播放器网格.addBehavior(六自由度控制行为)
}
init()
}
/*
//------- 加载普通模型 -----------------
BABYLON.SceneLoader.ImportMesh("", 模型路径, "girl.glb", scene, function (newMeshes, particleSystems, skeletons, animationGroups) {
let hero = newMeshes[0];
hero.position = new BABYLON.Vector3(0, 0, 5)
hero.scaling.scaleInPlace(0.3);
camera.target = hero; //跟随
let 前进速度 = 0.3, 后退速度 = 0.5, 旋转速度 = 0.1;
let animating = true;
const walkAnim = _.find(animationGroups, {name:"Walking"});
const walkBackAnim = _.find(animationGroups, {name:"WalkingBack"});
const idleAnim = _.find(animationGroups, {name:"Idle"});
const sambaAnim = _.find(animationGroups, {name:"Samba"});
const 物体前方射线拾取物体 = ()=>{
function vecToLocal(vector, mesh){
var m = mesh.getWorldMatrix();
var v = BABYLON.Vector3.TransformCoordinates(vector, m);
return v;
}
var origin = hero.position;
let forward = vecToLocal(new BABYLON.Vector3(0,0,1), hero);
var direction = forward.subtract(origin);
direction = BABYLON.Vector3.Normalize(direction);
var ray = new BABYLON.Ray(origin, direction, 100);
var hit = scene.pickWithRay(ray);
if (hit.pickedMesh){
// hit.pickedMesh.scaling.y += 0.01;
}
}
//Rendering loop (executed for everyframe)
scene.onBeforeRenderObservable.add(() => {
// 物体前方射线拾取物体()
let keydown = false;
const 计算向量=()=>{
//Manage the movements of the character (e.g. position, direction)
// let 射线 = camera.getForwardRay()
let 终点= hero.position
let 起点 = camera.position
// let plane = new BABYLON.Plane(0,1,0,0) // Ax + By + Cz + D =0 设任意平面上一点(x,y,z),(A,B,C)是法向量
// let 射线与平面相交点 = 终点.projectOnPlane(plane, 起点)
// 点在平面上投影 new BABYLON.Vector3(终点.x,0,终点.z)
let 纵向向量 = new BABYLON.Vector3(终点.x - 起点.x, 0, 终点.z - 起点.z) .normalize()
let 横向向量 = 纵向向量.cross(BABYLON.Vector3.Up())
return{
前方视线向量:纵向向量.scale(100).negate(),
纵向向量,
横向向量
}
}
if (inputMap["w"]) {
let 向量 = 计算向量()
hero.lookAt(向量.前方视线向量) ;
// hero.moveWithCollisions( 向量.纵向向量.scaleInPlace(0.2) )
hero.position = hero.position.add(向量.纵向向量.scale(0.2)) // scaleInPlace 会修改自身
keydown = true;
}
if (inputMap["s"]) {
let 向量 = 计算向量()
hero.lookAt(向量.前方视线向量)
// hero.moveWithCollisions(hero.forward.scaleInPlace(-后退速度));
// hero.moveWithCollisions(向量.纵向向量.scaleInPlace(0.2).negate());
hero.position = hero.position.add(向量.纵向向量.scaleInPlace(0.2).negate())
keydown = true;
}
if (inputMap["a"]) {
let 向量 = 计算向量()
hero.lookAt(向量.横向向量.scale(100).negate())
hero.position = hero.position.add(向量.横向向量.scaleInPlace(0.2))
keydown = true;
}
if (inputMap["d"]) {
let 向量 = 计算向量()
hero.lookAt(向量.横向向量.scale(100))
hero.position = hero.position.add(向量.横向向量.scaleInPlace(0.2).negate())
keydown = true;
}
if (inputMap["b"]) {
keydown = true;
}
if (inputMap[" "]) {
mmd启停动画_防抖()
}
//管理播放动画
if (keydown) {
if (!animating) {
animating = true;
if (inputMap["s"]) walkBackAnim.start(true, 1.0, walkBackAnim.from, walkBackAnim.to, false);
else if(inputMap["b"]) sambaAnim.start(true, 1.0, sambaAnim.from, sambaAnim.to, false);
else walkAnim.start(true, 1.0, walkAnim.from, walkAnim.to, false);
}
}
else {
if (animating) {
//Default animation is idle when no key is down
idleAnim.start(true, 1.0, idleAnim.from, idleAnim.to, false);
//Stop all animations besides Idle Anim when no key is down
sambaAnim.stop();
walkAnim.stop();
walkBackAnim.stop();
//Ensure animation are played only once per rendering loop
animating = false;
}
}
});
});
let 角色模型 = BABYLON.MeshBuilder.CreateCapsule('sphere1', { segments: 10, diameter: 1, });
角色模型.material = new BABYLON.StandardMaterial("myMaterial", scene);
角色模型.material.diffuseColor = BABYLON.Color3.Green();//漫反射颜色
角色模型.position = new BABYLON.Vector3(0, 0, 3); ;
角色模型.checkCollisions = true; //检测碰撞
角色模型.ellipsoid = new BABYLON.Vector3(1, 1, 1); //碰撞的表面 长 高 宽
角色模型.ellipsoidOffset = new BABYLON.Vector3(1, 1, 1);
scene.addMesh(角色模型)
const 六自由度控制行为 = new BABYLON.SixDofDragBehavior();
六自由度控制行为.dragDeltaRatio = 0.2;
var boundingBox = BABYLON.BoundingBoxGizmo.MakeNotPickableAndWrapInBoundingBox(角色模型)
boundingBox.addBehavior(六自由度控制行为)
BABYLON.SceneLoader.Append(模型路径, "植物.glb", scene, function (scene) { }); // 模型添加成功后,执行场景对象的一些方法
BABYLON.SceneLoader.ImportMesh("", 模型路径, "植物.glb", scene, function (newMeshes, particleSystems,skeletons,animationGroups){
let hero = newMeshes[0];
hero.position = new BABYLON.Vector3(3, 0, -5)
//Scale the model down
hero.scaling.scaleInPlace(1);
hero.lookAt( new BABYLON.Vector3(1, 0, 1) )
let 摇摆动画 =_.find(animationGroups,{name:"摇摆"})
摇摆动画.stop();
//Lock camera on the character
// camera.target = hero;
//Get the Samba animation Group
// const sambaAnim = scene.getAnimationGroupByName("Samba");
// //Play the Samba animation
// sambaAnim.start(true, 1.0, sambaAnim.from, sambaAnim.to, false);
});
BABYLON.SceneLoader.ImportMesh("", 模型路径, "cute.glb", scene, function (newMeshes, particleSystems,skeletons,animationGroups){
let hero = newMeshes[0];
hero.position = new BABYLON.Vector3(20, 0, 5)
hero.scaling.scaleInPlace(0.6);
hero.lookAt( new BABYLON.Vector3(0, 0, 1) )
let 摇摆动画 =_.find(animationGroups,{name:"摇摆"})
摇摆动画.stop();
//Lock camera on the character
// camera.target = hero;
//Get the Samba animation Group
// const sambaAnim = scene.getAnimationGroupByName("Samba");
// //Play the Samba animation
// sambaAnim.start(true, 1.0, sambaAnim.from, sambaAnim.to, false);
});
*/