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

前言

前段时间想要做一个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

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,125评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,293评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,054评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,077评论 1 291
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,096评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,062评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,988评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,817评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,266评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,486评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,646评论 1 347
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,375评论 5 342
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,974评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,621评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,796评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,642评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,538评论 2 352

推荐阅读更多精彩内容