美文网首页
Vue实现图形化积木式编程(二)

Vue实现图形化积木式编程(二)

作者: 温温温B | 来源:发表于2021-08-15 19:45 被阅读0次

    前言

    前段时间想要做一个web端的图形化积木式编程(类似少儿编程)的案例,网上冲浪了一圈又一圈,终于技术选型好,然后代码一顿敲,终于出来了一个雏形。

    TIPS:该案例设计主要参考iRobot Coding,只用做学习用途,侵删。

    https://code.irobot.com/#/

    最终实现效果

    最终实现效果

    本文实现效果

    • 加载模型到场景中


      替换上文中的蓝色正方形为babylon模型文件

    完整代码

    • 加载模型实现
    <template>
      <div style="height: 100%;width: 100%;">
        <div>
          <canvas id="renderCanvas"></canvas>
        </div>
      </div>
    </template>
    
    <script>
    import * as BABYLON from 'babylonjs';
    import * as BABYLON_MATERAIAL from "babylonjs-materials"
    
    async function loadScene() {
      //第一篇场景初始化,可看上一篇文章
      var scene = initScene()
    
      //本文内容,加载网络模型
      await initRobot(scene)
    
      //开启debug窗口
      // scene.debugLayer.show()
    
    }
    
    async function initRobot(scene) {
      console.log('initRobot')
      //模型url路径
      const url = "http://localhost:8887/"
      //模型名称
      const modelName = "sportcar.babylon"
      var result = await BABYLON.SceneLoader.ImportMeshAsync(null, url, modelName, scene);
      var meshes = result.meshes
      console.log("meshes", meshes)
      const scale = 10//缩放比例
      for (var mesh of meshes) {
        mesh.scaling = new BABYLON.Vector3(scale, scale, scale)
      }
    }
    
    function initScene() {
      //获取到renderCanvas这个元素
      var canvas = document.getElementById("renderCanvas");
      //初始化引擎
      var engine = new BABYLON.Engine(canvas, true);
      //初始化场景
      var scene = new BABYLON.Scene(engine);
      //注册一个渲染循环来重复渲染场景
      engine.runRenderLoop(function () {
        scene.render();
      });
      //浏览器窗口变化时监听
      window.addEventListener("resize", function () {
        engine.resize();
      });
    
      //相机初始化
      var camera = new BABYLON.ArcRotateCamera("Camera", 0, 0, 5, new BABYLON.Vector3(0, 0, 10), scene);
      camera.setPosition(new BABYLON.Vector3(20, 200, 400));
      //相机角度限制
      camera.upperBetaLimit = 1.5;//最大z轴旋转角度差不多45度俯瞰
      camera.lowerRadiusLimit = 50;//最小缩小比例
      camera.upperRadiusLimit = 1500;//最大放大比例
      //变焦速度
      camera.wheelPrecision = 1; //电脑滚轮速度 越小灵敏度越高
      camera.pinchPrecision = 20; //手机放大缩小速度 越小灵敏度越高
      scene.activeCamera.panningSensibility = 100;//右键平移灵敏度
      // 将相机和画布关联
      camera.attachControl(canvas, true);
    
      //灯光初始化
      var light = new BABYLON.HemisphericLight("light1", new BABYLON.Vector3(0, 10, 0), scene);
      //设置高光颜色
      light.specular = new BABYLON.Color3(0, 0, 0);
      //设置灯光强度
      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()
      var plane = BABYLON.MeshBuilder.CreateDisc("ground", {radius: 3000}, scene);
      plane.rotation.x = Math.PI / 2;
      plane.material = materialPlane;
      plane.position.y = -0.1;
      plane.freezeWorldMatrix()
    
      //网格地板初始化
      const groundSide = 144;
      var 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; // 可看到背面
      //大网格间距
      groundMaterial.majorUnitFrequency = 16;
      //小网格间距
      groundMaterial.minorUnitVisibility = 0;
      const gridOffset = 8; // 网格偏移量
      groundMaterial.gridOffset = new BABYLON.Vector3(gridOffset, 0, gridOffset);
      groundMaterial.freeze(); // 冻结材质,优化渲染速度
      ground.material = groundMaterial
      ground.freezeWorldMatrix()
    
      //天空盒初始化
      var skyMaterial = new BABYLON_MATERAIAL.SkyMaterial("skyMaterial", scene);
      skyMaterial.inclination = 0
      skyMaterial.backFaceCulling = false;
      var skybox = BABYLON.Mesh.CreateBox("skyBox", 5000.0, scene);
      skybox.material = skyMaterial;
    
      return scene
    }
    
    
    export default {
      name: "test",
      data() {
        return {}
      },
      async mounted() {
        //加载场景
        await loadScene()
      },
    }
    </script>
    
    <style scoped>
    #renderCanvas {
      width: 680px;
      height: 680px;
      touch-action: none;
      z-index: 10000;
      border-radius: 10px;
    }
    </style>
    

    babylonjs模型格式转换与导入

    0、在开源模型网上下载一个模型/自己制作一个
    • emmmm,没有云服务器的同学可以直接跳到第2步的方案二啦!
    • 文件格式 .glTF, .glb, .obj, .babylon(实际上只想要obj/babylon格式🤦♂️)
    • ⬇️上述模型都可在下方网址在线预览模型⬇️

    https://sandbox.babylonjs.com/

    • ⬇️3d开源素材哪里找⬇️

    https://www.zhihu.com/question/19959438

    • 笔者从这里下载了一个心爱的老爷车(有obj文件就选obj文件,其他格式的话,需要在blender等3d引擎中导出obj)😬


      image.png
    • 为什么非要obj格式文件?
      为了形成规范,笔者所做的系统中模型能够正常运行的基础是nodes如下图格式(每一个mesh网格都没有父节点,不存在嵌套关系)。obj文件在线预览就是这种规范。当然,其他导入的模型能符合这个规范也行。

    • 正确nodes结构(sandbox.babylonjs.com中打开obj文件)


      例子1
      例子2
    • 错误nodes结构((sandbox.babylonjs.com中打开glb文件)


      带__root__父节点的nodes
    1、转换为.babylon文件

    obj文件导入在线预览网页后,笔者选择了统一把模型转化为.babylon模型文件

    • 理论上,将obj文件拖入上方打开在线模型预览窗口,应该出现下面模型预览界面。


      理论上,导入obj文件后,会自动关联mtl文件,然后mtl文件自动关联所有图片
    • 实际上


      导入obj文件时,无法导入文件关联的mtl文件
      导入mtl文件时,无法导入文件关联的图片文件
    • 这是因为浏览器内核为了安全性,不能代码加载本地资源,所以只能手动导入了。

    • 具体步骤
      1.0、准备好obj、mtl、mtl关联的所有png图片(不太清楚的可以用记事本打开mtl文件看一看)
      1.1、打开【https://sandbox.babylonjs.com】,选择或拖动obj文件进去。
      1.2、拖动关联的mtl文件进去。
      1.3、打开调试工具栏,左边工具栏点击展开Textures,点击里面的每一个png,然后点击右边的【load Texture from file】选择对应图片。

      导入mtl文件关联的材质图片指引

    1.4、按照提示红框提示即可导出.babylon格式模型文件。导出的babylon格式,其模型结构和obj的模型结构是一致的,只不过.babylon格式模型相当于将obj、mtl、所有png都整合在一起形成一个文件了。有兴趣的小伙伴可以看看左侧Scene菜单,在代码,可以遍历该模型对象找到某个node节点,然后对这个节点进行位置变换从而到达动画效果,或者对某个节点进行材质变换,颜色变化。

    导出babylon文件格式操作指引
    2、将模型文件放在服务器上
    方案一(最新发现的方案)
    • Web Server for Chrome(一个谷歌浏览器插件),可以将本地文件搭建为一个本地HTTP服务器,插件地址⬇️

    https://chrome.google.com/webstore/detail/web-server-for-chrome/ofhbbkphhbklhfoeikjpcbhemlocgigb

    本地http服务启动指引
    方案二
    • 由于安全原因🤦,无法加载本地模型[file://],只能把它放在服务器上[http(s)://]
    • 如果出现了Access-Control-Allow-Origin跨域问题,可以看看这里,改一下配置文件

    https://segmentfault.com/a/1190000012550346

    方案三

    没有服务器的同学,也可以使用官方提供的模型进行学习啦

    https://doc.babylonjs.com/toolsAndResources/assetLibraries/availableMeshes

    3、加载模型

    虽然笔者使用的是obj模型结构(所有mesh都是同级没有父节点的,不知道能不能这样一刀切🤦♂️,我想表达是没有父节点),但是这里也会有glb模型结构(带有root父节点的mesh加载)的模型加载。

    3.1、async同步形式(obj模型结构)
    async function initRobot(scene) {
      console.log('initRobot')
      //模型url路径
      const url = "http://localhost:8887/"
      //模型名称
      const modelName = "sportcar.babylon"
      var result = await BABYLON.SceneLoader.ImportMeshAsync(null, url, modelName, scene);
      var meshes = result.meshes
      console.log("meshes", meshes)
      const scale = 10//缩放比例
      for (var mesh of meshes) {
        mesh.scaling = new BABYLON.Vector3(scale, scale, scale)
      }
    }
    
    3.2、回调的形式(obj模型结构)
    function initRobot(scene) {
      console.log('initRobot')
      //模型url路径,上传了自己模型到服务器的同学可以将url和modelName改为对应路径即可
      const url = "http://localhost:8887/"
      //模型名称
      const modelName = "sportcar.babylon"
      BABYLON.SceneLoader.ImportMesh("", url, modelName, scene, function (meshes) {
        console.log(meshes)
        const scale = 10//缩放比例
        for (var mesh of meshes) {
          mesh.scaling = new BABYLON.Vector3(scale, scale, scale)
        }
      });
    }
    
    3.3、async同步形式(glb模型结构)
    async function initRobot(scene) {
      console.log('initRobot')
      //模型url路径,上传了自己模型到服务器的同学可以将url和modelName改为对应路径即可
      const url = "https://models.babylonjs.com/"
      //模型名称
      const modelName = "toast_acrobatics.glb"
      var result = await BABYLON.SceneLoader.ImportMeshAsync(null, url, modelName, scene);
      var meshes = result.meshes
      console.log(meshes)
      var scale = 100//缩放比例
      //只对__root__节点进行操作
      meshes[0].scaling = new BABYLON.Vector3(scale, scale, scale)
      meshes[0].position._x += 24
      // scene.createDefaultEnvironment()
    }
    
    3.4、回调的形式(glb模型结构)
    function initRobot(scene) {
      console.log('initRobot')
      //模型url路径,上传了自己模型到服务器的同学可以将url和modelName改为对应路径即可
      const url = "https://models.babylonjs.com/"
      //模型名称
      const modelName = "toast_acrobatics.glb"
      BABYLON.SceneLoader.ImportMesh("", url, modelName, scene, function (meshes) {
        scene.createDefaultEnvironment()
        console.log(meshes)
        var scale = 100//缩放比例
        //只对__root__节点进行操作
        meshes[0].scaling = new BABYLON.Vector3(scale, scale, scale)
        meshes[0].position._x += 24
        // scene.createDefaultEnvironment()
      });
    }
    
    • 加载很慢的话,可以f12开发者工具network看看是不是模型还在下载,模型很大,你要忍一下😬
    • 可以发现meshes打印出来数据列表(列表中每个t对象的name)和本文第1点中在线查看模型左侧Scene菜单中的nodes列表中节点名称是一致的
    问题:导入模型后一片黑
    • 解决方案:
    scene.createDefaultEnvironment()
    
    4、(额外)启用调试窗口
    scene.debugLayer.show()
    
    • 是不是看到了熟悉的窗口

    后续计划

    Babylon.js

    • 点击移动物体
    • 自定义启动界面
    • 物体重力效果
    • babylonjs-gui 按钮实现
    • babylonjs+ammojs 碰撞体实现
    • 将3d界面放入可拖动窗口中

    Blockly

    • 入门使用blockly
    • 自定义block块
    • blockly第三方组件使用
    • 接入js-interpreter,步骤运行block块
    • ......(想到啥写啥)

    开源项目GitHub链接

    https://github.com/Wenbile/Child-Programming-Web

    你的点赞是我继续编写的动力

    相关文章

      网友评论

          本文标题:Vue实现图形化积木式编程(二)

          本文链接:https://www.haomeiwen.com/subject/ghyubltx.html