前言
前段時(shí)間想要做一個(gè)web端的圖形化積木式編程(類(lèi)似少兒編程)的案例,網(wǎng)上沖浪了一圈又一圈,終于技術(shù)選型好,然后代碼一頓敲岩喷,終于出來(lái)了一個(gè)雛形。
TIPS:該案例設(shè)計(jì)主要參考iRobot Coding,只用做學(xué)習(xí)用途监憎,侵刪纱意。
最終實(shí)現(xiàn)效果
最終實(shí)現(xiàn)效果
本文實(shí)現(xiàn)效果
-
將3d界面放入可拖動(dòng)窗口中
將3d界面放入可拖動(dòng)窗口中
完整代碼
- 可移動(dòng)的cavans窗口
<template>
<div style="height: 100%;width: 100%;">
<!--窗口關(guān)閉后顯示的開(kāi)啟按鈕-->
<v-btn
icon
color="#505781"
style="padding: 10px 20px 5px 20px;position: absolute;right: 10px;top: 10px;"
v-show="!show3Dcanvans"
@click="show3Dcanvans=!show3Dcanvans"
>
<v-icon>mdi-equal-box</v-icon>
</v-btn>
<!--可移動(dòng)窗口-->
<div id="window1" v-window="windowParams" v-show="show3Dcanvans">
<!--頂欄-->
<div id="header" style="display: flex;justify-content: space-between;">
<!--標(biāo)題-->
<div class="header">仿真
</div>
<!--關(guān)閉窗口按鈕-->
<v-btn
icon
color="white"
style="padding: 10px 20px 5px 20px;"
@click="show3Dcanvans=!show3Dcanvans"
>
<v-icon>mdi-close</v-icon>
</v-btn>
</div>
<!--3d引擎cavans-->
<div style="z-index: 10001;padding-left: 10px;">
<canvas id="renderCanvas"></canvas>
</div>
</div>
<div style="display: flex;justify-content: space-around;width: 680px;">
<div>
<label>alpha:</label>
<button type="button" class="btn"
@click="setCameraPosition('alpha',Math.PI/10)">+
</button>
<button type="button" class="btn"
@click="setCameraPosition('alpha',-Math.PI/10)">-
</button>
</div>
<div>
<label>beta:</label>
<button type="button" class="btn"
@click="setCameraPosition('beta',Math.PI/10)">+
</button>
<button type="button" class="btn"
@click="setCameraPosition('beta',-Math.PI/10)">-
</button>
</div>
<div>
<label>radius:</label>
<button type="button" class="btn"
@click="setCameraPosition('radius',Math.PI)">+
</button>
<button type="button" class="btn"
@click="setCameraPosition('radius',-Math.PI)">-
</button>
</div>
</div>
</div>
</template>
<script>
import * as BABYLON from 'babylonjs';
import * as BABYLON_MATERAIAL from "babylonjs-materials"
import * as GUI from 'babylonjs-gui';
import ammo from "ammo.js";
import utils from "./utils";
const url = "http://localhost:8088/static/simulator/"
//全局變量
var scene = null //場(chǎng)景實(shí)例
var engine = null //3d引擎實(shí)例
var camera = null //攝像機(jī)實(shí)例
var plane = null //綠地
var ground = null //網(wǎng)格
var skybox = null //天空盒
var car = null //小車(chē)
var cubeParent = null //方塊組
var startingPoint = new BABYLON.Vector3(0, 0, 0)//當(dāng)前點(diǎn)擊位置
//質(zhì)量 、摩擦系數(shù)鲸阔、反彈系數(shù)
const bodyMass = 0.5, bodyFriction = 0.5, bodyRestitution = 0.9;
const groundFriction = 0.8, groundRestitution = 0.5;
let speedSelect = null//顯示速度選擇窗
let buttonClicked = false//按鈕是否被點(diǎn)擊
async function loadScene() {
//場(chǎng)景初始化偷霉,可看文章一
scene = initScene()
//可看文章五,自定義啟動(dòng)動(dòng)畫(huà)
customLoadingUI()
//加載網(wǎng)絡(luò)模型褐筛,可看文章二
await initRobot()
//可看文章三类少,監(jiān)聽(tīng)拖動(dòng)事件,實(shí)現(xiàn)點(diǎn)擊拖動(dòng)模型
dragListening()
//可看文章四渔扎,實(shí)現(xiàn)碰撞效果
// 1硫狞、初始化重力碰撞系統(tǒng)
await initAmmo()
// 2、將地面和小車(chē)加入碰撞體
addPhysicEffect()
//3、加入碰撞體方塊
initCubes()
//可看文章五残吩,關(guān)閉啟動(dòng)動(dòng)畫(huà)
setTimeout(() => {
hideLoadingUI()
}, 1000)
//可看文章六财忽,相機(jī)控制與相機(jī)動(dòng)畫(huà)
setTimeout(function () {
console.log(camera.alpha, camera.beta, camera.radius)
//攝像機(jī)原位置 1.1383885512243588 1.3642551964995249 50
//通過(guò)相機(jī)控制輸出獲取期望值,然后填入
ArcAnimation(-1.5649881922490174, 0, 68.84955592153878)
}, 1500)
//可看文章七世剖,babylonjs-gui 按鈕實(shí)現(xiàn)
initButtons()
}
function initButtons() {
//在場(chǎng)景中設(shè)置一個(gè)全屏的前景2d界面
var advancedTexture = GUI.AdvancedDynamicTexture.CreateFullscreenUI("btnsUI", true, scene);
//初始化重啟按鈕
var restartBtn = GUI.Button.CreateImageOnlyButton(
"but",
url + "restart.png"
);
restartBtn.height = "60px";
restartBtn.width = "60px";
restartBtn.thickness = 0;//邊框
restartBtn.horizontalAlignment = GUI.Control.HORIZONTAL_ALIGNMENT_RIGHT//在全屏的水平排列方位
restartBtn.verticalAlignment = GUI.Control.VERTICAL_ALIGNMENT_BOTTOM//在全屏的垂直排列方位
restartBtn.top = "-20px"http://頂部偏移量
restartBtn.left = "-20px"http://左側(cè)偏移量
//按鈕點(diǎn)擊時(shí)間監(jiān)聽(tīng)回調(diào)
restartBtn.onPointerClickObservable.add(function () {
console.log("重啟引擎")
});
//初始化速度選擇彈窗框(包含了龜速和兔速按鈕的向上彈出框)
speedSelect = new GUI.Rectangle("speedSelect");
speedSelect.height = "110px";
speedSelect.width = "60px";
speedSelect.horizontalAlignment = GUI.Control.HORIZONTAL_ALIGNMENT_LEFT
speedSelect.verticalAlignment = GUI.Control.VERTICAL_ALIGNMENT_BOTTOM
speedSelect.top = "-90px"
speedSelect.left = "20px"
speedSelect.thickness = 3;
speedSelect.color = "#505781";
speedSelect.background = "white";
speedSelect.cornerRadius = 15;
speedSelect.isVisible = false
//初始化龜速按鈕
var slowImg = GUI.Button.CreateImageOnlyButton(
"slowlyBtn",
url + "turtle.png"
);
slowImg.width = "30px";
slowImg.height = "30px";
slowImg.thickness = 0;
slowImg.verticalAlignment = GUI.Control.VERTICAL_ALIGNMENT_TOP
slowImg.top = 15;
slowImg.onPointerClickObservable.add(function () {
console.log("slowImg click")
image.source = url + "turtle.png"
// robotCotroller.setSpeed(1)
//后續(xù)通過(guò)robot控制器實(shí)例設(shè)置移動(dòng)速度參數(shù)
console.log('設(shè)置移動(dòng)速度:1')
speedSelect.isVisible = false//選擇完定罢,關(guān)閉速度選擇彈窗
});
//初始化兔速按鈕
var fastImg = GUI.Button.CreateImageOnlyButton(
"fastBtn",
url + "rabbit.png"
);
fastImg.width = "30px";
fastImg.height = "30px";
fastImg.thickness = 0;
fastImg.verticalAlignment = GUI.Control.VERTICAL_ALIGNMENT_BOTTOM
fastImg.top = -15;
fastImg.onPointerClickObservable.add(function () {
console.log("fastImg click")
image.source = url + "rabbit.png"
// robotCotroller.setSpeed(6)
console.log('設(shè)置移動(dòng)速度:6')
speedSelect.isVisible = false//選擇完笤虫,關(guān)閉速度選擇彈窗
});
speedSelect.addControl(slowImg)
speedSelect.addControl(fastImg)
//當(dāng)前選擇速度模式按鈕(點(diǎn)擊會(huì)彈出速度選擇彈窗)
var speedBtn = new GUI.Button("speedBtn");
speedBtn.height = "60px";
speedBtn.width = "60px";
speedBtn.horizontalAlignment = GUI.Control.HORIZONTAL_ALIGNMENT_LEFT
speedBtn.verticalAlignment = GUI.Control.VERTICAL_ALIGNMENT_BOTTOM
speedBtn.top = "-20px"
speedBtn.left = "20px"
speedBtn.thickness = 0;
speedBtn.onPointerClickObservable.add(function () {
speedSelect.isVisible = !speedSelect.isVisible
if (speedSelect.isVisible) {
buttonClicked = true//設(shè)置速度選擇彈窗彈窗狀態(tài)為true旁瘫,用于彈窗后移動(dòng)模型時(shí)取消彈窗狀態(tài)
}
});
//當(dāng)前選擇速度模式按鈕的邊框
var speedBtnBorder = new GUI.Ellipse();
speedBtnBorder.width = "60px"
speedBtnBorder.height = "60px";
speedBtnBorder.color = "#505781";
speedBtnBorder.thickness = 3;
speedBtnBorder.background = "white";
//當(dāng)前選擇速度模式按鈕的圖案
var image = new GUI.Image("currentSpeedBtn", url + "turtle.png");
image.width = "30px";
image.height = "30px";
image.thickness = 0;
speedBtn.addControl(speedBtnBorder);
speedBtn.addControl(image);
//將按鈕添加在全屏的2d前景界面中
advancedTexture.addControl(restartBtn);
advancedTexture.addControl(speedBtn);
advancedTexture.addControl(speedSelect);
}
/**
* 相機(jī)動(dòng)畫(huà)
* @param toAlpha 動(dòng)畫(huà)完成時(shí)的alpha
* @param toBeta 動(dòng)畫(huà)完成時(shí)的beta
* @param toRadius 動(dòng)畫(huà)完成時(shí)的radius
* @constructor
*/
function ArcAnimation(toAlpha, toBeta, toRadius) {
let animCamAlpha = new BABYLON.Animation("animCam",
"alpha",//需要設(shè)置動(dòng)畫(huà)的屬性名稱
30,//每秒幀數(shù)
BABYLON.Animation.ANIMATIONTYPE_FLOAT,//屬性變量類(lèi)型 浮點(diǎn)型
BABYLON.Animation.ANIMATIONLOOPMODE_CONSTANT//動(dòng)畫(huà)循環(huán)模式 保持最終狀態(tài)
);
let begin = 0, end = 100
let keysAlpha = [];//alpha動(dòng)畫(huà)關(guān)鍵幀列表,從0-100%琼蚯,alpha從camera.alpha變化到傳入的toAlpha參數(shù)值
keysAlpha.push({
frame: begin,
value: camera.alpha
});
keysAlpha.push({
frame: end,
value: toAlpha
});
animCamAlpha.setKeys(keysAlpha)//配置動(dòng)畫(huà)關(guān)鍵幀列表到動(dòng)畫(huà)對(duì)象中
//初始化beta動(dòng)畫(huà)參數(shù)
let animCamBeta = new BABYLON.Animation("animCam", "beta", 30, BABYLON.Animation.ANIMATIONTYPE_FLOAT, BABYLON.Animation.ANIMATIONLOOPMODE_CONSTANT)
let keysBeta = []
keysBeta.push({frame: begin, value: camera.beta})
keysBeta.push({frame: end, value: toBeta})
animCamBeta.setKeys(keysBeta)
//初始化radius動(dòng)畫(huà)參數(shù)
let animCamRadius = new BABYLON.Animation("animCam", "radius", 30, BABYLON.Animation.ANIMATIONTYPE_FLOAT, BABYLON.Animation.ANIMATIONLOOPMODE_CONSTANT)
let keysRadius = [];
keysRadius.push({frame: begin, value: camera.radius})
keysRadius.push({frame: end, value: toRadius})
animCamRadius.setKeys(keysRadius)
//加入相機(jī)動(dòng)畫(huà)列表中
camera.animations.push(animCamAlpha, animCamBeta, animCamRadius)
//通過(guò)scene開(kāi)啟camera的動(dòng)畫(huà)列表
scene.beginAnimation(
camera,//開(kāi)始動(dòng)畫(huà)列表的對(duì)象
begin,//動(dòng)畫(huà)開(kāi)始幀
end,//動(dòng)畫(huà)結(jié)束幀
false,//動(dòng)畫(huà)是否循環(huán)
6,//動(dòng)畫(huà)的速度比
() => {
console.log('camera')
}//動(dòng)畫(huà)執(zhí)行完成回調(diào)
)
}
function hideLoadingUI() {
engine.hideLoadingUI()
document.getElementById("customLoadingScreenDiv").remove()
}
function customLoadingUI() {
BABYLON.DefaultLoadingScreen.prototype.displayLoadingUI = function () {
this._loadingDiv = document.createElement("div");
this._loadingDiv.id = "customLoadingScreenDiv";
this._loadingDiv.style.background = "#505781";
this._loadingDiv.style.zIndex = "10006"
this._loadingDiv.style.height = "100%"
var img = new Image()
img.src = url + "loading.gif";
img.style.padding = "15%";
img.style.paddingTop = "30%";
this._loadingDiv.appendChild(img);
this._resizeLoadingUI();
window.addEventListener("resize", this._resizeLoadingUI);
//這兩個(gè)樣式修改需要在this._resizeLoadingUI之后酬凳,因?yàn)樵摵瘮?shù)執(zhí)行后會(huì)相對(duì)window窗口定位出cavans的位置,然后設(shè)置loading的位置
//而我們需要的是將其插入到可移動(dòng)窗口中遭庶,以統(tǒng)一窗口的開(kāi)啟關(guān)閉
this._loadingDiv.style.left = "10px"
this._loadingDiv.style.top = "39px"
// document.body.appendChild(this._loadingDiv);
// 修改為
// 獲取當(dāng)前可移動(dòng)窗口元素
let window1 = document.getElementById('window1')
let header = document.getElementById('header')
window1.insertBefore(this._loadingDiv,header)
};
engine.displayLoadingUI();
}
async function initAmmo() {
const Ammo = await ammo();
console.log("Ammo", Ammo)
//啟用y方向重力
scene.enablePhysics(new BABYLON.Vector3(0, -10, 0), new BABYLON.AmmoJSPlugin(true, Ammo));
scene.onReadyObservable.add(function () {
console.log(scene.getPhysicsEngine()._physicsPlugin.bjsAMMO.btDefaultCollisionConfiguration());
console.log(scene.getPhysicsEngine()._physicsPlugin._collisionConfiguration);
console.log(scene.getPhysicsEngine()._physicsPlugin._dispatcher);
console.log(scene.getPhysicsEngine()._physicsPlugin._solver);
console.log(scene.getPhysicsEngine()._physicsPlugin.world);
});
}
function addPhysicEffect() {
//地面啟用碰撞體
plane.physicsImpostor = new BABYLON.PhysicsImpostor(plane, BABYLON.PhysicsImpostor.BoxImpostor, {
mass: 0,
restitution: groundRestitution,
friction: groundFriction
}, scene);
//小車(chē)啟用碰撞體
var robotBody = utils.getMeshFromMeshs(car, "Glass_Plane.006")
console.log('robotBody', robotBody)
var robotSize = utils.getMeshSize(robotBody)
var robotScale = 50
const robotScalingFactor = robotScale / 10;
var physicsRoot = makePhysicsObjects(car, scene, robotScalingFactor, robotSize)
//小車(chē)實(shí)例
car = physicsRoot
}
function makePhysicsObjects(newMeshes, scene, scaling, size) {
var physicsRoot = new BABYLON.Mesh("robot", scene);
// physicsRoot.position.y -= 2
newMeshes.forEach((m) => {
if (m.parent == null) {
physicsRoot.addChild(m)
}
})
// 將所有碰撞體加入physics impostor
physicsRoot.getChildMeshes().forEach((m) => {
m.scaling.x = Math.abs(m.scaling.x)
m.scaling.y = Math.abs(m.scaling.y)
m.scaling.z = Math.abs(m.scaling.z)
m.physicsImpostor = new BABYLON.PhysicsImpostor(m, BABYLON.PhysicsImpostor.BoxImpostor, {mass: 0.1}, scene);
})
// 縮放根對(duì)象并將其變成physics impostor
physicsRoot.scaling.scaleInPlace(scaling)
physicsRoot.physicsImpostor = new BABYLON.PhysicsImpostor(physicsRoot, BABYLON.PhysicsImpostor.NoImpostor, {
mass: bodyMass,
friction: bodyFriction,
restitution: bodyRestitution
}, scene);
//轉(zhuǎn)為碰撞體后宁仔,其y軸會(huì)偏移,偏移比例根據(jù)實(shí)際調(diào)整
const impostorOffset = -(size.y) / 1.1
physicsRoot.physicsImpostor.setDeltaPosition(new BABYLON.Vector3(0, impostorOffset, 0));
physicsRoot.position.subtractInPlace(new BABYLON.Vector3(0, -impostorOffset, 0));
return physicsRoot
}
function initCubes() {
var scale = 1
const scalingFactor = scale / 10;
cubeParent = new BABYLON.TransformNode("cubes");
const cubeHeight = 80 * scalingFactor
var cube = createBasicRoundedBox(scene, "cube", cubeHeight)
cube.position._y += cubeHeight / 2
cube.position._x -= 100
cube.material = new BABYLON.StandardMaterial("amaterial", scene);
cube.material.diffuseColor = new BABYLON.Color3(16 / 255.0, 156 / 255.0, 73 / 255.0);
cubeParent[0] = cube
var cube2 = createBasicRoundedBox(scene, "cube2", cubeHeight)
cube2.position._y += cubeHeight / 2
cube2.position._x -= 100
cube2.position._z += cubeHeight * 2
cube2.material = new BABYLON.StandardMaterial("amaterial", scene);
cube2.material.diffuseColor = new BABYLON.Color3(48 / 255.0, 102 / 255.0, 150 / 255.0);
cubeParent[1] = cube2
var cube3 = createBasicRoundedBox(scene, "cube3", cubeHeight)
cube3.position._y += cubeHeight / 2
cube3.position._x -= 100
cube3.position._z -= cubeHeight * 2
cube3.material = new BABYLON.StandardMaterial("amaterial", scene);
cube3.material.diffuseColor = new BABYLON.Color3(199 / 255.0, 88 / 255.0, 93 / 255.0);
cubeParent[2] = cube3
//對(duì)象事件監(jiān)聽(tīng)
let actionManager = new BABYLON.ActionManager(scene);
cube.actionManager = actionManager;
cube2.actionManager = actionManager;
cube3.actionManager = actionManager;
// 方塊鼠標(biāo)hover高亮
var hl = new BABYLON.HighlightLayer("hl1", scene);
actionManager.registerAction(new BABYLON.ExecuteCodeAction(BABYLON.ActionManager.OnPointerOverTrigger, function (evn) {
var hover_cube = evn.meshUnderPointer.id
if (hover_cube == cube.name) {
hl.addMesh(cube, BABYLON.Color3.White());
} else if (hover_cube == cube2.name) {
hl.addMesh(cube2, BABYLON.Color3.White());
} else if (hover_cube == cube3.name) {
hl.addMesh(cube3, BABYLON.Color3.White());
}
}));
//方塊鼠標(biāo)hover離開(kāi)取消高亮
actionManager.registerAction(new BABYLON.ExecuteCodeAction(BABYLON.ActionManager.OnPointerOutTrigger, function (evn) {
var hover_cube = evn.meshUnderPointer.id
if (hover_cube == cube.name) {
hl.removeMesh(cube);
} else if (hover_cube == cube2.name) {
hl.removeMesh(cube2);
} else if (hover_cube == cube3.name) {
hl.removeMesh(cube3);
}
}));
scene.freezeMaterials();
}
//創(chuàng)建帶碰撞體的方塊
function createBasicRoundedBox(scene, name, size) {
let mass = 0.25, restitution = 0.5, friction = 0.5
const boxSide = size;
const sphereSide = boxSide * 3.1 / 2;
const sphere = BABYLON.MeshBuilder.CreateSphere('sphere', {diameter: sphereSide, segments: 16}, scene);
const box = BABYLON.Mesh.CreateBox('box', boxSide, scene);
const intersection = BABYLON.CSG.FromMesh(box).intersect(BABYLON.CSG.FromMesh(sphere));
sphere.dispose();
box.dispose();
const roundedBox = intersection.toMesh(
name,
new BABYLON.StandardMaterial('roundedBoxMaterial', scene),
scene
);
roundedBox.draggable = true;
roundedBox.physicsImpostor = new BABYLON.PhysicsImpostor(
roundedBox,
BABYLON.PhysicsImpostor.BoxImpostor,
{mass: mass, restitution: restitution, friction: friction}
);
roundedBox.material.freeze();
roundedBox.material.specularColor = new BABYLON.Color3(0, 0, 0);
roundedBox.freezeWorldMatrix()
return roundedBox;
}
//鼠標(biāo)點(diǎn)擊拖動(dòng)監(jiān)聽(tīng)
function dragListening() {
// 物體拖拽事件
var canvas = engine.getRenderingCanvas();
var currentMesh;//當(dāng)前點(diǎn)擊的模型網(wǎng)格
//判斷當(dāng)前點(diǎn)擊對(duì)象是否是地板
var getGroundPosition = function () {
var pickinfo = scene.pick(scene.pointerX, scene.pointerY, function (mesh) {
return (mesh == ground || mesh == plane);
});
if (pickinfo.hit) {
return pickinfo.pickedPoint;
}
return null;
}
//鼠標(biāo)點(diǎn)下
var onPointerDown = function (evt) {
if (evt.button !== 0) {
return;
}
//判斷當(dāng)前是否點(diǎn)擊一個(gè)模型網(wǎng)格峦睡,如果是地板翎苫、天空盒等對(duì)象,則設(shè)置hit為false
var pickInfo = scene.pick(scene.pointerX, scene.pointerY, function (mesh) {
return (mesh !== ground && mesh !== plane && mesh !== skybox);
});
// console.log("pickInfo", pickInfo)
//如果hit為true榨了,則不為地板煎谍、天空盒等對(duì)象
if (pickInfo.hit) {
currentMesh = pickInfo.pickedMesh;//獲取當(dāng)前點(diǎn)擊對(duì)象
if (currentMesh.parent == null) {
console.log("no parent")//沒(méi)有父節(jié)點(diǎn)則就是car對(duì)象了
} else if (currentMesh.parent.name == car.name) {
//有父節(jié)點(diǎn),證明現(xiàn)在點(diǎn)擊的是子對(duì)象龙屉,而移動(dòng)需要移動(dòng)整個(gè)小車(chē)對(duì)象呐粘,所以設(shè)置當(dāng)前點(diǎn)擊mesh為父節(jié)點(diǎn)(即car對(duì)象)
currentMesh = currentMesh.parent
}
console.log("currentMesh", currentMesh)
//獲取當(dāng)前移動(dòng)時(shí)地板的坐標(biāo)
startingPoint = getGroundPosition(evt);
//移動(dòng)物體時(shí),暫時(shí)屏蔽相機(jī)的移動(dòng)控制
if (startingPoint) { // we need to disconnect camera from canvas
setTimeout(function () {
camera.detachControl(canvas);
}, 0);
}
}
}
//鼠標(biāo)點(diǎn)擊著移動(dòng)中
var onPointerMove = function (evt) {
if (!startingPoint) {
return;
}
if (!currentMesh) {
return;
}
//更新當(dāng)前點(diǎn)擊的地板位置
var current = getGroundPosition(evt);
if (!current) {
return;
}
//更新當(dāng)前小車(chē)坐標(biāo)位置為點(diǎn)擊的地板位置
console.log('startingPoint', startingPoint)
var diff = current.subtract(startingPoint);
console.log('diff', diff)
currentMesh.position.addInPlace(diff);
console.log("currentMesh.name", currentMesh.name)
//更新位置信息
startingPoint = current;
}
//鼠標(biāo)點(diǎn)擊后松開(kāi)
var onPointerUp = function () {
//如果速度選擇窗口位關(guān)閉转捕,則關(guān)閉窗口
if (buttonClicked) {
buttonClicked = false
speedSelect.isVisible = false
}
//恢復(fù)相機(jī)移動(dòng)控制
if (startingPoint) {
camera.attachControl(canvas, true);
startingPoint = null;
return;
}
}
//canvas綁定監(jiān)聽(tīng)事件
canvas.addEventListener("pointerdown", onPointerDown, false);
canvas.addEventListener("pointerup", onPointerUp, false);
canvas.addEventListener("pointermove", onPointerMove, false);
}
async function initRobot() {
console.log('initRobot')
//模型url路徑
const url = "http://localhost:8088/static/model/"
//模型名稱
const modelName = "sportcar.babylon"
var result = await BABYLON.SceneLoader.ImportMeshAsync(null, url, modelName, scene);
var meshes = result.meshes
console.log("meshes", meshes)
//不直接實(shí)例化小車(chē)節(jié)點(diǎn)作岖,car對(duì)象存儲(chǔ)meshes網(wǎng)格列表,在小車(chē)引入碰撞體后再實(shí)例化
car = meshes
}
function initScene() {
//獲取到renderCanvas這個(gè)元素
var canvas = document.getElementById("renderCanvas");
//初始化引擎
engine = new BABYLON.Engine(canvas, true);
//初始化場(chǎng)景
var scene = new BABYLON.Scene(engine);
//注冊(cè)一個(gè)渲染循環(huán)來(lái)重復(fù)渲染場(chǎng)景
engine.runRenderLoop(function () {
scene.render();
});
//瀏覽器窗口變化時(shí)監(jiān)聽(tīng)
window.addEventListener("resize", function () {
engine.resize();
});
//相機(jī)初始化
camera = new BABYLON.ArcRotateCamera("Camera", 0, 0, 0, new BABYLON.Vector3(0, 0, 0), scene);
//這里的值可通過(guò)課程6的相機(jī)控制手動(dòng)控制獲取期望位置
camera.alpha = 1.1383885512243588
camera.beta = 1.3642551964995249
camera.radius = 50
// (new BABYLON.Vector3(18, 9, 39));
//相機(jī)角度限制
camera.upperBetaLimit = 1.5;//最大z軸旋轉(zhuǎn)角度差不多45度俯瞰
camera.lowerRadiusLimit = 50;//最小縮小比例
camera.upperRadiusLimit = 1500;//最大放大比例
//變焦速度
camera.wheelPrecision = 1; //電腦滾輪速度 越小靈敏度越高
camera.pinchPrecision = 20; //手機(jī)放大縮小速度 越小靈敏度越高
scene.activeCamera.panningSensibility = 100;//右鍵平移靈敏度
// 將相機(jī)和畫(huà)布關(guān)聯(lián)
camera.attachControl(canvas, true);
//燈光初始化
var light = new BABYLON.HemisphericLight("light1", new BABYLON.Vector3(0, 10, 0), scene);
//設(shè)置高光顏色
light.specular = new BABYLON.Color3(0, 0, 0);
//設(shè)置燈光強(qiáng)度
light.intensity = 1
// 綠地初始化
var materialPlane = new BABYLON.StandardMaterial("texturePlane", scene);
materialPlane.diffuseColor = new BABYLON.Color3(152 / 255.0, 209 / 255.0, 115 / 255.0)
materialPlane.backFaceCulling = false;
materialPlane.freeze()
plane = BABYLON.MeshBuilder.CreateDisc("ground", {radius: 3000}, scene);
plane.rotation.x = Math.PI / 2;
plane.material = materialPlane;
plane.position.y = -0.1;
plane.freezeWorldMatrix()
//網(wǎng)格地板初始化
const groundSide = 144;
ground = BABYLON.Mesh.CreateGround("ground", groundSide, groundSide, 1, scene, true);
var groundMaterial = new BABYLON_MATERAIAL.GridMaterial("grid", scene);
groundMaterial.mainColor = BABYLON.Color3.White();//底板顏色
groundMaterial.alpha = 1;//透明度
const gridLineGray = 0.95;
groundMaterial.lineColor = new BABYLON.Color3(gridLineGray, gridLineGray, gridLineGray);
groundMaterial.backFaceCulling = true; // 可看到背面
//大網(wǎng)格間距
groundMaterial.majorUnitFrequency = 16;
//小網(wǎng)格間距
groundMaterial.minorUnitVisibility = 0;
const gridOffset = 8; // 網(wǎng)格偏移量
groundMaterial.gridOffset = new BABYLON.Vector3(gridOffset, 0, gridOffset);
groundMaterial.freeze(); // 凍結(jié)材質(zhì)五芝,優(yōu)化渲染速度
ground.material = groundMaterial
ground.freezeWorldMatrix()
//天空盒初始化
var skyMaterial = new BABYLON_MATERAIAL.SkyMaterial("skyMaterial", scene);
skyMaterial.inclination = 0
skyMaterial.backFaceCulling = false;
skybox = BABYLON.Mesh.CreateBox("skyBox", 5000.0, scene);
skybox.material = skyMaterial;
return scene
}
export default {
name: "test",
data() {
return {
show3Dcanvans: true,
//移動(dòng)窗口配置
windowParams: {
movable: true,
resizable: false
}
}
},
async mounted() {
//加載場(chǎng)景
await loadScene()
},
methods: {
setCameraPosition(type, value) {
console.log(type, value)
switch (type) {
case 'alpha':
camera.alpha += value
break;
case 'beta':
camera.beta += value
break
case 'radius':
camera.radius += value
break
}
let {alpha, beta, radius} = camera
console.log(`更改后的值:${alpha},${beta},${radius}`)
}
}
}
</script>
<style scoped>
#renderCanvas {
width: 680px;
height: 680px;
touch-action: none;
z-index: 10000;
border-radius: 10px;
}
.btn {
background-color: #D9D9D9;
padding: 2px 15px;
margin: 5px;
border-radius: 4px;
width: 50px;
}
#window1 {
background-color: #505781;
border-radius: 10px;
width: 700px;
position: absolute;
top: 5px;
right: 55px;
z-index: 10005;
}
.header {
padding: 10px 20px 5px 20px;
color: white;
display: flex;
}
</style>
- utils.js - 公用方法封裝
var utils = {
//meshs中根據(jù)名稱獲取mesh
getMeshFromMeshs(newMeshes, name) {
var mesh = null
newMeshes.forEach(m => {
if (m.name == name) {
mesh = m
}
})
return mesh
},
//獲取mesh的尺寸信息
getMeshSize(checkmesh) {
const sizes = checkmesh.getHierarchyBoundingVectors()
const size = {
x: (sizes.max.x - sizes.min.x),
y: (sizes.max.y - sizes.min.y),
z: (sizes.max.z - sizes.min.z)
}
return size
},
}
export default utils;
代碼分解
本文要實(shí)現(xiàn)的功能:
1痘儡、vue-directive-window基本使用
2、加入頂欄枢步,設(shè)置可移動(dòng)窗口的開(kāi)啟與關(guān)閉
3谤辜、細(xì)節(jié)優(yōu)化,解決加載窗口定位問(wèn)題
0.vue-directive-window庫(kù)安裝與引入
- 安裝依賴
npm install vue-directive-window --save
- 引入模塊
//全局引用
//main.js
import VueDirectiveWindow from 'vue-directive-window'
Vue.use(VueDirectiveWindow);
1.vue-directive-window使用
- vue-directive-window通過(guò)設(shè)置Vue的自定義指令集v-window(directive)來(lái)實(shí)現(xiàn)窗口位置和大小的控制
<template>
<div v-window="windowParams">
<!-- 可移動(dòng)窗口的內(nèi)容价捧,這里放babylonjs的cavans -->
</div>
</template>
<script>
export default {
data() {
return {
//移動(dòng)窗口配置
windowParams: {
movable: true,//可拖動(dòng)
resizable: false//不可改變大小
}
};
},
}
</script>
2.加入頂欄丑念,設(shè)置可移動(dòng)窗口的開(kāi)啟與關(guān)閉
- 加入頂欄,方便移動(dòng)點(diǎn)擊和關(guān)閉
<!--窗口關(guān)閉后顯示的開(kāi)啟按鈕-->
<v-btn
icon
color="#505781"
style="padding: 10px 20px 5px 20px;position: absolute;right: 10px;top: 10px;"
v-show="!show3Dcanvans"
@click="show3Dcanvans=!show3Dcanvans"
>
<v-icon>mdi-equal-box</v-icon>
</v-btn>
<!--可移動(dòng)窗口-->
<div class="window1" v-window="windowParams" v-show="show3Dcanvans">
<!--頂欄-->
<div style="display: flex;justify-content: space-between;">
<!--標(biāo)題-->
<div style="padding: 10px 20px 5px 20px;color: white;display: flex;">仿真
</div>
<!--關(guān)閉窗口按鈕-->
<v-btn
icon
color="white"
style="padding: 10px 20px 5px 20px;"
@click="show3Dcanvans=!show3Dcanvans"
>
<v-icon>mdi-close</v-icon>
</v-btn>
</div>
<!--3d引擎cavans-->
<div style="z-index: 10001;padding-left: 10px;">
<canvas id="renderCanvas"></canvas>
</div>
</div>
3.細(xì)節(jié)優(yōu)化结蟋,解決加載窗口定位問(wèn)題
- 前文中自定義loading加載界面窗口脯倚,其定位是相對(duì)于window全局窗口的趋惨,所以當(dāng)加載窗口存在的時(shí)候拖動(dòng)可移動(dòng)窗口爬坑,加載窗口保留在原來(lái)位置
- 解決方案就是將加載窗口加到可移動(dòng)窗口的元素中,然后使加載窗口相對(duì)于可移動(dòng)窗口定位
function customLoadingUI() {
BABYLON.DefaultLoadingScreen.prototype.displayLoadingUI = function () {
this._loadingDiv = document.createElement("div");
this._loadingDiv.id = "customLoadingScreenDiv";
this._loadingDiv.style.background = "#505781";
this._loadingDiv.style.zIndex = "10006"
this._loadingDiv.style.height = "100%"
var img = new Image()
img.src = url + "loading.gif";
img.style.padding = "15%";
img.style.paddingTop = "30%";
this._loadingDiv.appendChild(img);
this._resizeLoadingUI();
window.addEventListener("resize", this._resizeLoadingUI);
//這兩個(gè)樣式修改需要在this._resizeLoadingUI之后,因?yàn)樵摵瘮?shù)執(zhí)行后會(huì)相對(duì)window窗口定位出cavans的位置挟憔,然后設(shè)置loading的位置
//而我們需要的是將其插入到可移動(dòng)窗口中,以統(tǒng)一窗口的開(kāi)啟關(guān)閉
this._loadingDiv.style.left = "10px"
this._loadingDiv.style.top = "39px"
// document.body.appendChild(this._loadingDiv);
// 修改為
// 獲取當(dāng)前可移動(dòng)窗口元素
let window1 = document.getElementById('window1')
let header = document.getElementById('header')
window1.insertBefore(this._loadingDiv,header)
};
engine.displayLoadingUI();
}
后續(xù)計(jì)劃
Blockly
- 入門(mén)使用blockly
- 自定義block塊
- blockly第三方組件使用
- 接入js-interpreter幻馁,步驟運(yùn)行block塊
- ......(想到啥寫(xiě)啥)