使用 Babylonjs Web3D 渲染引擎制作床垫定制系统(Typescript版)

Babylonjs是一款开源及免费的Web3D渲染引擎。

本示例的开发环境为VS2019,基于Vue的Typescript项目。演示地址 http://www.iotsys.net/dz/index.html

在新建项目窗口中选择 Typescript -> 所有平台 -> Web 再选择 基本Vue.js Web 应用程序。

使用 Babylonjs Web3D 渲染引擎制作床垫定制系统(Typescript版)_第1张图片

 安装npm包

开发包 @babylonjs/[email protected]

            @babylonjs/[email protected]   (GUI扩展库)

标准包

            [email protected]

            [email protected]     (GUI扩展库)

            linqts

使用 Babylonjs Web3D 渲染引擎制作床垫定制系统(Typescript版)_第2张图片

在public文件夹下创建材质目录

使用 Babylonjs Web3D 渲染引擎制作床垫定制系统(Typescript版)_第3张图片

在src目录下创建目录和文件夹如下,其中Mattress为床垫类,models为实体类

使用 Babylonjs Web3D 渲染引擎制作床垫定制系统(Typescript版)_第4张图片

在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中做如下修改






生成项目

使用 Babylonjs Web3D 渲染引擎制作床垫定制系统(Typescript版)_第5张图片

 

你可能感兴趣的:(Web3D,babylonjs,前端,html5,TypeScript,VUE)