Babylonjs是一款开源及免费的Web3D渲染引擎。
本示例的开发环境为VS2019,基于Vue的Typescript项目。演示地址 http://www.iotsys.net/dz/index.html
在新建项目窗口中选择 Typescript -> 所有平台 -> Web 再选择 基本Vue.js Web 应用程序。
安装npm包
开发包 @babylonjs/[email protected]
@babylonjs/[email protected] (GUI扩展库)
标准包
[email protected] (GUI扩展库)
linqts
在public文件夹下创建材质目录
在src目录下创建目录和文件夹如下,其中Mattress为床垫类,models为实体类
在models.ts中建立Fabric面料类
export class Fabric {
public name: string = ''; //面料名称
public url: string = ''; //材质地址
public video: string = ''; //材质视频说明
public minDeep: number = 1; //最小厚度
public maxDeep: number = 1; //最大厚度
public type: number = 1; // 材质类型
public price: number = 0; //单位厚度每平米价格
public des: string = ""; //面料描述
}
在mattress.ts中创建Mattress类
import { Engine, Scene, Light, Vector3, HemisphericLight, MeshBuilder, StandardMaterial, Texture, ArcRotateCamera, Mesh, ActionManager, ExecuteCodeAction, ActionEvent, Nullable } from 'babylonjs';
import { StackPanel, Control, AdvancedDynamicTexture, Button, TextBlock, Rectangle, Line, ScrollViewer, Slider, Grid } from 'babylonjs-gui';
import { List } from 'linqts';
import { Fabric } from './models';
export class Mattress {
private _canvas: HTMLCanvasElement; //canvas对象
private _engine: Engine;
private _scene: Scene;
private _camera: ArcRotateCamera; //摄像头
private _light: HemisphericLight; //环境光源
private _advancedTexture: AdvancedDynamicTexture; //2D GUI 支持
private _layer: number = 0;
private _pickMesh: Mesh | null = null;
private _width: number = 0; //床垫宽度
private _length: number = 0; //床垫长度
private _priceGrid: Grid;
private _priceTextBlock: TextBlock;
/*
type 1 布料 只能用于最外层
2
price 单位高度 每平米价格
*/
private _fabrics: List = new List([{
name: '0',
url: '/textures/mattress/fabric/0.jpg',
video: '',
minDeep: 1,
maxDeep: 1,
type: 1,
price: 10,
des: '请设置'
}, {
name: '1',
url: '/textures/mattress/fabric/1.jpg',
video: '',
minDeep: 0.1,
maxDeep: 0.1,
type: 1,
price: 500,
des: '毛绒面料'
}, {
name: '2',
url: '/textures/mattress/fabric/2.jpg',
video: '',
minDeep: 5,
maxDeep: 10,
type: 1,
price: 20,
des: '乳胶'
}, {
name: '3',
url: '/textures/mattress/fabric/3.jpg',
video: '',
minDeep: 5,
maxDeep: 20,
type: 1,
price: 20,
des: '椰棕'
}, {
name: '4',
url: '/textures/mattress/fabric/4.jpg',
video: '',
minDeep: 5,
maxDeep: 20,
type: 1,
price: 20,
des: '海绵'
}, {
name: '5',
url: '/textures/mattress/fabric/5.jpg',
video: '',
minDeep: 0.1,
maxDeep: 0.1,
type: 1,
price: 300,
des: '针织面料'
}, {
name: '6',
url: '/textures/mattress/fabric/6.jpg',
video: '',
minDeep: 10,
maxDeep: 20,
type: 1,
price: 20,
des: '弹簧'
}]);
private _layers: List = new List();
private _deepSlider: Slider = new Slider();
constructor(public elementId: string, public width: number, public length: number) {
var _this = this;
this._width = width;
this._length = length;
this._canvas = document.getElementById(elementId) as HTMLCanvasElement;
this._engine = new Engine(this._canvas, true);
this._scene = new Scene(this._engine);
this._light = new HemisphericLight('light1', new Vector3(0, 1, 0), this._scene);
this._advancedTexture = AdvancedDynamicTexture.CreateFullscreenUI("ui1");
this._priceGrid = new Grid();
this._priceGrid.height = "0px";
this._priceTextBlock = new TextBlock();
this._priceTextBlock.height = "40px";
this._priceTextBlock.color = "white";
this._priceTextBlock.horizontalAlignment = Control.HORIZONTAL_ALIGNMENT_RIGHT;
this._camera = new ArcRotateCamera("Camera", -5.5, 1.2, 260, Vector3.Zero(), this._scene);
this._camera.attachControl(this._canvas, false);
this._camera.onViewMatrixChangedObservable.add(() => {
this._light.direction = this._camera.position;
});
this._scene.onPointerDown = function (evt, pickResult) {
if (!pickResult.hit) {
_this.setLayerAlpha(1);
_this._pickMesh = null;
_this._deepSlider.minimum = 0;
_this._deepSlider.maximum = 0;
_this._deepSlider.value = 0;
}
};
this.createUI();
this.render();
}
createUI() {
var _this = this;
//左面板
var panel = new StackPanel();
panel.width = "250px";
panel.horizontalAlignment = Control.HORIZONTAL_ALIGNMENT_LEFT;
panel.verticalAlignment = BABYLON.GUI.Control.VERTICAL_ALIGNMENT_CENTER;
panel.paddingLeft = "20px";
this._advancedTexture.addControl(panel);
//输出摄像头信息
var button1 = Button.CreateSimpleButton("but1", "Camera Info");
button1.width = "200px";
button1.height = "40px";
button1.color = "white";
button1.background = "green";
button1.onPointerUpObservable.add(function () {
console.log(_this._camera);
});
panel.addControl(button1);
//费用
this._priceTextBlock.horizontalAlignment = BABYLON.GUI.Control.HORIZONTAL_ALIGNMENT_RIGHT;
panel.addControl(this._priceTextBlock);
this._priceGrid.addColumnDefinition(80, true);
this._priceGrid.addColumnDefinition(80, true);
this._priceGrid.addColumnDefinition(80, true);
panel.addControl(this._priceGrid);
//右面版
var rp = new StackPanel();
rp.width = "220px";
//rp.height = "600px";
rp.fontSize = "14px";
rp.paddingRight = "20px";
rp.horizontalAlignment = BABYLON.GUI.Control.HORIZONTAL_ALIGNMENT_RIGHT;
rp.verticalAlignment = BABYLON.GUI.Control.VERTICAL_ALIGNMENT_CENTER;
rp.isPointerBlocker = true;
var header = new TextBlock();
header.text = "层管理";
header.height = "40px";
header.color = "white";
header.textHorizontalAlignment = BABYLON.GUI.Control.HORIZONTAL_ALIGNMENT_LEFT;
header.paddingTop = "10px";
rp.addControl(header);
//层管理功能
var cp = new StackPanel();
cp.width = 1;
cp.horizontalAlignment = BABYLON.GUI.Control.HORIZONTAL_ALIGNMENT_CENTER;
let btn = Button.CreateSimpleButton("before_btn", "新增上方");
btn.height = "40px";
btn.color = "white";
btn.background = "green";
btn.paddingTop = btn.paddingBottom = 5;
btn.onPointerUpObservable.add(function () {
_this.insertLayer(true);
});
cp.addControl(btn);
btn = Button.CreateSimpleButton("after_btn", "新增下方");
btn.height = "40px";
btn.color = "white";
btn.background = "green";
btn.paddingTop = btn.paddingBottom = 5;
btn.onPointerUpObservable.add(function () {
_this.insertLayer(false);
});
cp.addControl(btn);
btn = Button.CreateSimpleButton("remove_btn", "删除");
btn.height = "40px";
btn.color = "white";
btn.background = "green";
btn.paddingTop = btn.paddingBottom = 5;
btn.onPointerUpObservable.add(function () {
_this.removeLayer();
});
cp.addControl(btn);
btn = Button.CreateSimpleButton("remove_btn", "上移");
btn.height = "40px";
btn.color = "white";
btn.background = "green";
btn.paddingTop = btn.paddingBottom = 5;
btn.onPointerUpObservable.add(function () {
_this.moveLayer(true);
});
cp.addControl(btn);
btn = Button.CreateSimpleButton("remove_btn", "下移");
btn.height = "40px";
btn.color = "white";
btn.background = "green";
btn.paddingTop = btn.paddingBottom = 5;
btn.onPointerUpObservable.add(function () {
_this.moveLayer(false);
});
cp.addControl(btn);
rp.addControl(cp);
//材料选择
header = new TextBlock();
header.text = "材料";
header.height = "40px";
header.color = "white";
header.textHorizontalAlignment = BABYLON.GUI.Control.HORIZONTAL_ALIGNMENT_LEFT;
header.paddingTop = "10px";
rp.addControl(header);
var sv = new ScrollViewer();
sv.width = "150px";
sv.height = "256px";
sv.background = "#CCCCCC";
rp.addControl(sv);
var fp = new StackPanel();
fp.width = 1;
fp.verticalAlignment = BABYLON.GUI.Control.VERTICAL_ALIGNMENT_TOP;
sv.addControl(fp);
_this._fabrics.ForEach((f, i) => {
if (i == 0)
return;
if (f !== undefined) {
let btn = Button.CreateImageWithCenterTextButton(
"but-" + f.name,
f.des,
f.url
);
btn.height = "128px";
btn.width = "128px";
btn.onPointerUpObservable.add(() => {
_this.setMaterial(_this._pickMesh, f);
});
fp.addControl(btn);
}
});
//材料设置
header = new TextBlock();
header.text = "材料设置";
header.height = "40px";
header.color = "white";
header.textHorizontalAlignment = BABYLON.GUI.Control.HORIZONTAL_ALIGNMENT_LEFT;
header.paddingTop = "10px";
rp.addControl(header);
var deepHeader = new TextBlock();
deepHeader.text = "厚度";
deepHeader.height = "40px";
deepHeader.color = "white";
deepHeader.textHorizontalAlignment = BABYLON.GUI.Control.HORIZONTAL_ALIGNMENT_LEFT;
deepHeader.paddingTop = "10px";
deepHeader.paddingLeft = "10px";
rp.addControl(deepHeader);
this._deepSlider.minimum = 1;
this._deepSlider.maximum = 1;
this._deepSlider.value = 1;
this._deepSlider.step = 1;
this._deepSlider.height = "20px";
this._deepSlider.width = "200px";
this._deepSlider.onValueChangedObservable.add(function (value) {
if (_this._pickMesh == null) {
deepHeader.text = ``;
return;
}
_this._pickMesh.scaling.y = value;
deepHeader.text = `厚度${value}厘米`;
_this.setLayerPosition(true);
});
rp.addControl(this._deepSlider);
this._advancedTexture.addControl(rp);
}
createLabel(meshName: string, text: string) {
let mesh = this._scene.getMeshByName(meshName);
if (mesh != null) {
var label = new Rectangle();
label.background = "black"
label.height = "30px";
label.alpha = 0.5;
label.width = "100px";
label.cornerRadius = 20;
label.thickness = 1;
label.linkOffsetY = 30;
label.top = "10%";
label.zIndex = 5;
label.verticalAlignment = BABYLON.GUI.Control.VERTICAL_ALIGNMENT_TOP;
this._advancedTexture.addControl(label);
var text1 = new TextBlock();
text1.text = text;
text1.color = "white";
label.addControl(text1);
var line = new Line();
line.alpha = 0.5;
line.lineWidth = 5;
line.dash = [5, 10];
this._advancedTexture.addControl(line);
line.linkWithMesh(mesh);
line.connectedControl = label;
}
}
createFabric(fabricName: string, index: number = -1): Mesh {
let meshName = `mesh_${this._layer}`;
let fabric = this._fabrics.First(m => m !== undefined && m.name === fabricName);
let box = MeshBuilder.CreateBox(meshName, { size: 1, width: this.width, height: 1, depth: this.length, updatable: true }, this._scene);
box.scaling.y = fabric.minDeep;
this._layer++;
let material = new StandardMaterial(fabric.name, this._scene);
material.diffuseTexture = new Texture(fabric.url, this._scene);
box.material = material;
box.actionManager = new ActionManager(this._scene);
box.actionManager.registerAction(new ExecuteCodeAction(ActionManager.OnPickTrigger, (evt: ActionEvent) => {
this.setLayerAlpha(0.25);
this._pickMesh = box;
if (box.material) {
box.material.alpha = 1;
let name = box.material.name;
console.log(name);
let f = this._fabrics.First(m => m !== undefined && m.name === name);
this._deepSlider.minimum = f.minDeep;
this._deepSlider.maximum = f.maxDeep;
this._deepSlider.value = box.absoluteScaling.y;
}
}));
if (index == -1)
this._layers.Add(box);
else
this._layers.Insert(index, box);
return box;
}
setMaterial(mesh: Mesh | null, fabric: Fabric) {
let m = new StandardMaterial(fabric.name, this._scene);
m.diffuseTexture = new Texture(fabric.url, this._scene);
if (mesh != null) {
mesh.material = m;
mesh.scaling.y = fabric.minDeep;
this._deepSlider.minimum = fabric.minDeep;
this._deepSlider.maximum = fabric.maxDeep;
this._deepSlider.value = fabric.minDeep;
}
this.setLayerPosition();
}
insertLayer(before: boolean) {
let index = 0;
if (this._pickMesh == null) {
if (before) {
index = -1;
}
}
else {
index = this._layers.IndexOf(this._pickMesh);
if (before) {
index++;
}
}
var box = this.createFabric("0", index);
if (box.material)
box.material.alpha = 0.5;
this.setLayerPosition();
}
removeLayer() {
if (this._pickMesh == null)
return;
this._layers.Remove(this._pickMesh);
this._scene.removeMesh(this._pickMesh);
this._pickMesh = null;
this.setLayerPosition();
this.setLayerAlpha(1);
}
moveLayer(up: boolean) {
if (this._pickMesh == null)
return;
let index = this._layers.IndexOf(this._pickMesh);
if (up) {
if (index === this._layers.Count() - 1)
return;
} else {
if (index === 0)
return;
}
this._layers.Remove(this._pickMesh);
if (up) {
this._layers.Insert(index + 1, this._pickMesh);
} else {
this._layers.Insert(index - 1, this._pickMesh);
}
this.setLayerPosition();
}
setLayerPosition(abscaling: boolean = false) {
var y = 0;
this._layers.ForEach((m) => {
if (m) {
m.position.y = y + m.scaling.y / 2;
//if (abscaling)
// y += m.absoluteScaling.y + 10;
//else
y = m.position.y + m.scaling.y / 2 + 10;
}
});
this.createPriceGrid();
}
setLayerAlpha(alpha: number) {
this._layers.ForEach((m) => {
if (m && m.material) {
m.material.alpha = alpha;
}
});
}
createPriceGrid() {
this._priceGrid.clearControls();
this._priceGrid.height = `${this._layers.Count() * 40}px`;
let total = 0;
this._layers.Reverse().ForEach((m, i) => {
if (!m || !m.material)
return;
this._priceGrid.addRowDefinition(20, true);
let name = m.material.name;
let f = this._fabrics.First(m => m !== undefined && m.name === name);
var t = new TextBlock();
t.text = f.des;
t.height = "40px";
t.color = "white";
t.fontSize = "14px";
t.textHorizontalAlignment = BABYLON.GUI.Control.HORIZONTAL_ALIGNMENT_LEFT;
t.paddingTop = "10px";
t.paddingLeft = "10px";
this._priceGrid.addControl(t, i, 0);
t = new TextBlock();
t.text = `${m.scaling.y}厘米`;
t.height = "40px";
t.fontSize = "14px";
t.color = "white";
t.textHorizontalAlignment = BABYLON.GUI.Control.HORIZONTAL_ALIGNMENT_LEFT;
t.paddingTop = "10px";
t.paddingLeft = "10px";
this._priceGrid.addControl(t, i, 1);
let price = this.width * this.length * f.price * m.scaling.y / 10000;
total += price;
t = new TextBlock();
t.text = `¥${price}`;
t.height = "40px";
t.fontSize = "14px";
t.color = "white";
t.textHorizontalAlignment = BABYLON.GUI.Control.HORIZONTAL_ALIGNMENT_LEFT;
t.paddingTop = "10px";
t.paddingLeft = "10px";
this._priceGrid.addControl(t, i, 2);
});
this._layers.Reverse();
this._priceTextBlock.text = `¥${total}`;
}
render() {
// Run the render loop.
this._engine.runRenderLoop(() => {
this._scene.render();
});
// The canvas/window resize event handler.
let win: any = window;
win.addEventListener('resize', () => {
this._engine.resize();
});
}
};
在App.vue中做如下修改
生成项目