现在,3D 模型已经用于各种不同的领域。在医疗行业使用它们制作器官的精确模型;电影行业将它们用于活动的人物、物体以及现实电影;视频游戏产业将它们作为计算机与视频游戏中的资源;在科学领域将它们作为化合物的精确模型;建筑业将它们用来展示提议的建筑物或者风景表现;工程界将它们用于设计新设备、交通工具、结构以及其它应用领域;在最近几十年,地球科学领域开始构建三维地质模型,而且 3D 模型经常做成动画,例如,在故事片电影以及计算机与视频游戏中大量地应用三维模型。它们可以在三维建模工具中使用或者单独使用。为了容易形成动画,通常在模型中加入一些额外的数据,例如,一些人类或者动物的三维模型中有完整的骨骼系统,这样运动时看起来会更加真实,并且可以通过关节与骨骼控制运动。
这些种种都让我们前端开发者觉得如果我们可以不用学习 unity3d 或者其他游戏开发工具就能实现 3D 效果,而且能够精准的靠代码来控制移动或者方向就好了。。。于是我利用 HT For Web 中的 3D组件 来实现了一个小例子,用了 HT 中 3D组件 的大部分功能,做这个例子就是想把 3D 组件好好的掌握,尽量放进一个例子中,到时候别人有需要就可以参考了。
先来看看整体实现的效果图:
用 HT for Web,现有的 3d 模板创建三层底板不是问题,问题是要如何将图中第一层的“电脑”和“机柜组件”放上去?我是在网上 down 下来的 obj 格式的文件,然后我利用 HT 中的 ht.Default.loadObj(objUrl, mtlUrl, params) 函数将模型加载进去,其中的 params 部分可以参考 http://www.hightopo.com/guide...,代码如下:
ht.Default.loadObj('obj/机柜组件1.obj', 'obj/机柜组件1.mtl', { //加载 obj 文件 cube: true, //是否将模型缩放到单位1的尺寸范围内,默认为false center: true, //模型是否居中,默认为false,设置为true则会移动模型位置使其内容居中 shape3d: 'box', //如果指定了shape3d名称,则HT将自动将加载解析后的所有材质模型构建成数组的方式,以该名称进行注册 finishFunc: function(modelMap, array, rawS3){ //用于加载后的回调处理 if(modelMap){ device2 = createNode('box', floor1); //创建一个节点,在第一层“地板”上 device2.p3([x1-120, y1+13, z1+60]); //设置这个节点坐标 device2.s3(rawS3); //设置这个节点大小 createEdge(device1, device2); //创建连线 device3 = createNode('box', floor1); device3.s3(rawS3); device3.p3([x1+120, y1+13, z1+60]); createEdge(device1, device3); } } });
其中 finishiFunc 函数中的三个参数定义如下:
modelMap:调用 ht.Default.parseObj 解析后的返回值,若加载或解析失败则返回值为空
array:所有材质模型组成的数组
rawS3:包含所有模型的原始尺寸
一般在实际应用中我们都会将图元的大小设置为模型的原始尺寸。
“电脑”上方有个红色的立体能旋转的“警告”,是依靠 ht.Default.setShape3dModel 函数(HT for Web 建模手册 http://www.hightopo.com/guide... 3d 模型,在 ht 中,封装好的建模函数有很多,比较基础的就是球体,圆柱,立方体等等,这边我用的是构造环形的方法 createRingModel 来生成“警告”最外面的环,感叹号的上部分就是用的 createSmoothSphereModel 构造的球体,感叹号的下部分就是用 createSmoothCylinderModel 来构造的圆柱。我一开始直接使用了 3d 模型中封装好的函数,导致后来根本不知道函数中使用的参数是做什么用的,而且也不明白 3d 模型是怎么构成的,然后自己又重新看了前面的“模型基础”,才知道原来 3d 模型采用的一个面,最基础的是三角面,之后复杂的面也是由多个三角面来形成的,然后绕着一根特定的轴旋转之后形成的,当然,这个轴是你来决定的,不同的轴可以生成不同的形状,对于颜色等风格方面的设置可以参考 HT for Web 风格手册(http://www.hightopo.com/guide...。至于如何让这个 3d 模型旋转起来,ht 中封装了 addScheduleTask(Task) 方法,我在第三层 Task 中调用了 ht 封装的一个旋转函数 setRotation 来设置旋转的顺序和方向,并且指定了旋转的对象。以下是自定义“警告”的 3d 模型的方法(注意:因为本例的模型是自定义组合的,如果要设置整体模型的颜色要用 “all.blend” style 属性):
function createAlarm(device, formPane) { var ringModel = ht.Default.createRingModel([ 8, 1, 10, 1, 10, -1, 8, -1, 8, 1 ], null, null, false, false, 100);//根据xy平面的曲线,环绕一周形成3D模型。 var sphereModel = ht.Default.createSmoothSphereModel(8, 8, 0, Math.PI*2, 0, Math.PI, 2);//构建光滑球体模型 var cylinderModel = ht.Default.createSmoothCylinderModel(8, true, true, 1, 2, 0, Math.PI*2, 8);//构建光滑圆柱体模型 var alarmArr = [//组合模型 由三个模型ringModel、sphereModel、cylinderModel组合而成 { shape3d: ringModel,//定义模型类型 r3: [Math.PI/2, 0, 0],//设置旋转角度 color: {//设置模型颜色 func: 'style@all.blend',//数据绑定style样式中的all.blend属性,可通过data.s()获取和设置这个属性 } },{ shape3d: sphereModel, t3: [0, 4, 0], color: { func: 'style@all.blend', } },{ shape3d: cylinderModel, t3: [0, -3, 0], color: { func: 'style@all.blend', } } ]; ht.Default.setShape3dModel('alarm', {//注册自定义3D模型 shape3d: alarmArr }); var alarmTip = createNode('alarm', device);//创建shape3d为alarm的节点 alarmTip.s3([2, 2, 2]);//设置节点大小 alarmTip.p3(device.p3()[0], device.p3()[1]+60, device.p3()[2]); alarmTip.s('all.blend', 'red');//改变此属性可改变模型的颜色,因为模型创建的时候已经数据绑定了 return alarmTip; }
接下来看看怎么让这个“告警”节点“闪烁”,我是直接将这个动画跟节点绑定,这样可以直接通过节点来控制动画。所以在上面我们创建 alarm 的模型时就可以直接将动画绑在节点上:
if(formPane){ alarmNode.scaleFunc = function() {//设置大小变化动画 var size = alarmNode.s3();//获取节点的大小 if (size[0] === 2 && size[1] === 2 && size[2] === 2) alarmNode.s3([1, 1, 1]); else alarmNode.s3([2, 2, 2]); alarmNode.scaleTimer = setTimeout(alarmNode.scaleFunc, formPane.v('scaleInterval'));//设置动画 } alarmNode.blinkFunc = function(){//设置闪烁的动画 var color = alarmNode.s('all.blend');//获取节点的style样式 if (color === 'red') alarmNode.s({'all.blend': 'yellow'});//如果节点颜色为红色,那么设置为黄色 else alarmNode.s({'all.blend': 'red'}); alarmNode.blinkTimer = setTimeout(alarmNode.blinkFunc, formPane.v('blinkInterval')); } alarmNode.rotateFunc = function() {//设置旋转动画 alarmNode.setRotation(alarmNode.getRotation() + Math.PI/20);//获取节点当前的旋转角度,在这个旋转角度之上添加 Math.PI/20 个角度 alarmNode.rotateTimer = setTimeout(alarmNode.rotateFunc, formPane.v('rotInterval')); } }
上面的动画我设置了可以通过 form 表单面板上的属性来控制节点闪烁的速度,以及闪烁节点的动画等等,主要说一下这个功能在 form 表单上的实现:
formPane.addRow([//向form表单面板上添加一行元素 { checkBox: {//复选框 label: 'Enable Blink',//复选框对应的文本内容 selected: true,//设置选中复选框 onValueChanged: function(){//复选框值变化时回调的函数 var data = dataModel.getDataByTag('colorAlarm');//通过tag标签获取节点 if (this.getValue()) {//获取复选框当前值true/false data.blinkTimer = setTimeout(data.blinkFunc, formPane.v('blinkInterval'));//直接通过设置节点的blinkTimer来设置动画 } else { clearTimeout(data.blinkTimer);//清除动画 } } } }, { id: 'blinkInterval',//form可以通过getValue(简写为v)来获取这个item的值 slider: {//设置了该属性后HT将根据属